{"openapi":"3.1.1","info":{"title":"Delieta Partner API","version":"1.0.0","description":"Headless ordering, menu and delivery-estimate API for Delieta tenants. A key resolves the company — no request ever names a tenant. Money is integer grosze; PLN is the only currency in v1."},"servers":[{"url":"https://delieta.pl/api/v1"}],"security":[{"apiKey":[]}],"paths":{"/catalog-images/{id}/{filename}":{"get":{"operationId":"getCatalogImage","summary":"One dish or catering photo","description":"Public and keyless: a shop visitor's browser fetches it, so no secret may be required. A menu item's `imageUrl` and a catering format's `imageUrl` point here. Both the `id` and the exact `filename` must match, otherwise the answer is 404. The answer is a 302 to the storage host when it holds the file, otherwise the image bytes. A photo URL never changes content (a replaced photo gets a new URL), so every answer may be cached for a year.","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"filename","in":"path","required":true,"schema":{"type":"string"}},{"name":"size","in":"query","required":false,"description":"`card` asks for the 800 px wide rendition. Omitted = the original. A photo narrower than 800 px is served as is.","schema":{"type":"string","enum":["card"]}}],"responses":{"200":{"description":"The image bytes.","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"`public, max-age=31536000, immutable`."}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}},"image/avif":{"schema":{"type":"string","format":"binary"}}}},"302":{"description":"The storage host holds the file; follow `Location`.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"404":{"description":"No such photo, or the filename is not this photo's."},"429":{"description":"Rate limit exceeded for this client IP (`rate_limited`)."}}}},"/menu":{"get":{"operationId":"getMenu","summary":"The products of the API key's company","description":"Requires the `catalog:read` scope (`menu:read` is a stored alias for keys minted before that name). A publishable key may hold this scope plus `checkout:create`. Unavailable items are omitted. The shape is frozen. `?locale=` returns names and descriptions in that language; `locale` on the response says which was served.","parameters":[{"name":"locale","in":"query","required":false,"description":"Language for names and descriptions: one of the shop's `locales` (listed by `GET /shop-config`). Anything else — unknown, or a language the shop has not enabled — is served in Polish. Untranslated fields fall back to Polish field by field.","schema":{"enum":["pl","en","de","uk"]}}],"responses":{"200":{"description":"The tenant's menu.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Menu"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/checkout":{"post":{"operationId":"createCheckout","summary":"Price a cart from the company's products and take the order","description":"Requires the `checkout:create` scope (publishable). The body is `{ items: { itemId, quantity, options? }[] }` and, for a shop that collects the diner's details itself, `fulfilment` (`delivery` or `pickup`), `requestedFor` (an offered slot from `GET /checkout/slots`, or `null` for as soon as possible), `customer`, `address`, `invoice`, `note`, `locale` and `payment` (`blik`, `card`, `p24` or `cash`). A body with only `items` is the original shape: online payment, delivery, and Stripe collects the diner's phone and address. The server prices from the tenant's products and chosen options in grosze PLN and ignores any client `unitPrice` / `total`; options are named by group and option key, never by price. Online payment answers `{ url }`: redirect the diner to Stripe's hosted page, and the order appears when they pay. Cash answers `{ orderShortId, trackingUrl }` with the order already placed, unpaid, waiting for the kitchen to accept it. Success and cancel URLs come from the shop's stored config, never from the request URL. When the connected account cannot charge, the response is `connect_not_ready`. Packaging (per item and per order) and delivery (flat, free from a threshold, or by distance band) are added by the server exactly as `POST /checkout/quote` reported them; send the quoted `expectedTotalGrosze` and a total that moved since is refused with `total_changed` instead of charged.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutCreateRequest"}}}},"responses":{"201":{"description":"Online: the Stripe Checkout Session URL, redirect the diner. Cash: the placed order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"400":{"description":"`invalid_request` or `idempotency_key_required`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the company's ordering module is off (plan feature or owner switch); or `scope_required` / `test_key_readonly`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`shop_closed`, `shop_unconfigured`, `connect_not_ready`, `slot_unavailable` (the requested time is not an offered slot), `fulfilment_unavailable` (the shop does not offer that way to receive an order), `payment_unavailable` (the shop does not take that way to pay), `product_outside_hours` (a product's sale window does not cover the fulfilment time: `requestedFor`, or now + prep minutes for ASAP; every such line is in `errors[]` as `items.N.itemId`, so pick another time or drop the line), `out_of_range` (the address is past the delivery radius; field `address`), `delivery_unconfigured` (distance pricing but no venue location) or `total_changed` (the total moved since the quote; nothing was charged).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`below_minimum` — the dishes are under the shop's minimum (packaging and delivery not counted); `option_invalid` — an unknown option, a missing required group, or too many choices; `product_menu_only` — a product is „tylko w karcie” (`orderable: false` in the menu), listed but never sold online; every such line is in `errors[]` as `items.N.itemId`; `address_not_found` — the delivery address could not be located; `unprocessable` — unknown or unavailable product. See `errors[].code`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`idempotency_unavailable` — the idempotency store is down; retry. `module_unavailable` — the company's modules could not be read; retry. `delivery_quote_unavailable` — delivery cannot be priced right now; pickup still works.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/checkout/quote":{"post":{"operationId":"quoteCheckout","summary":"What a cart will cost: dishes, packaging, delivery and the total","description":"Requires the `checkout:create` scope (publishable). The body is `{ items, fulfilment?, address? }`, the same shapes checkout takes; money fields are ignored. The answer is computed by the calculator checkout itself uses, so the shop shows these numbers and never adds a fee. Delivery `status` is `none` (pickup), `needs_address` (the shop prices by distance and no address was sent: `fromGrosze`–`toGrosze`, `totalGrosze` null) or `priced`. Distance is the straight line from the venue, rounded up to 100 m. Below the minimum is not an error here: see `minimum.missingGrosze`. The sale rules (`product_menu_only`, `product_outside_hours`) are not checked here: checkout checks them at the fulfilment time. Never cached.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutQuoteRequest"}}}},"responses":{"200":{"description":"The quote.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutQuote"}}}},"400":{"description":"`invalid_request` — the body failed validation.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the company's ordering module is off; or `scope_required`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`fulfilment_unavailable`, `out_of_range` (field `address`) or `delivery_unconfigured`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`address_not_found`, `option_invalid` or `unprocessable` (unknown product).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`delivery_quote_unavailable` — delivery cannot be priced right now; pickup still quotes. `module_unavailable` — the company's modules could not be read; retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/checkout/slots":{"get":{"operationId":"getCheckoutSlots","summary":"When the shop can take an order: as soon as possible, and the times a diner may request","description":"Requires the `catalog:read` scope. Computed from the shop's opening hours, prep time, slot step and horizon, in Europe/Warsaw time and returned as ISO instants. Checkout enforces the same set a moment later, so a time that is not listed here is refused with `slot_unavailable`. Never cached. With no opening hours configured the shop takes orders as soon as possible but lists no slots.","parameters":[{"name":"fulfilment","in":"query","required":false,"schema":{"enum":["delivery","pickup"],"default":"delivery"}}],"responses":{"200":{"description":"The slot set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSlots"}}}},"400":{"description":"`invalid_request` — `fulfilment` is not `delivery` or `pickup`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the company's ordering module is off; or `scope_required`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`fulfilment_unavailable` — the shop does not offer that way to receive an order.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/checkout/result":{"get":{"operationId":"getCheckoutResult","summary":"The verified outcome of one online checkout, for the shop's confirmation page","description":"Requires the `checkout:create` scope. Send the `session_id` the hosted payment page appended to the success URL. The answer comes from Delieta's own order and payment, never from the browser: `paid`, `processing` (the money is on its way or the order is still being written; ask again in a few seconds), `unpaid` (the diner left or the session expired) or `unknown` (not a checkout of this shop). `statementDescriptor` is the line the bank statement will show. Never cached.","parameters":[{"name":"session_id","in":"query","required":true,"schema":{"type":"string","pattern":"^cs_(test|live)_[A-Za-z0-9]+$"}}],"responses":{"200":{"description":"The outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResult"}}}},"400":{"description":"`invalid_request` — `session_id` is missing or malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/quotes":{"post":{"operationId":"createQuote","summary":"Delivery time estimate, before an order exists","description":"Requires the `eta:quote` scope. Composed as prep + load + travel + pad from the company's own venue. Always a RANGE and never a guarantee (`guaranteed` is always false).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}}}},"responses":{"200":{"description":"An estimate range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"400":{"description":"`invalid_request` — body is not JSON or schema validation failed; see field errors.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`tenant_not_found` — the key's company no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`venue_not_configured` — the company has no venue coordinates.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/orders":{"post":{"operationId":"createOrder","summary":"Create an order for the API key's company","description":"Requires the `orders:write` scope and an `Idempotency-Key`. The server sums and validates the total and rejects a caller-supplied total, but unit prices are taken from the caller in slice 1 (`pricingSource: \"caller\"`); catalog-authoritative pricing arrives with the menu in slice 2. The catalog sale rules („tylko w karcie”, godziny dostępności) apply to a diner order: `channel: \"web\"` (the default). A web line whose `menuItemId` is menu-only is refused with 422 `product_menu_only`; one outside its window (checked at `scheduledFor`, or now plus the company's prep minutes) with 409 `product_outside_hours`; each such line is named in `errors[]` as `items.N.menuItemId`. The venue's own channels (`pos`, `phone`, `integration`) record a sale it made itself and bypass both rules. The response envelope is frozen for 24 hours against the idempotency key.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":255},"description":"Unique per logical order. Retrying with the same key returns the original envelope and `Idempotent-Replayed: true` instead of creating a duplicate."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCreateRequest"}}}},"responses":{"201":{"description":"The created (or replayed) order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"400":{"description":"`invalid_request`, `idempotency_key_required`, `line_price_missing`, `line_name_missing` or `total_out_of_range`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`tenant_not_found` — the key's company no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`duplicate_external_ref` — that reference already created an order.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`idempotency_unavailable` — the idempotency store is down; retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/orders/{id}/status":{"get":{"operationId":"getOrderStatus","summary":"Where an order stands, for the diner's tracking page","description":"Requires the `catalog:read` scope, so a publishable key can poll it from the shop. `{id}` is the order's short code, the `orderShortId` checkout returned, not the order id. The answer carries no customer data: only the status, how the order is received, and timing. `etaMinutes` is whole minutes to `promisedAt` and appears only while the kitchen has the order (`accepted`, `preparing` or `on_the_way`). `promisedAt` is the time staff promised, else the time the diner asked for. `scheduledFor` is the time the diner asked for, present only when they chose one (an as-soon-as-possible order has none). `readyAt` appears once a pickup order is `ready`. A code that does not exist, is malformed, or belongs to another company answers identically.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The order's short code (`orderShortId`)."}],"responses":{"200":{"description":"The order's public status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderStatus"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`not_found` — no such order for this API key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/orders/{id}":{"get":{"operationId":"readOrder","summary":"Read one order the API key's company owns","description":"Requires the `orders:write` scope — the scope set is closed and already minted onto live keys, and reading is strictly less than writing. Counts against the READ budget, not the order-creation budget, so polling for an acceptance decision never costs you the ability to place the next order. An order created while the company requires acceptance starts in `pending_acceptance` and leaves it only when a person accepts or rejects it, or when the acceptance timeout sweeps it — none of which the caller can observe any other way.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The `order.id` returned when the order was created."}],"responses":{"200":{"description":"The order's current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderReadResponse"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`not_found` — no such order for this API key. An order belonging to another company answers identically, so this is never an existence oracle.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/floor-plan":{"get":{"operationId":"getFloorPlan","summary":"The venue's zones and tables with geometry","description":"Requires `catalog:read` (publishable) and the reservations module. Geometry is unitless coordinates inside `grid`; a shop scales it to draw a plan. `?locale=` returns zone names in that language (Polish where untranslated).","parameters":[{"name":"locale","in":"query","required":false,"description":"Language for names and descriptions: one of the shop's `locales` (listed by `GET /shop-config`). Anything else — unknown, or a language the shop has not enabled — is served in Polish. Untranslated fields fall back to Polish field by field.","schema":{"enum":["pl","en","de","uk"]}}],"responses":{"200":{"description":"The floor plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FloorPlan"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — reservations are not enabled — or `scope_required`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/reservations/availability":{"get":{"operationId":"getReservationAvailability","summary":"Free start times, and per time the free tables, for a date and party size","description":"Requires `catalog:read` (publishable) and the reservations module. `time` is Europe/Warsaw wall clock; `startsAt` is the instant to book. A closed day answers 200 with no slots and a `closedReason`.","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"partySize","in":"query","required":true,"schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Offered slots.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReservationAvailability"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` or `scope_required`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`validation_failed` (bad date or party size), `party_too_large`, `out_of_range`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/reservations":{"post":{"operationId":"createReservation","summary":"Book a table for the API key's company","description":"Two key kinds. A PUBLISHABLE key with `checkout:create` makes a `web` booking: the start must be an offered slot; the status is `requested`, or `confirmed` when the venue auto-confirms. A SECRET key with `reservations:write` makes an `api` booking from your own system: any hour, the table must simply be free; always `confirmed`. A publishable key can never hold `reservations:write`. `Idempotency-Key` is optional but honoured: the same key answers with the first booking and never creates a second. The body field `website` is a honeypot and must be left empty.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":8,"maxLength":100}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReservationCreateRequest"}}}},"responses":{"201":{"description":"The booking (or, for a replayed key, the first booking).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReservationResponse"}}}},"400":{"description":"`invalid_request` — body is not JSON, or a bad Idempotency-Key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled`, or `scope_required` (the scope that matches the key kind).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`slot_unavailable` or `table_taken` — including a lost race for the same table.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`validation_failed`, `party_too_large` or `out_of_range`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/inquiries":{"post":{"operationId":"createInquiry","summary":"A visitor's contact, catering or event inquiry for the API key's company","description":"Requires the `checkout:create` scope (publishable); call it from the shop's server, never the browser. `kind` is `contact` or `catering`. A catering inquiry carries the event date, headcount and optionally a format id from `GET /shop-config`. Limited per visitor (forward the shopper's address in `x-delieta-visitor`). `website` is a honeypot: leave it empty. Refused with `module_disabled` while the company has the catering module off. The body is capped at 64 KiB (`payload_too_large`). `kind: event` is a sign-up for a Wydarzenie from `GET /events`: it carries an `event` block and a phone, and is gated on the `events` module instead of catering. It is refused 422 `unprocessable` with one of these `errors[].code`: `unknown_event`, `event_not_bookable` (a draft), `event_past`, `event_phone_only` (the detail names the number to call), `unknown_occurrence`, `event_in_past`, `participants_out_of_range`, `preferred_date_required` (an event without dates needs `preferredDate`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InquiryRequest"}}}},"responses":{"201":{"description":"Stored. Quote `reference` to the visitor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InquiryResponse"}}}},"400":{"description":"`invalid_request` — see field errors.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the module behind this `kind` is off for this company (catering for `contact` and `catering`, events for `event`); or a missing scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"`payload_too_large` — the body is over 64 KiB.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`unprocessable` — for example an unknown catering format. The field error's `code` names a package refusal: `headcount_out_of_range` (outside the format's guest range), `venue_not_offered` (`catering.venue` the format does not serve), `unknown_add_on` (`catering.addOns.N.key` not on the format), `venue_closed` (an on-site party on a closed day), `event_in_past`; or an event sign-up the event refuses (codes in `errors[].code`, listed above).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/catering-orders":{"post":{"operationId":"createCateringOrder","summary":"Record a catering zlecenie for the API key's company","description":"Requires the secret-only `catering:write` scope; a publishable key can never hold it. Delieta records the sale: the body carries the agreed amount in grosze, payment is optional (`paid: true` marks it). `lines` name this company's menu items by id; a foreign or unknown id is `unprocessable`. The zlecenie is stamped `source: api`. An optional `Idempotency-Key` makes a retry return the first zlecenie (`Idempotent-Replayed: true`). Refused with `module_disabled` while the catering module is off.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":8,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CateringOrderRequest"}}}},"responses":{"200":{"description":"A replay of an earlier request with the same `Idempotency-Key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CateringOrderResponse"}}}},"201":{"description":"The created zlecenie.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CateringOrderResponse"}}}},"400":{"description":"`invalid_request` — see field errors.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the catering module is off; or a missing scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`conflict` — for example the inquiry is already converted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"`payload_too_large` — the body is over 64 KiB.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`unprocessable` — unknown menu item, format or inquiry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"operationId":"listCateringOrders","summary":"List this company's catering zlecenia, newest first","description":"Requires `catering:write`. Query: `page` (default 1), `limit` (1-100, default 50).","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"A page of zlecenia.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CateringOrderList"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the catering module is off; or a missing scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/shop-config":{"get":{"operationId":"getShopConfig","summary":"The brand identity of the API key's company, as its shop renders it","description":"Requires the `catalog:read` scope — the same one `GET /menu` needs, so a publishable key already carries it. The shop's own brand settings win field by field; the company's name, logo and colour are the fallback. `hours` is the company's weekly opening hours, Monday first, or `null` when the venue has not configured them (or the stored document is unreadable) — a client must not invent a week in that case. Changing any of these revalidates the shop through its signed webhook. `locales` lists the languages the shop can be read in, Polish first; `?locale=` resolves the catering format names and descriptions.","parameters":[{"name":"locale","in":"query","required":false,"description":"Language for names and descriptions: one of the shop's `locales` (listed by `GET /shop-config`). Anything else — unknown, or a language the shop has not enabled — is served in Polish. Untranslated fields fall back to Polish field by field.","schema":{"enum":["pl","en","de","uk"]}}],"responses":{"200":{"description":"The tenant's shop config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopConfig"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is missing the required scope (`scope_required`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`tenant_not_found` — the key's company no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/events":{"get":{"operationId":"getEvents","summary":"The Wydarzenia (workshops, tastings, quiz nights) the API key's company runs","description":"Requires the `catalog:read` scope, like `GET /menu`. Published events that have not happened yet, each with only its upcoming `occurrences` — or none and `scheduleMode: on_request` (\"termin do uzgodnienia\"). Money is integer grosze. `booking: phone` means sign-ups go by phone (`phone`); `POST /inquiries` refuses them. `?locale=` resolves the text. Refused with `module_disabled` while the events module is off. Tagged `x-delieta-cache-tag: tenant:{companyId}:events`.","parameters":[{"name":"locale","in":"query","required":false,"description":"Language for names and descriptions: one of the shop's `locales` (listed by `GET /shop-config`). Anything else — unknown, or a language the shop has not enabled — is served in Polish. Untranslated fields fall back to Polish field by field.","schema":{"enum":["pl","en","de","uk"]}}],"responses":{"200":{"description":"The tenant's upcoming events.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Events"}}}},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`module_disabled` — the events module is off; or `scope_required`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`tenant_not_found` — the key's company no longer exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`module_unavailable` — the company's modules could not be read right now (the module is not switched off); retry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/shop-heartbeat":{"post":{"operationId":"postShopHeartbeat","summary":"A deployed shop reporting which runtime it was built with","description":"Requires the `catalog:read` scope, not a scope of its own: a new scope would mean re-minting every publishable key in the fleet to ship telemetry. Sent once per build, never per request. The write is fenced to fleet columns on the key's own shop row. Always answers `204`, whether or not the company has a shop row and whether or not the body was readable — a malformed report is dropped, not guessed at.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopHeartbeatRequest"}}}},"responses":{"204":{"description":"Accepted (or ignored). There is no body."},"401":{"description":"No valid API key was presented (`unauthorized`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"`scope_required` — the key lacks the `catalog:read` scope — or `test_key_readonly` — a test key may read the live plane but never write to it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded for this key (`rate_limited`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"dlt_{live|test}_…","description":"Per-company API key. Scopes are checked per endpoint and an empty scope list is denied everywhere."}},"schemas":{"ReservationCreateRequest":{"type":"object","properties":{"startsAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"partySize":{"type":"integer","minimum":1,"maximum":100},"tableId":{"type":"string","minLength":1,"maxLength":64},"guest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"phone":{"type":"string","minLength":3,"maxLength":32},"email":{"type":"string","maxLength":254}},"required":["name","phone"]},"occasion":{"type":"string","enum":["none","birthday","anniversary","date","business"]},"note":{"type":"string","maxLength":500},"locale":{"type":"string","enum":["pl","en","de","uk"]},"website":{"type":"string","maxLength":200}},"required":["startsAt","partySize","guest"]},"FloorPlan":{"type":"object","properties":{"grid":{"type":"object","properties":{"width":{"type":"number"},"height":{"type":"number"}}},"zones":{"type":"array","items":{"type":"object"}},"tables":{"type":"array","items":{"type":"object"}}},"required":["grid","zones","tables"]},"ReservationAvailability":{"type":"object","properties":{"date":{"type":"string"},"partySize":{"type":"integer"},"durationMinutes":{"type":"integer"},"slots":{"type":"array","items":{"type":"object","properties":{"startsAt":{"type":"string","format":"date-time"},"time":{"type":"string"},"tableIds":{"type":"array","items":{"type":"string"}}},"required":["startsAt","time","tableIds"]}},"closedReason":{"enum":["closed_day","closure","hours_not_configured"]}},"required":["date","partySize","durationMinutes","slots"]},"ReservationResponse":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string","description":"Short code the guest quotes."},"status":{"enum":["requested","confirmed","seated","completed","cancelled","no_show"]},"startsAt":{"type":"string","format":"date-time"},"durationMinutes":{"type":"integer"},"tableIds":{"type":"array","items":{"type":"string"}}},"required":["id","reference","status","startsAt","durationMinutes","tableIds"]},"OrderCreateRequest":{"type":"object","properties":{"customerName":{"type":"string","minLength":1,"maxLength":200},"customerPhone":{"type":"string","minLength":1,"maxLength":40},"destinationAddress":{"type":"string","minLength":1,"maxLength":500},"destinationPoint":{"type":"object","properties":{"lat":{"type":"number","minimum":-90,"maximum":90},"lng":{"type":"number","minimum":-180,"maximum":180}},"required":["lat","lng"]},"details":{"type":"string","maxLength":4000},"customerNote":{"type":"string","maxLength":2000},"items":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"menuItemId":{"type":"string","minLength":1,"maxLength":64},"name":{"type":"string","minLength":1,"maxLength":200},"qty":{"type":"integer","minimum":1,"maximum":99},"options":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"unitPrice":{"type":"integer","minimum":0,"maximum":100000000}},"required":["qty"]}},"paymentMethod":{"default":"cash","type":"string","enum":["cash","card","online"]},"channel":{"default":"web","type":"string","enum":["phone","web","pos","integration"]},"scheduledFor":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"sourceVariant":{"type":"string","maxLength":64},"externalRef":{"type":"string","minLength":1,"maxLength":64}},"required":["customerName","customerPhone","destinationAddress","items"]},"QuoteRequest":{"type":"object","properties":{"destination":{"type":"object","properties":{"lat":{"type":"number","minimum":-90,"maximum":90},"lng":{"type":"number","minimum":-180,"maximum":180}},"required":["lat","lng"]}},"required":["destination"]},"CheckoutCreateRequest":{"type":"object","properties":{"items":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"itemId":{"type":"string","minLength":1,"maxLength":64},"quantity":{"type":"integer","minimum":1,"maximum":99},"options":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"group":{"type":"string","minLength":1,"maxLength":64},"option":{"type":"string","minLength":1,"maxLength":64}},"required":["group","option"]}}},"required":["itemId","quantity"]}},"fulfilment":{"type":"string","enum":["delivery","pickup"]},"requestedFor":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"customer":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"phone":{"type":"string","minLength":1,"maxLength":30},"email":{"type":"string","maxLength":254}},"required":["name","phone"]},"address":{"type":"object","properties":{"street":{"type":"string","minLength":1,"maxLength":120},"number":{"type":"string","minLength":1,"maxLength":20},"flat":{"type":"string","maxLength":20},"postalCode":{"type":"string","pattern":"^\\d{2}-\\d{3}$"},"city":{"type":"string","minLength":1,"maxLength":80},"note":{"type":"string","maxLength":300}},"required":["street","number","postalCode","city"]},"invoice":{"type":"object","properties":{"nip":{"type":"string","minLength":1,"maxLength":20},"companyName":{"type":"string","maxLength":200},"address":{"type":"string","maxLength":300}},"required":["nip"]},"note":{"type":"string","maxLength":500},"locale":{"type":"string","maxLength":16,"pattern":"^[A-Za-z]{2,3}([-_][A-Za-z0-9]{2,8})*$"},"payment":{"type":"string","enum":["blik","card","p24","cash"]},"expectedTotalGrosze":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["items"]},"CheckoutQuoteRequest":{"type":"object","properties":{"items":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"itemId":{"type":"string","minLength":1,"maxLength":64},"quantity":{"type":"integer","minimum":1,"maximum":99},"options":{"maxItems":20,"type":"array","items":{"type":"object","properties":{"group":{"type":"string","minLength":1,"maxLength":64},"option":{"type":"string","minLength":1,"maxLength":64}},"required":["group","option"]}}},"required":["itemId","quantity"]}},"fulfilment":{"type":"string","enum":["delivery","pickup"]},"address":{"type":"object","properties":{"street":{"type":"string","minLength":1,"maxLength":120},"number":{"type":"string","minLength":1,"maxLength":20},"flat":{"type":"string","maxLength":20},"postalCode":{"type":"string","pattern":"^\\d{2}-\\d{3}$"},"city":{"type":"string","minLength":1,"maxLength":80},"note":{"type":"string","maxLength":300}},"required":["street","number","postalCode","city"]}},"required":["items"]},"CheckoutQuote":{"type":"object","properties":{"quote":{"type":"object","properties":{"currency":{"const":"PLN"},"itemsGrosze":{"type":"integer","description":"Dishes, options included."},"packagingGrosze":{"type":"integer"},"packaging":{"type":"array","description":"One row per distinct per-item unit price, then the per-order fee.","items":{"type":"object","properties":{"kind":{"enum":["item","order"]},"label":{"type":"string"},"quantity":{"type":"integer"},"unitGrosze":{"type":"integer"},"totalGrosze":{"type":"integer"}},"required":["kind","label","quantity","unitGrosze","totalGrosze"]}},"delivery":{"type":"object","properties":{"status":{"enum":["none","needs_address","priced"]},"feeGrosze":{"type":["integer","null"]},"fromGrosze":{"type":"integer"},"toGrosze":{"type":"integer"},"free":{"type":"boolean"},"freeFromGrosze":{"type":"integer","description":"0 = no free-delivery threshold."},"missingForFreeGrosze":{"type":"integer"},"distanceMeters":{"type":"integer","description":"Straight line, rounded up to 100 m."}},"required":["status","feeGrosze","free","freeFromGrosze","missingForFreeGrosze"]},"totalGrosze":{"type":["integer","null"],"description":"null while delivery needs an address."},"minimum":{"type":"object","properties":{"minimumOrderGrosze":{"type":"integer"},"missingGrosze":{"type":"integer"}},"required":["minimumOrderGrosze","missingGrosze"]}},"required":["currency","itemsGrosze","packagingGrosze","packaging","delivery","totalGrosze","minimum"]}},"required":["quote"]},"CheckoutResponse":{"description":"Online payment answers `url`; cash answers `orderShortId` and `trackingUrl`.","oneOf":[{"type":"object","properties":{"url":{"type":"string","format":"uri"},"totalGrosze":{"type":"integer"},"currency":{"const":"PLN"},"pricingSource":{"const":"catalog"}},"required":["url","totalGrosze","currency","pricingSource"]},{"type":"object","properties":{"orderShortId":{"type":"string"},"trackingUrl":{"type":"string","format":"uri"},"totalGrosze":{"type":"integer"},"currency":{"const":"PLN"},"pricingSource":{"const":"catalog"}},"required":["orderShortId","trackingUrl","totalGrosze","currency","pricingSource"]}]},"CheckoutSlots":{"type":"object","properties":{"asap":{"type":"object","properties":{"available":{"type":"boolean","description":"The shop is open and taking orders right now."},"readyInMinutes":{"type":"integer","description":"The shop's prep time."}},"required":["available","readyInMinutes"]},"slots":{"type":"array","description":"Times a diner may request, ascending. Send one back as `requestedFor`.","items":{"type":"object","properties":{"startsAt":{"type":"string","format":"date-time"}},"required":["startsAt"]}}},"required":["asap","slots"]},"CheckoutResult":{"type":"object","properties":{"status":{"enum":["paid","processing","unpaid","unknown"]},"orderShortId":{"type":["string","null"]},"trackingUrl":{"type":["string","null"],"format":"uri"},"totalGrosze":{"type":["integer","null"],"description":"Order total in grosze."},"merchantName":{"type":["string","null"]},"statementDescriptor":{"type":["string","null"],"description":"The line the bank statement shows for a card payment. Null when the shop has none, or the payment is not by card (BLIK and P24 show the account line)."},"method":{"anyOf":[{"enum":["blik","card","p24","cash","other"]},{"type":"null"}]},"livemode":{"type":["boolean","null"]}},"required":["status","orderShortId","trackingUrl","totalGrosze","merchantName","statementDescriptor","method","livemode"]},"OrderStatus":{"type":"object","properties":{"status":{"enum":["pending_acceptance","accepted","preparing","ready","on_the_way","delivered","rejected"],"description":"Where the order is in its ladder."},"fulfilment":{"enum":["delivery","pickup"]},"etaMinutes":{"type":"integer","minimum":0},"promisedAt":{"type":"string","format":"date-time"},"readyAt":{"type":"string","format":"date-time"},"scheduledFor":{"type":"string","format":"date-time"}},"required":["status","fulfilment"]},"InquiryRequest":{"type":"object","additionalProperties":false,"properties":{"kind":{"enum":["contact","catering","event"]},"name":{"type":"string","minLength":1,"maxLength":120},"phone":{"type":"string","description":"Polish number; required for catering and event."},"email":{"type":"string","format":"email"},"message":{"type":"string","minLength":1,"description":"Required for `contact`; at least the company's `limits.contact.messageMin` characters after trimming (set in the panel, 10 by default; a shorter one is a 400 on `message`)."},"topic":{"enum":["reservation","catering","order","other"]},"locale":{"type":"string"},"website":{"type":"string","description":"Honeypot. Leave empty."},"catering":{"type":"object","additionalProperties":false,"description":"Required for `kind: catering`.","properties":{"formatId":{"type":"string","description":"An id from `cateringPackages` (or `cateringFormats`) in `GET /shop-config`."},"headcount":{"type":"integer","minimum":1,"maximum":500,"description":"Without a format: the company's `limits.catering` range (set in the panel; 5..500 by default). With one: the format's own range (its `minHeadcount` wins, its `maxHeadcount` caps); outside it a package is 422 `headcount_out_of_range`, a plain delivered format (delivery, `from`, no `maxHeadcount`) the pre-package 400 `invalid_request` on `catering.headcount`."},"venue":{"enum":["onsite","delivery"],"description":"Where it is served. Fixed by an `onsite` or `delivery` format (a different value is 422 `venue_not_offered`); the visitor's choice for a `both` format. Omitted: a format that delivers records `delivery`; an `onsite`-only format is 422 `venue_not_offered` (never re-tagged)."},"addOns":{"type":"array","maxItems":30,"description":"Paid add-ons of the chosen format, by key. Needs `formatId`. Never a price or a name: both come from the stored format.","items":{"type":"object","additionalProperties":false,"properties":{"key":{"type":"string"},"quantity":{"type":"integer","minimum":1,"maximum":99}},"required":["key","quantity"]}},"eventDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"eventTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"deliveryAddress":{"type":"string"},"dietary":{"type":"array","items":{"enum":["vegetarian","gluten_free","lactose_free"]}},"dietaryNote":{"type":"string"},"company":{"type":"string"},"nip":{"type":"string","description":"Ten digits; checksum validated."}},"required":["headcount","eventDate"]},"event":{"$ref":"#/components/schemas/EventInquiry"}},"required":["kind","name"]},"EventInquiry":{"type":"object","additionalProperties":false,"description":"Required for `kind: event`. A dated event takes one `occurrenceId` from its list; an event without dates (`scheduleMode: on_request`) takes `preferredDate` instead.","properties":{"eventId":{"type":"string","description":"An `id` from `GET /events`."},"occurrenceId":{"type":"string","description":"An `occurrences[].id` of that event."},"preferredDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"preferredTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"participants":{"type":"integer","minimum":1,"maximum":500},"organization":{"type":"string","maxLength":160,"description":"A school, class or group."}},"required":["eventId","participants"]},"InquiryResponse":{"type":"object","properties":{"inquiry":{"type":"object","properties":{"reference":{"type":"string"},"status":{"const":"new"}},"required":["reference","status"]}},"required":["inquiry"]},"CateringOrderRequest":{"type":"object","additionalProperties":false,"properties":{"customer":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"company":{"type":"string"},"nip":{"type":"string"}},"required":["name"]},"eventDate":{"type":"string","description":"YYYY-MM-DD."},"eventTime":{"type":"string","description":"HH:MM."},"headcount":{"type":"integer","minimum":1},"deliveryAddress":{"type":"string"},"formatId":{"type":"string"},"lines":{"type":"array","items":{"type":"object","properties":{"menuItemId":{"type":"string"},"quantity":{"type":"integer","minimum":1},"unitPriceGrosze":{"type":"integer","minimum":0}},"required":["menuItemId","quantity"]}},"agreedAmountGrosze":{"type":"integer","minimum":0,"description":"The agreed amount, grosze PLN."},"note":{"type":"string"},"status":{"enum":["planned","confirmed","delivered"]},"paid":{"type":"boolean"},"inquiryId":{"type":"string"}},"required":["customer","eventDate","agreedAmountGrosze"]},"CateringOrder":{"type":"object","properties":{"id":{"type":"string"},"reference":{"type":"string"},"status":{"enum":["planned","confirmed","delivered","cancelled"]},"source":{"enum":["shop","staff","api"]},"paymentStatus":{"enum":["unpaid","paid"]},"agreedAmountGrosze":{"type":"integer"},"eventDate":{"type":"string"},"eventTime":{"type":["string","null"]},"headcount":{"type":["integer","null"]},"deliveryAddress":{"type":["string","null"]},"formatId":{"type":["string","null"]},"customer":{"type":"object"},"lines":{"type":"array","items":{"type":"object"}},"note":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","reference","status","source","paymentStatus","agreedAmountGrosze","eventDate"]},"CateringOrderResponse":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/CateringOrder"}},"required":["order"]},"CateringOrderList":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/CateringOrder"}},"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"}},"required":["orders","page","limit","total"]},"Problem":{"type":"object","description":"RFC 9457 problem detail.","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer"},"code":{"type":"string","description":"Stable machine code. Branch on this, never on `title`."},"detail":{"type":"string"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"}},"required":["field","message"]}}},"required":["type","title","status","code"]},"OrderLine":{"type":"object","properties":{"name":{"type":"string"},"qty":{"type":"integer"},"unitPrice":{"type":"integer","description":"Grosze."}},"required":["name","qty","unitPrice"]},"OrderResponse":{"type":"object","properties":{"order":{"type":"object","properties":{"id":{"type":"string"},"shortId":{"type":"string"},"status":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"tenantSlug":{"type":"string"},"externalRef":{"type":["string","null"]},"currency":{"const":"PLN"},"totalGrosze":{"type":"integer"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/OrderLine"}},"pricingSource":{"enum":["catalog","caller"]},"trackingUrl":{"type":"string","format":"uri"}},"required":["id","shortId","status","currency","totalGrosze","trackingUrl"]}},"required":["order"]},"OrderReadResponse":{"type":"object","properties":{"order":{"type":"object","description":"Deliberately narrower than the create envelope: a read publishes only the lifecycle fields a partner needs, never the tenant-internal columns on the row.","properties":{"id":{"type":"string"},"shortId":{"type":"string"},"status":{"type":"string"},"externalRef":{"type":["string","null"]},"createdAt":{"type":["string","null"],"format":"date-time"},"promisedAt":{"type":["string","null"],"format":"date-time"},"acceptedAt":{"type":["integer","null"],"description":"Unix ms."},"rejectedAt":{"type":["integer","null"],"description":"Unix ms."},"rejectionReason":{"type":["string","null"]},"acceptanceDeadlineAt":{"type":["integer","null"],"description":"Unix ms auto-reject deadline; null when there is no gate."}},"required":["id","shortId","status"]}},"required":["order"]},"QuoteResponse":{"type":"object","properties":{"quote":{"type":"object","properties":{"currency":{"const":"PLN"},"minMinutes":{"type":"integer"},"maxMinutes":{"type":"integer"},"guaranteed":{"const":false},"breakdown":{"type":"object","properties":{"prepMinutes":{"type":"integer"},"loadMinutes":{"type":"integer"},"travelMinutes":{"type":"integer"},"padMinutes":{"type":"integer"}},"required":["prepMinutes","loadMinutes","travelMinutes","padMinutes"]},"distanceMeters":{"type":"integer"},"computedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"}},"required":["currency","minMinutes","maxMinutes","guaranteed","breakdown"]}},"required":["quote"]},"ShopEventOccurrence":{"type":"object","properties":{"id":{"type":"string","description":"Stable across edits; the booking key."},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"time":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"endTime":{"type":["string","null"]},"note":{"type":["string","null"]}},"required":["id","date","time","endTime","note"]},"ShopEvent":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"audience":{"type":["string","null"]},"scheduleNote":{"type":["string","null"]},"imageUrl":{"type":["string","null"]},"priceMode":{"enum":["per_person","free","on_request"]},"pricePerPersonGrosze":{"type":["integer","null"]},"participants":{"type":"object","properties":{"min":{"type":"integer"},"max":{"type":"integer"}},"required":["min","max"]},"scheduleMode":{"enum":["dated","on_request"]},"occurrences":{"type":"array","items":{"$ref":"#/components/schemas/ShopEventOccurrence"}},"booking":{"enum":["inquiry","phone"]},"phone":{"type":["string","null"]}},"required":["id","slug","name","description","audience","scheduleNote","imageUrl","priceMode","pricePerPersonGrosze","participants","scheduleMode","occurrences","booking","phone"]},"Events":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/ShopEvent"}},"locale":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"}},"required":["events","locale","generatedAt"]},"ShopConfig":{"type":"object","description":"Brand identity a shop renders. Deliberately the shape `shop.config.ts` used to have.","properties":{"name":{"type":"string"},"tagline":{"type":"string"},"brandColor":{"type":"string","description":"Lowercase `#rrggbb`."},"logo":{"type":["string","null"]},"banner":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"Square icon, at least 512×512, for the favicon and app icon (runtime 1.9 `shopIcons`). `null` when the company has none; the shop then keeps its own."},"currency":{"const":"PLN"},"locale":{"type":"string"},"locales":{"type":"array","description":"Languages the shop can be read in, Polish first. Ask for one with `?locale=`.","items":{"enum":["pl","en","de","uk"]}},"layout":{"enum":["grid","list"]},"links":{"type":"object","description":"Only the links the venue filled in; blank ones are omitted.","properties":{"instagram":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"facebook":{"type":"string","format":"uri"},"email":{"type":"string"},"mapUrl":{"type":"string","format":"uri","description":"The venue's map link, or a Google Maps directions link derived from its address."}}},"ordering":{"description":"How the shop takes orders, or `null` while the ordering module is off. Read fresh on every request, never from the brand cache.","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ShopOrdering"}]},"hours":{"description":"Weekly opening hours, or `null` when the venue has not configured them.","oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ShopHours"}]},"generatedAt":{"type":"string","format":"date-time"},"limits":{"type":"object","description":"The numbers a shop's forms must respect, read fresh on every request; each company sets its own in the panel (reservation settings; Katering → Ustawienia for catering and contact). The API enforces them: a party above `reservation.partyMax` or beyond `daysAhead` is refused (`party_too_large`, `out_of_range`), a catering `headcount` outside `catering.peopleMin`..`peopleMax` and a contact `message` shorter than `contact.messageMin` characters are a 400 on that field. `peopleStep` is a stepper increment for the form, not a rule. `timezone` is the clock for the shop's \"today\".","properties":{"reservation":{"type":"object","properties":{"partyMin":{"type":"integer"},"partyMax":{"type":"integer","description":"The company's `maxPartyOnline`."},"daysAhead":{"type":"integer","description":"The company's booking horizon in days."}},"required":["partyMin","partyMax","daysAhead"]},"catering":{"type":"object","properties":{"peopleMin":{"type":"integer"},"peopleMax":{"type":"integer"},"peopleStep":{"type":"integer"}},"required":["peopleMin","peopleMax","peopleStep"]},"contact":{"type":"object","properties":{"messageMin":{"type":"integer"}},"required":["messageMin"]},"timezone":{"type":"string","description":"IANA name, `Europe/Warsaw`."}},"required":["reservation","catering","contact","timezone"]},"modules":{"type":"array","description":"The shop modules enabled for this company ((plan or grant) and the owner's switch). A shop renders `/rezerwacje` or `/katering` only when its module is listed.","items":{"enum":["ordering","reservations","catering","events"]}},"cateringFormats":{"type":"array","description":"Present only while the catering module is on. The runtime 1.7 key: only formats with `venue` delivery or both AND `priceBasis` `from`, which a shop that knows neither field sells right. Read `cateringPackages` for everything.","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string","description":"Shop address (`/catering?format=<slug>`), unique per company."},"name":{"type":"string"},"description":{"type":["string","null"]},"fromPriceGrosze":{"type":["integer","null"],"description":"`null` = Zapytaj o cenę."},"priceBasis":{"enum":["from","per_person","from_per_person"],"description":"How the price reads: \"od 45 zł\" / \"150 zł/os.\" / \"od 150 zł/os.\"."},"venue":{"enum":["onsite","delivery","both"],"description":"W lokalu / Z dowozem / both."},"minHeadcount":{"type":["integer","null"]},"maxHeadcount":{"type":"integer","description":"Present only when set."},"sections":{"type":"array","description":"Courses of a package; lines point at them by `section`. Omitted when empty.","items":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"},"note":{"type":["string","null"]}}}},"included":{"type":"array","items":{"type":"string"},"description":"\"W cenie\" bullets. Omitted when empty."},"terms":{"type":"array","items":{"type":"string"},"description":"\"Warto wiedzieć\" bullets. Omitted when empty."},"addOns":{"type":"array","description":"Paid add-ons: price per `unitAmount` × `unit`. Omitted when empty.","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"unitAmount":{"type":"number"},"unit":{"enum":["szt","kg","g","l","ml","porcja"]},"priceGrosze":{"type":"integer"}}}},"imageUrl":{"type":["string","null"],"format":"uri","description":"Absolute URL of the format's photo (`card` size), or `null`."},"items":{"type":"array","description":"Lines. A dish written as text has `productId: \"\"` and its label as `name`.","items":{"type":"object"}}},"required":["id","slug","venue","priceBasis","name","description","fromPriceGrosze","minHeadcount","imageUrl","items"]}},"cateringPackages":{"type":"array","description":"Present only while the catering module is on: every active format, on-site packages and per-person prices included, with all package fields. Runtime 1.8 reads it before `cateringFormats`.","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string","description":"Shop address (`/catering?format=<slug>`), unique per company."},"name":{"type":"string"},"description":{"type":["string","null"]},"fromPriceGrosze":{"type":["integer","null"],"description":"`null` = Zapytaj o cenę."},"priceBasis":{"enum":["from","per_person","from_per_person"],"description":"How the price reads: \"od 45 zł\" / \"150 zł/os.\" / \"od 150 zł/os.\"."},"venue":{"enum":["onsite","delivery","both"],"description":"W lokalu / Z dowozem / both."},"minHeadcount":{"type":["integer","null"]},"maxHeadcount":{"type":"integer","description":"Present only when set."},"sections":{"type":"array","description":"Courses of a package; lines point at them by `section`. Omitted when empty.","items":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"},"note":{"type":["string","null"]}}}},"included":{"type":"array","items":{"type":"string"},"description":"\"W cenie\" bullets. Omitted when empty."},"terms":{"type":"array","items":{"type":"string"},"description":"\"Warto wiedzieć\" bullets. Omitted when empty."},"addOns":{"type":"array","description":"Paid add-ons: price per `unitAmount` × `unit`. Omitted when empty.","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"unitAmount":{"type":"number"},"unit":{"enum":["szt","kg","g","l","ml","porcja"]},"priceGrosze":{"type":"integer"}}}},"imageUrl":{"type":["string","null"],"format":"uri","description":"Absolute URL of the format's photo (`card` size), or `null`."},"items":{"type":"array","description":"Lines. A dish written as text has `productId: \"\"` and its label as `name`.","items":{"type":"object"}}},"required":["id","slug","venue","priceBasis","name","description","fromPriceGrosze","minHeadcount","imageUrl","items"]}}},"required":["modules","name","tagline","brandColor","logo","banner","icon","currency","locale","locales","layout","links","ordering","hours","generatedAt"]},"ShopOrdering":{"type":"object","properties":{"fulfilmentModes":{"type":"array","description":"Ways the shop hands an order over.","items":{"enum":["delivery","pickup"]}},"paymentModes":{"type":"array","description":"`online` is listed only while the shop can charge a card; `cash` is paid on handover. Empty means the shop cannot take an order right now.","items":{"enum":["online","cash"]}},"onlineMethods":{"type":"array","description":"How an online payment can be made: the Stripe Connect capabilities active on the shop's account, card first. Empty while `online` is not in `paymentModes`. May be empty while `online` is offered (Delieta has not yet recorded which methods are active): show a generic «Płatność online» and name no method. Name only these (no bank transfer without `p24`). Older servers omit it; read that as `card` and `blik`.","items":{"enum":["card","blik","p24"]}},"deliveryFeeGrosze":{"type":"integer","minimum":0},"minimumOrderGrosze":{"type":"integer","minimum":0,"description":"0 = no minimum."},"prepMinutes":{"type":"integer","minimum":0}},"required":["fulfilmentModes","paymentModes","deliveryFeeGrosze","minimumOrderGrosze","prepMinutes"]},"ShopHours":{"type":"object","properties":{"days":{"type":"array","description":"Exactly seven entries, one per weekday, Monday first.","minItems":7,"maxItems":7,"items":{"$ref":"#/components/schemas/ShopHoursDay"}}},"required":["days"]},"ShopHoursDay":{"type":"object","properties":{"weekday":{"type":"integer","minimum":0,"maximum":6,"description":"0 = Monday … 6 = Sunday."},"closed":{"type":"boolean"},"open":{"type":"string","description":"`HH:MM`, venue local time. Only meaningful when `closed` is false."},"close":{"type":"string","description":"`HH:MM`, venue local time, after `open`. Only meaningful when `closed` is false."}},"required":["weekday","closed","open","close"]},"ShopHeartbeatRequest":{"type":"object","properties":{"runtime":{"type":"string","maxLength":32,"description":"The shop-runtime version."},"contract":{"type":"string","maxLength":16,"description":"The shop contract version."},"commit":{"type":["string","null"],"maxLength":64},"modified":{"type":"boolean","description":"Whether the shop edited runtime-owned files."}},"required":["runtime"]},"Menu":{"type":"object","properties":{"currency":{"const":"PLN"},"generatedAt":{"type":"string","format":"date-time"},"locale":{"type":"string","description":"The locale actually served: the requested one when the shop enables it, else `pl`."},"categories":{"type":"array","description":"Categories with something to order online (or no items yet).","items":{"$ref":"#/components/schemas/MenuCategory"}},"items":{"type":"array","description":"Orderable items only. A menu-only item is never here, so a client that predates sale rules never offers it.","items":{"$ref":"#/components/schemas/MenuItem"}},"menuOnlyCategories":{"type":"array","description":"Categories whose every item is menu-only (\"tylko w karcie\"). Show them with prices, without an add button.","items":{"$ref":"#/components/schemas/MenuCategory"}},"menuOnlyItems":{"type":"array","description":"The menu-only items, every field, `orderable: false`. Checkout refuses them (422 `product_menu_only`).","items":{"$ref":"#/components/schemas/MenuItem"}}},"required":["currency","generatedAt","locale","categories","items","menuOnlyCategories","menuOnlyItems"]},"MenuCategory":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"sort":{"type":"integer"},"orderable":{"type":"boolean","description":"`false` = „tylko w karcie”: every item in the category is listed with its price and refused by checkout (`product_menu_only`)."},"availability":{"oneOf":[{"$ref":"#/components/schemas/MenuAvailability"},{"type":"null"}],"description":"The category's sale windows; `null` = whenever the shop takes orders. Items can only narrow it."}},"required":["id","slug","name","sort","orderable","availability"]},"MenuAvailability":{"type":"object","description":"„Godziny dostępności”, checked by checkout at the fulfilment time (`requestedFor`, or now + prep minutes for ASAP), Europe/Warsaw. A row whose season has ended is left out of the menu. The menu is cached, so it never says whether a row is available *now*: compute it from this rule.","properties":{"windows":{"type":"array","maxItems":7,"description":"Sale windows; empty = any time within the season.","items":{"type":"object","properties":{"days":{"type":"array","minItems":1,"items":{"type":"integer","minimum":0,"maximum":6},"description":"Weekdays, 0 = Monday."},"from":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, inclusive."},"to":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"HH:MM, inclusive; after `from`, same day."}},"required":["days","from","to"]}},"fromDate":{"type":["string","null"],"format":"date","description":"Season start, inclusive."},"toDate":{"type":["string","null"],"format":"date","description":"Season end, inclusive."}},"required":["windows","fromDate","toDate"]},"MenuModifierOption":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"priceDeltaGrosze":{"type":"integer"}},"required":["key","name","priceDeltaGrosze"]},"MenuModifierGroup":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"required":{"type":"boolean"},"min":{"type":"integer"},"max":{"type":"integer"},"options":{"type":"array","items":{"$ref":"#/components/schemas/MenuModifierOption"}}},"required":["key","name","required","min","max","options"]},"MenuItem":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"categoryId":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"basePriceGrosze":{"type":"integer","description":"Grosze."},"allergens":{"type":"array","items":{"type":"string"}},"modifierGroups":{"type":"array","items":{"$ref":"#/components/schemas/MenuModifierGroup"}},"imageUrl":{"type":["string","null"],"format":"uri","description":"Absolute URL of the dish photo (`card` size, 800 px wide), or `null` when the dish has none. Public: fetching it needs no key (`GET /catalog-images/{id}/{filename}`)."},"featured":{"type":"boolean","description":"The owner marked the dish „Polecane” (highlighted)."},"sort":{"type":"integer"},"orderable":{"type":"boolean","description":"Effective: `false` when the item or its category is „tylko w karcie”. Show it with its price, without an add-to-cart button; checkout refuses it with `product_menu_only`."},"availability":{"oneOf":[{"$ref":"#/components/schemas/MenuAvailability"},{"type":"null"}],"description":"The item's OWN sale windows. The category's rule applies as well (both must allow the time)."}},"required":["id","slug","categoryId","name","basePriceGrosze","allergens","imageUrl","featured","sort","orderable","availability"]}}}}