Partner API
HTTP /api/v1 — 菜单、店铺配置、结账、时段、预订、咨询、餐饮、ETA 报价与订单。一份 API 说明,与 OpenAPI 3.1 一致。
Partner API v1 是位于 https://delieta.pl/api/v1 的 HTTP 接口。Delieta 签发给某家公司的 API 密钥就代表这家公司:任何请求都没有公司字段。这是唯一面向人的说明;面向机器的描述是 GET /api/v1/openapi(OpenAPI 3.1,无需密钥)。
已发布的操作:GET /menu、GET /shop-config、POST /shop-heartbeat、GET /checkout/slots、POST /checkout、POST /checkout/quote、GET /checkout/result、GET /events、POST /quotes、POST /orders、GET /orders/{id}、GET /orders/{id}/status、GET /floor-plan、GET /reservations/availability、POST /reservations、POST /inquiries、POST /catering-orders、GET /catering-orders 和 GET /catalog-images/{id}/{filename}。
flowchart LR
bearer["Klucz API"] --> verify["Klucz jest firma"]
verify --> scope{"scope"}
scope -->|"catalog:read"| menu["GET /menu"]
scope -->|"checkout:create"| checkout["POST /checkout"]
scope -->|"orders:write"| handoff["POST /orders"]
handoff --> same["to samo zamowienie co panel"]密钥与权限范围
两种等价的请求头,先找到的生效;查询字符串里的密钥一律不接受。
Authorization: Bearer dlt_live_…
x-delieta-api-key: dlt_live_…
检查顺序:密钥 → 权限范围 → 测试密钥(仅写操作)→ 限流。缺少或错误的密钥返回 401 unauthorized。
| Scope | 密钥类型 | 操作 |
|---|---|---|
catalog:read | 可发布 | GET /api/v1/menu、GET /api/v1/shop-config、GET /api/v1/checkout/slots、GET /api/v1/events、GET /api/v1/orders/{id}/status、GET /api/v1/floor-plan、GET /api/v1/reservations/availability 和 POST /api/v1/shop-heartbeat。menu:read 是旧密钥的别名。 |
checkout:create | 可发布 | POST /api/v1/checkout、POST /api/v1/checkout/quote、GET /api/v1/checkout/result、POST /api/v1/inquiries 和 POST /api/v1/reservations(web 预订)。 |
eta:quote | 保密 | POST /api/v1/quotes |
orders:write | 保密 | POST /api/v1/orders 和 GET /api/v1/orders/{id} |
reservations:write | 保密 | 从公司自己的系统调用 POST /api/v1/reservations(api 预订) |
catering:write | 保密 | POST /api/v1/catering-orders 和 GET /api/v1/catering-orders |
可发布密钥恰好是 catalog:read + checkout:create,仅此而已:店铺持有它,并且只保存在服务端,绝不放进浏览器。可发布密钥不能拥有 orders:write、eta:quote、reservations:write 或 catering:write。空的权限列表什么都打不开 — 403 scope_required。预订按密钥类型计权限:可发布密钥需要 checkout:create,保密密钥需要 reservations:write。
dlt_test_… 读取公司真实的产品并计算报价。所有写操作都会被 403 test_key_readonly 拒绝:POST /api/v1/checkout、/orders、/reservations、/inquiries、/catering-orders 和 /shop-heartbeat。没有单独的测试数据集。
限流
限流按密钥计算,窗口 60 秒。默认每分钟 60 次写 和 600 次读,分开计算,频繁查询不会耗光写入限额。写操作包括创建订单、结账、预订、咨询、餐饮订单和报价,其余为读。超限返回 429 rate_limited;请遵守 Retry-After 以及 RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset。
可发布密钥被许多访客使用,所以它的限流按访客计算。请在 x-delieta-visitor 请求头中传入访客地址;没有该头则按 Origin 计。另有共同上限,为限额的 20 倍。保密密钥忽略此请求头。
错误
v1 的每一次拒绝都是 application/problem+json(RFC 9457)。请根据 code 判断,不要根据文字。
{
"type": "https://delieta.pl/problems/unauthorized",
"title": "Unauthorized",
"status": 401,
"code": "unauthorized",
"detail": "Provide a valid API key as `Authorization: Bearer dlt_live_…`."
}
例外:GET /api/v1/catalog-images/{id}/{filename} 超限时返回 429 和简单的 { "error": "rate_limited" },不是 problem+json。
| code | status | 何时出现 |
|---|---|---|
unauthorized | 401 | 缺少或错误的密钥 |
scope_required | 403 | 密钥缺少所需的权限范围 |
test_key_readonly | 403 | dlt_test_… 尝试写入 |
module_disabled | 403 | 公司的模块已关闭(套餐或店主开关):POST /checkout 与 GET /checkout/slots 的下单、预订、餐饮。已下订单的状态仍可查询 |
invalid_request | 400 | 请求体不是 JSON 或校验失败;详情在 errors[](例如 POST /orders 的 unknown_field) |
idempotency_key_required | 400 | 写操作缺少 Idempotency-Key(8–255 个字符) |
line_price_missing / line_name_missing / total_out_of_range | 400 | 订单行或合计无法定价 |
payload_too_large | 413 | 咨询或餐饮订单的请求体超过 64 KiB |
not_found | 404 | 此密钥下没有这笔订单(他人的 id 同样回答) |
tenant_not_found | 404 | 密钥对应的公司已不存在 |
idempotency_key_conflict | 409 | 相同的幂等键,不同的请求体 |
conflict | 409 | 例如该咨询已关联到另一笔订单 |
venue_not_configured | 409 | 报价,但公司没有门店坐标 |
out_of_range | 409 | 报价或带配送的结账:地址超出公司的配送范围(errors[] 指向 address 字段)。预订中同一个 code 是 422(日期超出范围) |
delivery_unconfigured | 409 | 带配送的结账:门店有公里档位,但没有地址或没有设置配送。自取可用 |
total_changed | 409 | 结账:提供的 expectedTotalGrosze 与现在计算的金额不一致。服务器没有用它来定价 |
product_outside_hours | 409 | 结账:某商品在所选时间不在供应时间内;errors[] 中的行指向 items.N.itemId |
shop_closed | 409 | 店铺此刻已打烊,不接受“尽快”的订单 |
shop_unconfigured | 409 | 没有为支付返回地址保存店铺来源 |
connect_not_ready | 409 | 在线支付账户还不能收款 |
sample_mode | 409 | 示例数据店铺发起在线支付,而支付处于正式模式 |
slot_unavailable | 409 | requestedFor 不在 GET /checkout/slots 给出的时间之内;预订时:该时间已被占用 |
table_taken | 409 | 预订:桌位已被占用(包括刚被别人抢先订走) |
fulfilment_unavailable | 409 | 店铺不提供该履约方式(配送或自取) |
payment_unavailable | 409 | 店铺不接受该支付方式(在线或现金) |
duplicate_external_ref | 409 | 该 externalRef 已为此公司创建过订单 |
unprocessable | 422 | 商品未知或不可用(errors[].code:product_unknown、product_unavailable);餐饮套餐未知 |
below_minimum | 422 | 购物车低于店铺的最低订单金额(只计菜品,不含包装和配送;字段 items) |
product_menu_only | 422 | 结账:某商品是“Tylko w karcie”(仅在菜单上),不能在线下单;errors[] 中的行指向 items.N.itemId。两种情况同时出现时,以此为准 |
address_not_found | 422 | 带配送的结账:无法确定地址(字段 address) |
headcount_out_of_range | 422 | 餐饮询价:人数超出该套餐形式的范围(catering.headcount) |
venue_not_offered | 422 | 餐饮询价:该套餐形式不提供所选地点(catering.venue) |
unknown_add_on | 422 | 餐饮询价:加购项不属于所选套餐形式(catering.addOns.N.key) |
unknown_event, event_not_bookable, event_past, event_phone_only | 422 | 活动询价:没有这场活动、不接受报名、已经举办,或只能电话报名(event.eventId) |
unknown_occurrence, event_in_past | 422 | 活动询价:日期不在列表内或已过去(event.occurrenceId、event.preferredDate) |
participants_out_of_range | 422 | 活动询价:参与人数超出该活动的范围(event.participants) |
option_invalid | 422 | 选项未知、缺少必选组或组内选择过多 |
validation_failed / party_too_large | 422 | 预订:内容不合法,人数超过场地容量 |
rate_limited | 429 | 超过每分钟限额;见限流 |
internal | 500 | 我们这边的故障;请重试 |
create_failed | 500 | POST /orders:订单未能保存;用相同的 Idempotency-Key 重试 |
idempotency_unavailable | 503 | 幂等存储不可用;用相同的键重试 |
delivery_quote_unavailable | 503 | 带配送的结账或报价:暂时无法计算距离;请重试或选择自取 |
module_unavailable | 503 | 无法读取公司的模块(模块并未关闭);稍后重试(Retry-After) |
金额
只用整数格罗什(grosze)。3000 即 30.00 PLN。不用浮点数,金额上没有币种字段。响应里的 currency 恒为 "PLN"。
GET /api/v1/menu
密钥所属公司的产品。权限 catalog:read。不可用的商品会被省略 — DTO 里没有需要客户端处理的 available 标志。“Tylko w karcie”(仅在菜单上,orderable: false)的商品不在 items 中而在 menuOnlyItems 中,供应时间放在 availability 里。响应里没有“现在可供应”标志:店铺自己根据 availability 计算。结构不会改变,只会新增字段。
GET /api/v1/menu
Authorization: Bearer dlt_live_…
{
"currency": "PLN",
"generatedAt": "2026-08-26T12:00:00.000Z",
"locale": "pl",
"categories": [
{ "id": "c1", "slug": "pizza", "name": "Pizza", "sort": 0, "orderable": true, "availability": null }
],
"items": [
{
"id": "i1",
"slug": "margherita",
"categoryId": "c1",
"name": "Margherita",
"description": null,
"basePriceGrosze": 3200,
"allergens": [],
"modifierGroups": [],
"imageUrl": null,
"featured": false,
"sort": 0,
"orderable": true,
"availability": null
}
],
"menuOnlyCategories": [],
"menuOnlyItems": []
}
类别和商品为空数组是有效响应,不是错误。Cache-Control: no-store。响应带有 x-delieta-cache-tag 响应头;自带缓存的店铺可以用它作键。
可选的 ?locale=(pl、en、de、uk)按字段返回该语言的名称和描述,没有翻译时返回波兰语。公司未启用的语言(或未知语言)按波兰语返回;响应里的 locale 说明实际返回的语言。GET /api/v1/shop-config(餐饮套餐名称;同时返回已启用的 locales,波兰语排第一)和 GET /api/v1/floor-plan(区域名称)读取同一个 ?locale=。
imageUrl 是菜品照片的绝对地址(800 px 版本),没有照片时为 null。featured: true 表示店主把该菜品标记为“推荐”。
orderable: false 表示“Tylko w karcie”(仅展示):显示商品和价格,不显示“Do koszyka”按钮。这类商品不在 items 中,而在 menuOnlyItems 中;所有商品都仅展示的分类在 menuOnlyCategories 中,不在 categories 中——不认识这些键的客户端不会显示它们,也永远不会提供无法下单的商品。@delieta/shop-runtime 从 1.8.0 起会把它们合并回来(withMenuOnly)。商品上的标志已考虑其分类(仅展示的分类 = 其中每个商品)。availability 是供应时间:{ "windows": [{ "days": [1,2,3,4], "from": "12:00", "to": "16:30" }], "fromDate": null, "toDate": null }——最多 7 个时段,天从 0 = 星期一算起,两端都包含,时区 Europe/Warsaw。商品有自己的规则,分类也有;两者同时生效。季节已结束(toDate 已过)的商品根本不会进入菜单。菜单有缓存,所以它不会告诉你商品现在是否可售——请根据规则计算(@delieta/shop-runtime 从 1.8.0 起提供:itemSaleState、liveMenu)。结账按履约时间检查规则:requestedFor,或选择“jak najszybciej”时为现在 + 准备时间。
GET /api/v1/catalog-images//
菜品或餐饮套餐的照片。无需密钥:店铺顾客的浏览器会打开 imageUrl 里的地址,所以不能要求密钥。必须知道 id 和准确的文件名 — 不匹配的组合返回 404。?size=card 返回 800 px 宽的版本(更窄的照片原样返回),不带参数则得到原图。
GET /api/v1/catalog-images/{id}/{filename}?size=card
响应是指向文件存储的 302,或图片字节(image/jpeg、image/png、image/webp 或 image/avif)。照片地址的内容永不改变 — 换了照片就有新地址 — 所以响应带 Cache-Control: public, max-age=31536000, immutable。限制:同一 IP 每分钟 600 次请求(rate_limited)。
GET /api/v1/shop-config
密钥所属公司的店铺品牌形象与规则,与其店铺显示的一致。权限 catalog:read — 与菜单相同,可发布密钥已经具备。店铺自己的品牌设置按字段优先;没有设置时,使用公司的名称、标志和颜色。响应带有 x-delieta-cache-tag。
GET /api/v1/shop-config
Authorization: Bearer dlt_live_…
{
"name": "Bella Napoli",
"tagline": "Pizza z pieca opalanego drewnem",
"brandColor": "#e2574c",
"logo": "https://delieta.pl/api/v1/catalog-images/{id}/{filename}",
"banner": "https://delieta.pl/api/v1/catalog-images/{id}/{filename}",
"icon": "https://delieta.pl/api/v1/catalog-images/{id}/{filename}",
"currency": "PLN",
"locale": "pl",
"locales": ["pl", "en"],
"layout": "grid",
"links": {
"phone": "+48000000000",
"email": "kontakt@bella.example",
"instagram": "https://instagram.com/bella.example",
"facebook": "https://facebook.com/bella.example",
"address": "Kwiatowa 5, Bydgoszcz",
"mapUrl": "https://www.google.com/maps/dir/?api=1&destination=Kwiatowa%205%2C%20Bydgoszcz"
},
"modules": ["ordering", "reservations", "catering"],
"ordering": {
"fulfilmentModes": ["delivery", "pickup"],
"paymentModes": ["online", "cash"],
"onlineMethods": ["card", "blik"],
"deliveryFeeGrosze": 500,
"minimumOrderGrosze": 4000,
"prepMinutes": 30
},
"hours": {
"days": [
{ "weekday": 0, "closed": false, "open": "10:00", "close": "22:00" },
{ "weekday": 6, "closed": true, "open": "10:00", "close": "22:00" }
]
},
"closures": [
{ "from": "2026-12-24", "to": "2026-12-26", "note": "Święta" }
],
"cateringFormats": [
{
"id": "f1",
"name": "Lunch firmowy",
"description": "Pizza i napoje dla zespołu.",
"fromPriceGrosze": 3500,
"minHeadcount": 10,
"imageUrl": null,
"items": [
{ "productId": "i1", "name": "Margherita", "quantity": 1, "perPerson": true, "note": null }
]
}
],
"generatedAt": "2026-08-26T12:00:00.000Z"
}
| 字段 | 含义 |
|---|---|
banner | 公司可选的 3:1 横幅,或 null |
icon | 公司的方形图标(至少 512×512,PNG),用作 favicon 和应用图标,或 null:此时店铺沿用自己的图标。Runtime 1.9:shopIcons(config) |
locales | 公司启用的语言,波兰语排第一 |
layout | grid 或 list |
links | 只有门店填写过的内容;门店没有自己的地图链接时,mapUrl 根据地址生成(Google Maps 路线) |
modules | 公司已开启的店铺模块:ordering、reservations、catering、events。这里没有的,店铺就不显示 |
ordering | 店铺如何接单:fulfilmentModes、paymentModes(只有店铺能刷卡时才有 online;列表可以为空)、onlineMethods(card、blik、p24:Stripe 账户上已启用的支付方式;没有 online 时为空;只展示这些。即使提供 online,也可能为空(Delieta 尚未记录哪些方式已启用):此时显示通用的 «Płatność online»,不要列出任何方式)、deliveryFeeGrosze、minimumOrderGrosze、prepMinutes。订单模块关闭时为 null:此时不要显示结账表单 |
hours | 每周营业时间,门店未设置时为 null — 此时不要自己假设营业时间 |
closures | 即将到来的休息日(from、to、note 或 null),日期按 Europe/Warsaw。字段缺失表示没有休息日 |
cateringFormats | 带商品清单(items)和照片(imageUrl)的餐饮套餐;仅在餐饮模块开启时出现。这里只有 venue 为 delivery 或 both 且 priceBasis 为 from 的套餐 — 即 1.7 版店铺包能正确显示的套餐 |
cateringPackages | 所有启用的套餐及其全部套餐字段;仅在餐饮模块开启时出现。字段:slug、venue(onsite、delivery、both)、priceBasis(from、per_person、from_per_person)、minHeadcount/maxHeadcount、sections(带商品或自定义名称的行)、included(„W cenie”)、terms(„Warto wiedzieć”)和 addOns(key、name、amount、unit:szt、kg、g、l、ml 或 porcja、priceGrosze、product?)。1.8 版店铺包优先读取此字段,而不是 cateringFormats |
hours.days 恰好有七项,每天一项(weekday 0 = 周一 … 6 = 周日);上面缩短为两项。open 和 close 是门店当地时间的 HH:MM,仅当 closed: false 时有意义。模块、ordering 和 links 每次请求都重新读取。密钥所属公司已不存在 → 404 tenant_not_found。
POST /api/v1/shop-heartbeat
已部署的店铺报告它是用哪个 runtime 构建的。每次构建一次,绝不按请求发送。权限 catalog:read,没有专属权限。写入只涉及该密钥所属公司店铺记录上的集群字段。始终返回 204,即使公司没有店铺记录或请求体无法读取。dlt_test_… 密钥返回 403 test_key_readonly。delieta-shop-assert 会替你发送,你不必自己调用。
POST /api/v1/shop-heartbeat
Authorization: Bearer dlt_live_…
Content-Type: application/json
{ "runtime": "1.6.0", "contract": "v3", "commit": null, "modified": false }
POST /api/v1/checkout
店铺的购物车。权限 checkout:create(可发布密钥)。需要订单模块。Idempotency-Key 为必填(8–255 个字符)。客户端发送买什么,绝不发送多少钱:{ items: [{ itemId, quantity, options? }] },其中 quantity 为 1–99,最多 50 行,options 为 [{ group, option }](菜单里的组键和选项键)。价格、选项加价和合计由 Delieta 根据密钥所属公司的产品计算,单位为整数 PLN 格罗什。请求体里的 unitPrice / total / companyId 字段会被忽略。支付成功和取消后的地址取自该公司的店铺配置,绝不取自请求。
自己收集顾客信息的店铺可额外提供:fulfilment(delivery 或 pickup,默认 delivery)、requestedFor(来自 GET /api/v1/checkout/slots 的时间,带偏移的 ISO 日期;或 null = 尽快)、customer(name、phone、email?)、address(street、number、flat?、格式为 00-000 的 postalCode、city、note?)、invoice(带校验和的 nip、companyName?、address?)、note(最多 500 个字符)、locale 和 payment(blik、card、p24 或 cash)。规则:自取、现金和地址都需要 customer;带 customer 的配送需要 address;自取订单会丢弃地址。可选的 expectedTotalGrosze 是客户端在屏幕上看到的金额。服务器从不据此定价,只是把它与现在计算的金额比较:不一致时,返回 409 total_changed。只有 items 是旧格式:在线支付、配送,由 Stripe 自己询问电话和地址。
POST /api/v1/checkout
Authorization: Bearer dlt_live_…
Content-Type: application/json
Idempotency-Key: chk-2026-08-26-42
{ "items": [{ "itemId": "i1", "quantity": 2 }] }
201 响应(结账)
{
"url": "https://checkout.stripe.com/c/pay/cs_test_…",
"totalGrosze": 6400,
"currency": "PLN",
"pricingSource": "catalog"
}
在线支付返回 url:把顾客重定向到支付页面。顾客付款后订单才会创建;结果请从 GET /api/v1/checkout/result 读取。现金支付(payment: "cash")会立刻返回已下单但尚未付款的订单,等待厨房接单:
{
"orderShortId": "a1b2c3d4",
"trackingUrl": "https://delieta.pl/o/a1b2c3d4",
"totalGrosze": 6400,
"currency": "PLN",
"pricingSource": "catalog"
}
把 trackingUrl 交给顾客,并通过 GET /api/v1/orders/{id}/status 轮询状态。现金不需要在线支付账户,但需要 customer。相同的 Idempotency-Key 返回同一笔订单;重试一笔已下的现金订单时,会在检查任何条件之前直接返回该订单。
拒绝情形:订单模块关闭 → 403 module_disabled。商品未知或不可用 → 422 unprocessable(errors[].code:product_unknown、product_unavailable)。选项未知、缺少必选组或选择过多 → 422 option_invalid。购物车低于店铺最低金额 → 422 below_minimum。商品为“Tylko w karcie” → 422 product_menu_only。商品不在供应时间内 → 409 product_outside_hours。配送到范围之外的地址 → 409 out_of_range。找不到的地址 → 422 address_not_found。暂时无法报出配送费 → 503 delivery_quote_unavailable。门店未设置配送 → 409 delivery_unconfigured。金额变了 → 409 total_changed。已打烊且没有“尽快” → 409 shop_closed。时间不在列表内 → 409 slot_unavailable。店铺不提供的履约方式 → 409 fulfilment_unavailable。店铺不接受的支付方式 → 409 payment_unavailable。示例数据店铺在支付正式模式下发起在线支付 → 409 sample_mode(预览只能用测试模式支付)。没有店铺来源 → 409 shop_unconfigured。在线支付账户未就绪 → 409 connect_not_ready(不会有假支付)。
GET /api/v1/checkout/slots
店铺何时接单:“尽快”以及顾客可选的时间。权限 catalog:read。参数 fulfilment 为 delivery(默认)或 pickup。时间根据店铺的营业时间、休息日、备餐时间、步长和时间范围按 Europe/Warsaw 时区计算,以 ISO 时刻返回。结账稍后会检查同样的时间,所以不在列表里的时间会以 409 slot_unavailable 结束。从不缓存。没有设置营业时间时,店铺接受“尽快”,但没有时间网格。
GET /api/v1/checkout/slots?fulfilment=pickup
Authorization: Bearer dlt_live_…
{
"asap": { "available": true, "readyInMinutes": 30 },
"slots": [
{ "startsAt": "2026-08-26T08:30:00.000Z" },
{ "startsAt": "2026-08-26T08:45:00.000Z" }
]
}
错误的 fulfilment → 400 invalid_request。店铺不提供该履约方式 → 409 fulfilment_unavailable。订单模块关闭 → 403 module_disabled。
GET /api/v1/checkout/result
一笔在线支付的已核实结果,用于店铺的确认页面。权限 checkout:create。传入返回地址里的 session_id(cs_test_… 或 cs_live_…;支付会把它附加到成功地址上)。答案来自 Delieta 中的订单与支付,而不是浏览器:paid、processing(款项在路上,或订单仍在保存;几秒后再问)、unpaid(顾客离开或会话过期)或 unknown(这不是该店铺的结账)。statementDescriptor 是顾客在对账单上看到的文字;不是刷卡支付时为 null。method 为 blik、card、p24、cash、other 或 null。缺少或错误的 session_id → 400 invalid_request。从不缓存。
GET /api/v1/checkout/result?session_id=cs_live_…
Authorization: Bearer dlt_live_…
{
"status": "paid",
"orderShortId": "K7M2QX",
"trackingUrl": "https://bella.delieta.pl/o/K7M2QX",
"totalGrosze": 5400,
"merchantName": "Bella Pizza",
"statementDescriptor": "BELLA* 3F9A12CD",
"method": "blik",
"livemode": true
}
POST /api/v1/checkout/quote
不创建订单的购物车报价:菜品、包装、配送和合计。权限 checkout:create(可发布密钥)。请求体:{ items, fulfilment?, address? },与 POST /checkout 相同。店铺不自己计算费用,只显示报价返回的内容。
响应 200:
{
"quote": {
"currency": "PLN",
"itemsGrosze": 4500,
"packagingGrosze": 200,
"packaging": [{ "kind": "item", "label": "Opakowanie", "quantity": 2, "unitGrosze": 100, "totalGrosze": 200 }],
"delivery": { "status": "priced", "feeGrosze": 500, "free": false, "freeFromGrosze": 9000, "missingForFreeGrosze": 4500, "distanceMeters": 3200 },
"totalGrosze": 5200,
"minimum": { "minimumOrderGrosze": 3000, "missingGrosze": 0 }
}
}
delivery.status 为 none(自取)、needs_address(有公里档位而还没有地址)或 priced。priced 时会有 feeGrosze,当价格取决于距离时还有 distanceMeters。fromGrosze 和 toGrosze 字段描述按距离定价时费用的范围。packaging[].kind 字段为 item(按件收费)或 order(按订单收费)。freeFromGrosze 和 missingForFreeGrosze 用于“距免费配送还差……”的提示。需要地址时 totalGrosze 为 null。
低于最低金额时,报价不算错误:差额见 minimum.missingGrosze。错误只在 POST /checkout 中以 below_minimum 出现。报价的拒绝情形:product_menu_only、product_outside_hours、out_of_range、address_not_found、delivery_quote_unavailable、delivery_unconfigured。代码见错误表。定价规则:店铺。
POST /api/v1/quotes
在订单存在之前对行程的估算。权限 eta:quote。起点是公司的门店;你只需给出目的地。结果始终是一个范围(minMinutes / maxMinutes)。guaranteed 字段恒为 false — 这不是 SLA。expiresAt 之后请重新询问。
{ "destination": { "lat": 53.123456, "lng": 18.012345 } }
{
"quote": {
"currency": "PLN",
"minMinutes": 35,
"maxMinutes": 50,
"guaranteed": false,
"breakdown": {
"prepMinutes": 15,
"loadMinutes": 4,
"travelMinutes": 12,
"padMinutes": 8
},
"distanceMeters": 4200,
"computedAt": "2026-08-26T12:00:00.000Z",
"expiresAt": "2026-08-26T12:05:00.000Z"
}
}
请同时显示区间的两端。合作方界面上只显示一个数字会产生误导。没有门店坐标 → 409 venue_not_configured。目的地在公司配送半径之外 → 409 out_of_range。请求体有误 → 400 invalid_request。密钥所属公司不存在 → 404 tenant_not_found。
POST /api/v1/orders
为密钥所属公司创建订单。权限 orders:write。服务器会自己汇总订单行,并丢弃你传的合计。单价由调用方提供:pricingSource 为 "caller"。这是来自合作方渠道的订单,不是店铺购物车;按目录定价的购物车是 POST /api/v1/checkout。
Idempotency-Key
必填请求头,去除空白后 8–255 个字符。每次逻辑尝试一个键。网络错误、500 create_failed 或 503 idempotency_unavailable 之后,用相同的键和相同的请求体重试。成功的 201 响应会保存 24 小时:重复请求返回同样的响应和 Idempotent-Replayed: true。相同的键、不同的请求体 → 409 idempotency_key_conflict。幂等存储不可用时,你得到 503,而不是重复订单。
请求体
没有公司字段。带调用方价格的典型调用:
{
"customerName": "Jan Kowalski",
"customerPhone": "+48123456789",
"destinationAddress": "ul. Przykładowa 1, 85-000 Bydgoszcz",
"destinationPoint": { "lat": 53.123456, "lng": 18.012345 },
"items": [
{ "name": "Margherita 32cm", "qty": 1, "unitPrice": 3200 },
{ "name": "Cola 0,5l", "qty": 2, "unitPrice": 800 }
],
"paymentMethod": "cash",
"channel": "web",
"externalRef": "your-storefront-order-42"
}
| 字段 | 必填 | 说明 |
|---|---|---|
customerName | 是 | 1–200 个字符 |
customerPhone | 是 | 1–40 个字符 |
destinationAddress | 是 | 1–500 个字符 |
destinationPoint | 否 | { lat, lng } WGS84。不要用 destination 或 destinationCoordinates — 那会得到 400 invalid_request,errors[].code 为 unknown_field,因为没有坐标的订单拿不到 ETA |
items | 是 | 1–50 行;qty 1–99 |
items[].name / items[].unitPrice | 是 | 格罗什;服务器会重新计算 totalGrosze |
items[].menuItemId | 否 | 产品 id;定价时忽略 options |
details | 否 | 最多 4000 个字符 |
customerNote | 否 | 最多 2000 个字符 |
paymentMethod | 否 | cash | card | online(默认 cash) |
channel | 否 | phone | web | pos | integration(默认 web) |
scheduledFor | 否 | ISO 日期格式的时间 |
sourceVariant | 否 | 最多 64 个字符 |
externalRef | 否 | 最多 64 个字符;在你这边去重,冲突 → duplicate_external_ref |
POST /api/v1/orders
Authorization: Bearer dlt_live_…
Content-Type: application/json
Idempotency-Key: ord-2026-08-26-storefront-42
201 响应(订单)
{
"order": {
"id": "1",
"shortId": "a1b2c3d4",
"status": "accepted",
"createdAt": "2026-08-26T12:00:00.000Z",
"tenantSlug": "bella-pizza",
"externalRef": "your-storefront-order-42",
"currency": "PLN",
"totalGrosze": 4800,
"lines": [
{ "name": "Margherita 32cm", "qty": 1, "unitPrice": 3200 },
{ "name": "Cola 0,5l", "qty": 2, "unitPrice": 800 }
],
"pricingSource": "caller",
"trackingUrl": "https://delieta.pl/o/a1b2c3d4"
}
}
status 为 accepted 或 pending_acceptance,取决于公司的接单模式。把 trackingUrl 交给客户,它是唯一的公开跟踪地址。写入错误:404 tenant_not_found、409 duplicate_external_ref、500 create_failed。
GET /api/v1/orders/
读取密钥所属公司拥有的一笔订单。权限相同:orders:write — 权限集合是封闭的,并且已发放给在用密钥;新增 orders:read 会破坏现有集成。查询计入读取限额,而不是创建限额。
id 是 201 响应中的 order.id。他人或不存在的 id → 404 not_found,不会泄露是否存在。响应的字段比创建时少。合作方拿到的是订单进度,而不是内部字段:
{
"order": {
"id": "1",
"shortId": "a1b2c3d4",
"status": "pending_acceptance",
"externalRef": "your-storefront-order-42",
"createdAt": "2026-08-26T12:00:00.000Z",
"promisedAt": null,
"acceptedAt": null,
"rejectedAt": null,
"rejectionReason": null,
"acceptanceDeadlineAt": 1756200000000
}
}
acceptedAt、rejectedAt 和 acceptanceDeadlineAt 是 unix 毫秒或 null。你会看到的状态依次是:pending_acceptance、accepted、preparing、ready、on_the_way、delivered 和 rejected。配送跳过 ready,自取跳过 on_the_way。
GET /api/v1/orders//status
订单在哪里,供顾客的跟踪页面使用。权限 catalog:read,因此可发布密钥可以从店铺轮询。{id} 是订单短码(结账响应里的 orderShortId,不区分大小写),不是 order.id。响应不含任何客户数据:只有状态、履约方式和时间。不存在、拼错或别人的短码,回答都一样:404 not_found。
GET /api/v1/orders/a1b2c3d4/status
Authorization: Bearer dlt_live_…
{
"status": "preparing",
"fulfilment": "pickup",
"etaMinutes": 12,
"promisedAt": "2026-08-26T08:45:00.000Z"
}
status 是以下之一:pending_acceptance、accepted、preparing、ready(自取:“可取餐”)、on_the_way、delivered、rejected。etaMinutes 是距 promisedAt 的整分钟数,不会为负,仅在厨房持有订单时出现(accepted、preparing、on_the_way)。promisedAt 是员工承诺的时间,没有时则为顾客要求的时间。自取订单处于 ready 时会出现 readyAt。
餐桌预订
需要开启预订模块(套餐或授予,加上店主的开关)。模块关闭:403 module_disabled。
GET /api/v1/floor-plan
GET /api/v1/floor-plan
公司的区域和桌位及其几何信息(grid 是无单位的 { width, height } 布局,店铺把它缩放到自己的图上)。权限 catalog:read。?locale= 翻译区域名称。响应带有 x-delieta-cache-tag。
GET /api/v1/reservations/availability
GET /api/v1/reservations/availability?date=2030-06-14&partySize=2
空闲的开始时间,以及每个时间下能容纳该人数的空闲桌位:date、partySize、durationMinutes 以及含 startsAt、time 和 tableIds 的 slots[]。time 是 Europe/Warsaw 的当地时间,startsAt 是可预订的时刻。没有时间时,closedReason 字段说明原因:closed_day、closure 或 hours_not_configured。date 格式错误或 partySize 超出 1–100:422 validation_failed。权限 catalog:read。从不缓存。
POST /api/v1/reservations
POST /api/v1/reservations
Authorization: Bearer dlt_live_…
Idempotency-Key: 4b0b3f0e-6d21-4f0a-9c1c-0d6f0e0a7a11
Content-Type: application/json
{ "startsAt": "2030-06-14T16:00:00.000Z", "partySize": 2, "tableId": "…",
"guest": { "name": "Ala Kowalska", "phone": "600100200", "email": "ala@example.pl" },
"occasion": "birthday", "note": "przy oknie" }
字段:startsAt(带偏移的 ISO 日期)、partySize(1–100)、tableId?、guest(name 最多 80 个字符、phone、email?)、occasion?(none、birthday、anniversary、date、business)、note?(最多 500 个字符)、locale?(pl、en、de、uk — 发给顾客的确认使用该语言)。请求体超过 8 KiB → 422 validation_failed。
两种密钥。带 checkout:create 的可发布密钥创建 web 预订:开始时间必须是提供的时间,状态为 requested,自动确认时为 confirmed。带 reservations:write 的保密密钥从你自己的系统创建 api 预订:任意时间,桌位只要空闲即可,状态始终为 confirmed。Idempotency-Key(8–100 个字符)可选但会被遵守:相同的键返回第一次的预订。website 字段是机器人陷阱,请保持为空。
201 返回 id、reference、status、startsAt、durationMinutes、tableIds。status 为 requested、confirmed、seated、completed、cancelled 或 no_show。拒绝是带稳定 code 的 problem+json:
| Status | code |
|---|---|
409 | slot_unavailable, table_taken |
422 | validation_failed, party_too_large, out_of_range |
403 | module_disabled, scope_required |
GET /api/v1/events
公司的活动:工作坊、品鉴会、主题夜。权限 catalog:read。需要活动模块:关闭 → 403 module_disabled,无法读取 → 503 module_unavailable。可选 ?locale=。
响应 200:{ "events": [...], "locale": "pl", "generatedAt": "..." }。列表只包含有即将到来日期的已发布活动,按 sort 排序,然后按最近的日期。每场活动:
| 字段 | 含义 |
|---|---|
id、slug、name、description、audience、scheduleNote、imageUrl | 活动描述;description 是纯文本,以 - 开头的行是要点 |
priceMode、pricePerPersonGrosze | per_person、free 或 on_request;仅 per_person 时有价格 |
participants | 每次询价的 { min, max } |
scheduleMode、occurrences | dated 或 on_request;日期 { id, date, time, endTime, note } |
booking、phone | inquiry 或 phone;活动电话或公司电话 |
响应带有 x-delieta-cache-tag 响应头(标记 tenant:{id}:events,每 5 分钟刷新),时间过滤在缓存之后进行,所以已过去的日期无需等待就会消失。
POST /api/v1/inquiries
顾客咨询:联系、餐饮或活动。权限 checkout:create(可发布密钥),所以请从店铺服务端调用,不要从浏览器调用。限流按访客计(在 x-delieta-visitor 中传入其地址)。website 字段是机器人陷阱:请保持为空。所需模块取决于类型:catering 和 contact 需要餐饮模块,event 需要活动模块。所需模块关闭时,响应为 403 module_disabled。请求体超过 64 KiB → 413 payload_too_large。未知字段 → 400 invalid_request。
字段:kind(contact、catering 或 event)、name、phone 和/或 email(二者必有其一,餐饮需要 phone)、message(contact 必填)、topic?(reservation、catering、order、other)、locale?,以及对于 catering 的 catering 对象:formatId?、headcount、eventDate(YYYY-MM-DD)、eventTime?(HH:MM)、deliveryAddress?、dietary?(vegetarian、gluten_free、lactose_free)、dietaryNote?、company?、nip?、venue?(onsite 或 delivery)和 addOns?([{ key, quantity }],最多 30 项,quantity 为 1–99,每个 key 一次)。请求体从不携带加购项的价格或名称:它们来自套餐形式。加购项只能与 formatId 一起发送。
对于 event,字段是一个 event 对象:eventId、occurrenceId?(来自 GET /events 的日期)、preferredDate? 和 preferredTime?(用于“Do uzgodnienia”的活动;此时 preferredDate 必填)、participants 和 organization?。phone 必填,message 可选。请求体是严格的:未知字段返回 400。
POST /api/v1/inquiries
Authorization: Bearer dlt_live_…
Content-Type: application/json
{
"kind": "catering",
"name": "Anna Nowak",
"phone": "+48600100200",
"catering": { "formatId": "f1", "headcount": 40, "eventDate": "2026-12-12", "company": "Acme", "nip": "5260250995" }
}
成功:201 { "inquiry": { "reference": "AB12CD", "status": "new" } }。NIP 有误(校验和)、缺少电话或未知字段 → 400 invalid_request,并附 errors 列表。套餐未知 → 422 unprocessable。现场服务的询价若落在场所的休息日 → 422 venue_closed(„W tym dniu lokal jest zamknięty.”)。
活动报名(kind: "event")
报名参加场所的活动(Wydarzenia 模块)也是一条咨询:kind: "event" 加 event 块。电话为必填。有固定日期的活动使用 occurrenceId(来自 GET /api/v1/events 的某个日期);“termin do uzgodnienia”(日期待定)的活动使用 preferredDate(可选 preferredTime)。公司关闭 Wydarzenia 模块时返回 403 module_disabled。拒绝为 422 unprocessable,代码在 errors[].code,字段旁附波兰语说明:unknown_event、event_not_bookable、event_past、event_phone_only、unknown_occurrence、event_in_past、preferred_date_required、participants_out_of_range。
POST /api/v1/catering-orders
公司从自己的系统记录一笔餐饮订单。权限 catering:write(仅保密密钥)。Delieta 记录这笔销售:请求体带有以格罗什计的约定金额(agreedAmountGrosze);付款可选(paid: true 表示已付)。订单行(lines)通过 menuItemId 指向公司的产品;他人或未知的 id → 422 unprocessable。订单的 source 为 api。可选的 Idempotency-Key:重试返回第一笔订单(200、Idempotent-Replayed: true)。模块关闭 → 403 module_disabled。咨询的关联已被占用 → 409 conflict。请求体超过 64 KiB → 413 payload_too_large。
字段:customer(name、phone?、email?、company?、nip?)、eventDate(YYYY-MM-DD)、eventTime?、headcount?、deliveryAddress?、formatId?、lines[](menuItemId、quantity、unitPriceGrosze?)、agreedAmountGrosze、note?、status?(planned、confirmed、delivered、cancelled)、paid?、inquiryId?。
{
"customer": { "name": "Acme sp. z o.o.", "nip": "5260250995" },
"eventDate": "2026-12-12",
"headcount": 40,
"lines": [{ "menuItemId": "i1", "quantity": 40 }],
"agreedAmountGrosze": 240000
}
成功:201 { "order": { "id", "reference", "status": "planned", "source": "api", "paymentStatus": "unpaid", … } }。paymentStatus 为 unpaid 或 paid。
GET /api/v1/catering-orders
GET /api/v1/catering-orders?page=1&limit=50 返回列表 { orders, page, limit, total },最新的在前(limit 最大 100)。权限相同,为 catering:write。
公司的餐饮套餐位于店铺配置响应中,仅在模块开启时出现:cateringFormats(可配送、按“起价”计价的套餐)和 cateringPackages(所有套餐及其套餐字段)。每个套餐都有 imageUrl(照片地址或 null)。
OpenAPI
GET /api/v1/openapi
公开的 OpenAPI 3.1 文档,无需密钥。不想手写 curl 时,可以由它生成客户端。本页与它保持一致 — 若出现分歧,以 OpenAPI 和仓库中的测试为准。
Delieta 没有订单状态的 webhook 操作。状态请在此用 GET 读取。唯一反方向的通信(Delieta 到店铺)见 Webhook。

