{"openapi":"3.0.1","info":{"title":"Cibusy Public API","description":"The Cibusy public API lets a restaurant's own website, app or software work with the venue's Cibusy account. It reads the venue and its menu, lists its tables, prices a basket, places an order that goes straight to the venue's kitchen, and follows that order until the venue closes it. It also takes the table bookings a site collects: it lists the times a venue can be booked at, sends a reservation in, and follows it until the guest has been and gone.\n\nIt is for a venue's own integrations. It does not pay for an order online (the customer pays at the venue), cancel an order, announce menu changes, tell a guest about their reservation (your site does), or reach any venue the key was not made for.\n\n## Base URL\n\nEvery path in this reference starts with `https://api.cibusy.com`, and every call is over HTTPS.\n\n```\nhttps://api.cibusy.com/public/v1\n```\n\nRequests and responses are JSON in UTF-8. Send `Content-Type: application/json` with a body.\n\n## Authentication\n\nEvery call carries a venue's API key in the `X-Api-Key` header.\n\n```bash\ncurl https://api.cibusy.com/public/v1/venues \\\n  -H \"X-Api-Key: cbk_your_key_here\"\n```\n\n**Getting a key.** The venue's owner makes keys in the Cibusy venue panel, signed in as the venue itself: staff accounts cannot. A key has a name, which also labels the orders it places so the venue can see which integration an order came from, and it may be given either or both of two permissions on top of the one every key has:\n\n| Permission | What it allows |\n|---|---|\n| Read menu (every key) | List the venues the key reaches, read a venue's details and menu, list its tables. |\n| Place orders (optional) | Price a basket, place an order, read orders back, and send a test webhook. |\n| Take reservations (optional) | Read a venue's booking calendar, send a reservation in, read reservations back, cancel one, and send a test webhook. |\n\nThe two are separate on purpose: a site that only takes bookings holds a key that cannot place an order, and the other way round.\n\nA key is shown once, when it is made. Cibusy keeps only a hash of it, so a lost key is revoked and replaced, never recovered. A venue can have 10 working keys at a time and can revoke any of them at once. A revoked key, and every key of a venue that has closed its account, stops working immediately. The Cibusy team can also revoke a key.\n\n**Keep the key on your server.** It is the venue's credential. Put it in your server's configuration, never in a web page, a mobile app or a repository. Calls from a browser are not allowed: the API does not accept cross-origin requests from other sites, and a key placed in a page can be copied by anyone who opens it. Have your page call your own server, and your server call Cibusy.\n\n| Answer | Meaning |\n|---|---|\n| `401` `PUBLIC_API_KEY_MISSING` | There is no `X-Api-Key` header. |\n| `401` `PUBLIC_API_KEY_INVALID` | The key is malformed, unknown or revoked, or its venue has closed. One answer for all of them, so it never tells you which. |\n| `403` `PUBLIC_API_SCOPE_MISSING` | The key is good but was not made with the permission this call needs: \"place orders\" for the order calls, \"take reservations\" for the reservation calls, either for the webhook test. |\n\nA key made for a venue whose subscription has lapsed still reads the venue, its menu, its tables and its booking calendar, as the QR menu does. Placing an order or taking a reservation is refused until the subscription is active again.\n\n## Venues and branches\n\nA key reaches the venue it was made for. A key made for a **headquarter** also reaches each of the headquarter's active branches. A key made for a **branch** reaches that branch only, never its headquarter or another branch.\n\n`GET /venues` lists the venues a key reaches, its own first, and every other call that is about one venue takes that venue's `id` in the path. A venue the key cannot reach is answered `404` `PUBLIC_API_VENUE_NOT_FOUND`, exactly as a venue that does not exist is, so a key can never find out which venues exist beyond its own.\n\n## Requests and responses\n\n- **Names and values.** Property names are camelCase and enum values are their names (`\"DineIn\"`, never `1`). Absent values are `null`: a property is never left out and never an empty string.\n- **Times.** Instants are ISO 8601 in UTC with a `Z`: `2026-10-01T09:30:00.000Z`. A clock time on the venue's own clock, such as an opening hour, is a `\"HH:mm\"` string, and the venue's `timeZone` says whose clock it is.\n- **Money.** Amounts are JSON numbers in Turkish lira (`TRY`), VAT included, to two decimals. The currency is always `TRY`.\n- **Ids** are UUIDs.\n\nEvery answer has the same envelope. A success carries the result in `data`:\n\n```json\n{\n  \"success\": true,\n  \"timestamp\": \"2026-10-01T09:30:00.123Z\",\n  \"traceId\": null,\n  \"data\": { },\n  \"message\": \"Operation completed successfully\"\n}\n```\n\nAn error carries a code to act on and two messages:\n\n```json\n{\n  \"success\": false,\n  \"timestamp\": \"2026-10-01T09:30:00.123Z\",\n  \"traceId\": null,\n  \"userMessage\": \"Table not found at this venue.\",\n  \"developerMessage\": \"The table is not one of this venue's tables.\",\n  \"errorCode\": \"PUBLIC_API_TABLE_NOT_FOUND\",\n  \"validationErrors\": [\n    { \"field\": \"tableId\", \"message\": \"Not one of this venue's tables.\", \"attemptedValue\": null }\n  ],\n  \"details\": null\n}\n```\n\nDecide what to do from the HTTP status and `errorCode`. `userMessage` is a sentence for the person using your site, so your site may show it as it is; `developerMessage` is for your logs, in English, and its wording may change. When an error is about one part of the request, `validationErrors` names it by its path in the request body (`lines[1].extraIds`). `traceId` identifies the request in Cibusy's logs when it is set: quote it when you write to support.\n\n**Language.** `userMessage` is in the language of the `Accept-Language` header: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`); one Cibusy does not offer is answered in Turkish. This does not choose the language of a menu, which is `lang` on the menu call.\n\n## Rate limits\n\nEach key has two budgets, counted in fixed one-minute windows:\n\n| Calls | Limit per key |\n|---|---|\n| Reading venues, menus, tables, orders, reservations and the booking calendar, and pricing a basket | about 120 a minute |\n| Placing an order, taking or cancelling a reservation, and sending a test webhook | about 30 a minute |\n\nThe limits are about that, not exact counts. Cibusy runs on several servers and each one counts the calls it receives for itself, so under load a key may be admitted somewhat more than 120 or 30 in a minute. Plan for the figures above rather than for what may get through, and treat a `429` as the signal to slow down.\n\nA call over the budget is not queued. It is answered `429` `RATE_LIMIT_EXCEEDED` with a `Retry-After: 60` header, and the next window opens within the minute. The two budgets are separate, so reading the menu cannot use up the orders a key may place. A retried order counts as a call. All the calls from one IP address also share a ceiling of about 500 a minute with everything else on that address.\n\nKeep to the budget by keeping a copy of the menu (see \"Reading a menu\" below) and by following orders and reservations with webhooks rather than polling them.\n\n## Error codes\n\n| Status | Code | Meaning |\n|---|---|---|\n| `400` | `VALIDATION_ERROR` | A parameter or the JSON body could not be read: a value that is not a UUID or not one of an enum's names, or malformed JSON. `validationErrors` names the fields. |\n| `400` | `INVALID_PAGE_NUMBER` | `cursor` is below 1. |\n| `400` | `INVALID_PAGE_SIZE` | `pageSize` is outside 1 to 100. |\n| `400` | `PUBLIC_API_IDEMPOTENCY_KEY_INVALID` | The `Idempotency-Key` header is missing, or is not 8 to 64 letters, digits, `-` or `_`. |\n| `400` | `PUBLIC_API_ORDER_INVALID` | The request breaks the shape rules for its order type, table, customer or lines. Every problem found is in `validationErrors`. |\n| `400` | `PUBLIC_API_PRODUCT_OPTION_INVALID` | An extra or removed ingredient is not the product's own, is repeated, or breaks the product's option groups. |\n| `400` | `PUBLIC_API_RESERVATION_INVALID` | The reservation request is missing its time, its number of guests or the guest's name or phone, or one of them cannot be read. Every problem found is in `validationErrors`. |\n| `400` | `TOO_MANY_GUESTS` | A reservation for more than 20 guests. A larger party calls the venue. |\n| `400` | `INVALID_RESERVATION_DATE` | The reservation's time is not in the future. |\n| `401` | `PUBLIC_API_KEY_MISSING` | No `X-Api-Key` header. |\n| `401` | `PUBLIC_API_KEY_INVALID` | The key is malformed, unknown, revoked, or its venue has closed. |\n| `403` | `PUBLIC_API_SCOPE_MISSING` | The key may not make this call: it needs the \"place orders\" permission for orders, or the \"take reservations\" permission for reservations. |\n| `404` | `PUBLIC_API_VENUE_NOT_FOUND` | The venue does not exist, or this key cannot reach it. |\n| `404` | `PUBLIC_API_ORDER_NOT_FOUND` | No order with this id was placed through the API at a venue this key reaches. |\n| `404` | `PUBLIC_API_TABLE_NOT_FOUND` | A dine-in order names a table that is not one of the venue's. |\n| `404` | `PUBLIC_API_PRODUCT_NOT_FOUND` | A product or portion in the order is not on the venue's menu. |\n| `404` | `PUBLIC_API_RESERVATION_NOT_FOUND` | No reservation with this id was taken through the API at a venue this key reaches. |\n| `409` | `PUBLIC_API_PRODUCT_UNAVAILABLE` | A product cannot be ordered: it is hidden from the menu, sold by weight, or out of stock. |\n| `409` | `PUBLIC_API_ORDERING_UNAVAILABLE` | The venue has no active subscription, so it takes no orders. |\n| `409` | `PLACE_IS_BUSY` | The venue's kitchen has declared a rush. `ordering.busyUntil` on the venue says until when. |\n| `409` | `OUTSIDE_WORKING_HOURS` | The venue is closed. |\n| `409` | `ORDER_CREATION_IN_PROGRESS` | The same `Idempotency-Key` is still being processed. Send the request again with the same key. |\n| `409` | `PUBLIC_API_WEBHOOK_NOT_CONFIGURED` | The key has no webhook address, when a test event or a new signing secret is asked for. |\n| `409` | `PUBLIC_API_RESERVATIONS_UNAVAILABLE` | The venue has no active subscription, so it takes no reservations. |\n| `409` | `RESERVATIONS_DISABLED` | The venue has switched online bookings off. |\n| `409` | `RESERVATION_TOO_SOON` | The reservation's time is less than 30 minutes away. |\n| `409` | `RESERVATION_TOO_FAR` | The reservation's date is more than 60 days ahead. |\n| `409` | `RESERVATION_TIME_UNAVAILABLE` | The venue does not seat at that time: it is outside its booking hours, or on a day it is closed. |\n| `409` | `RESERVATION_NOT_CANCELLABLE` | The reservation can no longer be cancelled: it is over, the guest has been seated, or its time has come. |\n| `429` | `RATE_LIMIT_EXCEEDED` | The key is over its budget. Wait `Retry-After` seconds. |\n| `500` | `INTERNAL_ERROR` | A fault on Cibusy's side. Try again after a pause; an order is retried with the same `Idempotency-Key`. |\n\nFour more codes are answered by the venue panel while an owner sets keys and webhooks up, never by this API, and are listed so every code a venue may quote is explained: `PUBLIC_API_KEY_NOT_FOUND` (404), `PUBLIC_API_KEY_LIMIT_REACHED` (409), `PUBLIC_API_KEY_NAME_INVALID` (400) and `PUBLIC_API_WEBHOOK_URL_INVALID` (400).\n\n## Reading a menu\n\n`GET /venues/{venueId}/menu` returns the venue's categories and the products in them, with each product's portions, prices, extras, option groups, removable ingredients, allergens and stock status. Only what the venue shows on its menu is there: a hidden product or category is never listed, and so can never be ordered. The menu does not depend on how the venue's QR menu is set up. Prices are in lira with VAT included.\n\n- **Language.** `lang` is the two-letter code of one of the venue's languages (listed in the venue's `languages`). A language the venue does not offer, or none, is answered in the venue's default language; `language` in the answer says which one the text is in.\n- **Campaigns.** A portion that a running discount covers carries `campaign` with the price to show. It is worked out on every call, so a happy hour starts and ends on time.\n- **Sold by weight.** A portion with `orderable: false` is priced per kilogram or similar and is weighed at the venue. It can be shown, not ordered.\n- **Caching.** Cibusy keeps a menu for up to five minutes and refreshes it at once when the venue edits it. The answer carries an `ETag`. Send it back in `If-None-Match` and an unchanged menu answers `304 Not Modified` with no body. The `ETag` stands for `data`, not for the envelope around it, whose `timestamp` changes on every call. The answer says `Cache-Control: private, no-cache`: keep a copy, but ask whether it is still current before you use it.\n\nDo not read the menu for every visitor of your website. Keep a copy on your server and refresh it with `If-None-Match`.\n\n## Placing an order\n\nAn order is placed with `POST /venues/{venueId}/orders`. It needs a key with the \"place orders\" permission and an `Idempotency-Key` header.\n\n**Types.**\n\n| `type` | What it needs | What happens |\n|---|---|---|\n| `DineIn` | `tableId` from `GET /venues/{venueId}/tables`. No `customer`, no `paymentMethod`. | The round is added to the table's open bill, or opens one. |\n| `Takeaway` | `customer.name` and `customer.phone`. No `tableId`. | The customer collects it at the venue. |\n| `Delivery` | `customer.name`, `customer.phone` and `customer.address`. No `tableId`. | The venue brings it to the customer. |\n\nAn order has 1 to 30 lines, each a product in one of its portions with a quantity of 1 to 20, at most 20 extras and 20 removed ingredients, and a note of at most 200 characters. The order's own note is at most 300 characters. A customer's name is at most 100 characters, a phone number 30 and an address 300.\n\n**The server prices everything.** The request carries no prices. Cibusy prices each line at the menu price with its extras, applies the venue's automatic campaigns and its service fee, and says what it came to in the answer's `totals`. `POST /venues/{venueId}/orders/preview` takes the same body and prices it without ordering it, so a site can show the total first; it checks the basket the way an order does, but not whether the venue is open. When a dine-in round joins a bill that is already open, a campaign that looks at the whole bill can price the round differently once it is on the bill, so the placed order's `totals` are the figures that count.\n\n**It goes straight to the kitchen.** There is no approval step. The kitchen tickets print as the order is placed, and the venue's staff are told as they are for an order from the QR menu.\n\n**Paid at the venue.** Nothing is charged through the API. `paymentMethod` (`Cash`, `Card` or `MealCard`) is only a hint for a takeaway or a delivery about how the customer means to pay, for whoever hands the order over. `paymentStatus` follows what the venue records at the till.\n\n**The venue must be taking orders.** It needs an active subscription, no kitchen rush and to be inside its opening hours. `ordering.acceptingOrdersNow` in `GET /venues/{venueId}` says whether an order would be accepted right now, so a site can show or hide its order button, but check again by placing the order: that can change between the two calls. Two things that apply to a diner using the QR menu do not apply here: the diner's location, which a website cannot give, and the venue's menu-only setting.\n\n**There is no sandbox.** An order placed with a real key is a real order at a real venue, and its kitchen prints a ticket. Develop against a venue you run, tell its staff, and use `preview` for everything that does not need to be an order. The API cannot cancel an order: a mistaken one is cancelled at the venue's till.\n\n### Retrying safely\n\nSend an `Idempotency-Key` with every order: a value you make up once per order, 8 to 64 letters, digits, `-` or `_`. A UUID works. If a request times out or the connection drops, send it again with the same key. You get the order the first call made, as the venue has it now, with status `200` where the first answer was `201`: nothing is ordered twice. Use a new key for a new order.\n\n- A retry is answered before the venue's hours and subscription are looked at again, since the order already exists.\n- A key is remembered for the API key that sent it and the venue it was sent to, and a key sent again with a different basket returns the original order, not a new one.\n- **Keep the `addedLineIds` of the first (`201`) answer.** In that answer they are exactly the lines the call wrote. A retry cannot say that exactly, because an order does not record which request wrote which line: on a `200`, `addedLineIds` is a best-effort reconstruction, the lines put on the order within 15 seconds before the first call was recorded. On a dine-in order that joined a table's bill, it can therefore include a line somebody else added to the same bill in that window. If you never received the `201`, treat the ids of a retry as likely rather than certain.\n- `409` `ORDER_CREATION_IN_PROGRESS` means a request with the same key is still being processed. Send yours again with the same key and you receive the order.\n\n### Following an order\n\n`GET /orders/{orderId}` reads an order as the venue has it now, and `GET /orders` lists the orders placed through the API, newest first, a page at a time (`cursor` is a page number from 1 and `pageSize` is 1 to 100, 50 by default; `nextCursor` is null on the last page). `status` is `open` (the default), `closed` or `all`. Any of the venue's keys can read an order placed with another of them, so a key can be replaced without losing sight of its orders. To follow an order without polling, use a webhook.\n\n**Order `status`.** It is worked out from the order's lines and its bill, taking the first rule that fits:\n\n| Status | When |\n|---|---|\n| `Cancelled` | The order was voided, or every line on it was struck off. |\n| `Completed` | The venue closed the bill. |\n| `OnTheWay` | A delivery has left the venue. |\n| `Served` | Every line on it is served. |\n| `Ready` | Every line is ready or already served. |\n| `Preparing` | The kitchen has started on at least one line. |\n| `Received` | The venue has the order and the kitchen has not started. |\n\n**`paymentStatus`.** `Unpaid` when nothing has been paid at the venue, `Paid` when the bill is settled, `PartiallyPaid` in between. `totals.remaining` is what the customer still owes.\n\n**Line `status`.** `Pending`, `Preparing`, `Ready`, `Served` or `Cancelled`. A line of a cancelled order reads `Cancelled` too.\n\nA dine-in order that joined a bill that was already open comes back as the whole bill, with the lines other people ordered on it. `addedLineIds` in the answer to placing it says which of `lines` that call wrote: exactly in the first answer, and as a best-effort reconstruction in a retry's (see \"Retrying safely\").\n\n## Taking a reservation\n\nA site that takes table bookings sends them in with `POST /venues/{venueId}/reservations`. It needs a key with the \"take reservations\" permission. The booking lands where every other one does: on the venue's reservation list at the till and in the staff app, with the notification a guest's own booking raises.\n\n```json\n{\n  \"startsAt\": \"2026-10-09T17:00:00.000Z\",\n  \"guests\": 4,\n  \"customer\": { \"name\": \"Ayşe Yılmaz\", \"phone\": \"+905321112233\" },\n  \"note\": \"A table by the window, if there is one.\"\n}\n```\n\n`startsAt` is an instant in UTC, `guests` is 1 to 20, the name is at most 100 characters, the phone number 30 and the note 250.\n\n**Offer the venue's own times.** `GET /venues/{venueId}/reservation-availability` returns the venue's booking calendar, a day at a time (`from` is the first day, `days` is 7 by default and at most 14). It is the calendar the venue's booking page on cibusy.com shows, worked out by the rules a booking is held to, so a slot listed there is a time the booking call accepts. Show a slot's `time`, which is on the venue's own clock, and send its `startsAt`. The calendar says when the venue seats, not how full it is: Cibusy does not count tables, and a venue declines a request it has no room for.\n\n**The venue's rules decide.** A booking is refused when the venue has switched bookings off (`RESERVATIONS_DISABLED`), for more than 20 guests (`TOO_MANY_GUESTS`), for a time that has passed (`INVALID_RESERVATION_DATE`), less than 30 minutes away (`RESERVATION_TOO_SOON`) or more than 60 days ahead (`RESERVATION_TOO_FAR`), and for a time the venue does not seat at (`RESERVATION_TIME_UNAVAILABLE`). The venue also needs an active subscription (`PUBLIC_API_RESERVATIONS_UNAVAILABLE`).\n\n**Confirmed, or waiting for the venue.** That is the venue's own setting, and `confirmsAutomatically` in the availability answer says which. At a venue that confirms bookings as they are made the answer is `Confirmed`. Otherwise it is `Pending` until somebody at the venue accepts or declines it; a request the venue has not answered by the time it was for is cancelled, with `cancelledBy` reading `System`.\n\n**Your site tells the guest.** Cibusy sends no text message about a reservation taken through the API: your site took the booking, and it is your site the guest expects to hear from. What you need for that is in every answer and on the webhook. Give the guest the `pageUrl` of the answer, or its `code`: the page on cibusy.com shows the booking and, once it is confirmed, the QR the venue scans at the door, and the code is what the guest reads out if they have nothing to show. Whoever holds either can see and cancel the booking, so give them to the guest only.\n\n**Sending it twice is safe, and needs no key.** A guest cannot sit at two tables at once, so a booking for the same phone number at the same venue for the same moment, while the first still stands, is the same booking. You get the one the first call made, as the venue has it now, with status `200` where the first answer was `201`, and the venue is not told a second time. If a request times out, send it again unchanged. A repeat is answered before the venue's rules are looked at again, since the booking already exists.\n\n**There is no sandbox here either.** A reservation taken with a real key is a real booking on a real venue's list. Develop against a venue you run, and cancel what you make.\n\n### Following a reservation\n\n`GET /reservations/{reservationId}` reads a reservation as the venue has it now, and `GET /reservations` lists the ones taken through the API, a page at a time, the way orders are listed (`cursor`, `pageSize`, `nextCursor`). `status` is `open` (the default), `closed` or `all`. The open ones come soonest first, so the next table to arrive is at the top; `closed` and `all` come latest first. A booking the venue took some other way, in the Cibusy app, on its page on cibusy.com or over the phone, is never listed. Any of the venue's keys can read a reservation taken with another of them.\n\n**Reservation `status`.**\n\n| Status | When |\n|---|---|\n| `Pending` | The venue has not answered yet. |\n| `Confirmed` | The venue accepted it, or confirms bookings as they are made. The table is expected. |\n| `Declined` | The venue said no. |\n| `Cancelled` | It was called off. `cancelledBy` says by whom: the `Customer`, the `Venue`, or the `System` when the venue never answered. |\n| `Seated` | The guest was checked in at the door. |\n| `Completed` | The visit is over. |\n| `NoShow` | The venue accepted it and nobody came. |\n\n**Cancelling.** `POST /reservations/{reservationId}/cancel` is for a guest who changes their mind on your site, with an optional `reason` for the venue to read. It is the guest's cancellation, and the venue is told. A reservation can be cancelled while it is `Pending` or `Confirmed` and its time has not come; `canCancel` on the reservation says so beforehand, and anything else is answered `RESERVATION_NOT_CANCELLABLE`. Cancelling one that is already cancelled answers `200` with it as it stands, so a request that was cut off can be sent again. The API does not change a reservation's time or its number of guests: cancel it and take a new one.\n\n## Webhooks\n\nA webhook tells your server when an order or a reservation changes, so it does not have to ask. An event usually arrives within seconds. When Cibusy is idle it can take up to about a minute, because a sweep that runs once a minute is what delivers it then, so do not build on it being instant.\n\n**Setting one up.** On the key, in the venue panel, the owner enters a webhook address: an `https://` URL on the public internet. Cibusy shows the signing secret (`whsec_…`) once, when the address is first saved. The owner can make a new secret at any time, and the old one stops working at once. Without an address on the key no event is sent.\n\n**What is followed.** The first `order.updated` for an order is sent when the order is placed, with the order as it started, and usually arrives within seconds of the order being placed (up to about a minute when Cibusy is idle). An order is followed for 48 hours after it was placed: a change after that is not announced, and `GET /orders/{orderId}` still tells. An address added to a key later starts receiving the updates of that key's open orders from the last 48 hours.\n\nA reservation is followed for much longer, from the day it is taken until 48 hours after the time it was for, or until it is declined, cancelled or over and that last state was delivered. Its first `reservation.updated`, with the state it was taken in, and every one after it are sent by the sweep that runs every minute, so they arrive within about a minute and never within seconds: a reservation changes because somebody decided something, not by the second. An address added to a key later starts receiving the updates of the reservations that key still has open.\n\n**The events.** There are three types.\n\n| `type` | When |\n|---|---|\n| `order.updated` | An order this key placed, or added a round to, changes in the form the API shows it: its status, payment status, table, total, amount paid, when it was closed, or the lines on it or any line's quantity or status. `data` is the same order `GET /orders/{orderId}` returns. |\n| `reservation.updated` | A reservation this key took changes in the form the API shows it: its status, its time, its number of guests, or who cancelled it. `data` is the same reservation `GET /reservations/{reservationId}` returns. |\n| `webhook.test` | You asked for it with `POST /webhooks/test`. `data` is `{ venueId, keyPrefix, message }`: whose test it is. No order has changed. |\n\nEach event is a JSON body in the usual style (camelCase, enum names, UTC times), written compactly in UTF-8 without a byte-order mark. It is shown indented here, to be read:\n\n```json\n{\n  \"id\": \"evt_0f8e2c1a7d444b0e9d2a5c3e7b1f9a60\",\n  \"type\": \"order.updated\",\n  \"createdAt\": \"2026-10-01T09:42:17.481Z\",\n  \"data\": { }\n}\n```\n\n`id` is `evt_` and 32 lowercase hexadecimal characters. `createdAt` is when Cibusy prepared that delivery. The OpenAPI document of this reference describes the three bodies as the schemas `OrderUpdatedWebhookEvent`, `ReservationUpdatedWebhookEvent` and `TestWebhookEvent`, so that types can be generated for them.\n\nAn `order.updated` event's `id` is the same every time the same change to the same order is delivered again, so it is what to de-duplicate on, and it is different for every new change of the order. A `reservation.updated` event's `id` works the same way for a reservation. A `webhook.test` event has a new `id` every time.\n\n**What Cibusy sends.** A `POST` to your address with these headers:\n\n| Header | Value |\n|---|---|\n| `Content-Type` | `application/json` |\n| `User-Agent` | `Cibusy-Webhooks/1.0` |\n| `X-Cibusy-Event-Id` | The event's `id`. |\n| `X-Cibusy-Event-Type` | The event's `type`: `order.updated`, `reservation.updated` or `webhook.test`. |\n| `X-Cibusy-Signature` | `t=<unix seconds>,v1=<signature>`: the time of this attempt, and the signature in lowercase hexadecimal. A retry has a fresh `t` and a fresh signature. |\n\n### Verifying the signature\n\nCheck every delivery before you act on it. The signature proves that the body came from Cibusy and was not changed.\n\n1. Read the **raw** body of the request: the exact bytes received, before any JSON parser has touched them, and verify them before you parse. Re-serialising parsed JSON does not give the same bytes.\n2. Split the `X-Cibusy-Signature` header on `,` and read `t` (a Unix timestamp in seconds) and `v1`.\n3. Reject the request if `t` is more than 5 minutes from your own clock. This stops an old delivery being replayed. A retry has a `t` of its own, so a delivery that is retried passes it.\n4. Compute the HMAC-SHA256 of the message `t` + `.` + the raw body, with the whole signing secret as the key, exactly as the panel showed it, including the `whsec_` prefix. Take the UTF-8 bytes of the secret and of the message, and write the result as lowercase hexadecimal.\n5. Compare it with `v1` in constant time.\n\nA test vector to check your code against: a `webhook.test` event as Cibusy writes it, signed with a secret made up for the purpose. Its `t` is 2026-10-01 09:30:00 UTC, so the check in step 3 rejects it unless your test sets the clock to that time or leaves that one check out.\n\n```\nsecret    whsec_PVJ2W1xHqfX6u1b0Jg9eT3nR5s8dKzYaLmQwEoCtUiA\nbody      {\"id\":\"evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34\",\"type\":\"webhook.test\",\"createdAt\":\"2026-10-01T09:30:00.000Z\",\"data\":{\"venueId\":\"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\"keyPrefix\":\"cbk_a1B2c3D4\",\"message\":\"This is a test event from Cibusy. No order has changed.\"}}\nheader    t=1790847000,v1=0d704c590d7d639a6658090e4fbb189e4e4e0e410319f007cd509ad2e67c36c9\n```\n\n**PHP**\n\n```php\n<?php\n$secret  = getenv('CIBUSY_WEBHOOK_SECRET');      // whsec_...\n$rawBody = file_get_contents('php://input');     // the exact bytes received\n$header  = $_SERVER['HTTP_X_CIBUSY_SIGNATURE'] ?? '';\n\n$parts = [];\nforeach (explode(',', $header) as $part) {\n    [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');\n    $parts[$key] = $value;\n}\n$timestamp = $parts['t'] ?? '';\n$signature = $parts['v1'] ?? '';\n\n$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);\n\nif (!ctype_digit($timestamp)\n    || abs(time() - (int) $timestamp) > 300\n    || !hash_equals($expected, $signature)) {\n    http_response_code(400);\n    exit;\n}\n\n$event = json_decode($rawBody, true);\n// De-duplicate on $event['id'], then handle the event.\nhttp_response_code(204);\n```\n\n**Node.js** (Express)\n\n```js\nconst crypto = require('node:crypto');\nconst express = require('express');\n\nconst SECRET = process.env.CIBUSY_WEBHOOK_SECRET;   // whsec_...\nconst TOLERANCE_SECONDS = 300;\n\nfunction verify(rawBody, header, secret) {\n  const parts = Object.fromEntries(header.split(',').map((part) => part.trim().split('=')));\n  const { t, v1 } = parts;\n\n  if (!/^\\d+$/.test(t ?? '') || !v1) return false;\n  if (Math.abs(Date.now() / 1000 - Number(t)) > TOLERANCE_SECONDS) return false;\n\n  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');\n  const a = Buffer.from(expected);\n  const b = Buffer.from(v1);\n\n  return a.length === b.length && crypto.timingSafeEqual(a, b);\n}\n\nconst app = express();\n\n// express.raw keeps the body as the bytes that were signed\napp.post('/cibusy/webhook', express.raw({ type: 'application/json' }), (req, res) => {\n  if (!verify(req.body, req.get('X-Cibusy-Signature') ?? '', SECRET)) return res.sendStatus(400);\n\n  const event = JSON.parse(req.body.toString('utf8'));\n  // De-duplicate on event.id, then handle the event.\n  res.sendStatus(204);\n});\n```\n\n**Python** (Flask)\n\n```python\nimport hashlib\nimport hmac\nimport os\nimport time\n\nfrom flask import Flask, abort, request\n\nSECRET = os.environ[\"CIBUSY_WEBHOOK_SECRET\"]  # whsec_...\nTOLERANCE_SECONDS = 300\n\n\ndef verify(raw_body: bytes, header: str, secret: str) -> bool:\n    try:\n        parts = dict(part.strip().split(\"=\", 1) for part in header.split(\",\"))\n        timestamp, signature = parts[\"t\"], parts[\"v1\"]\n        if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:\n            return False\n    except (KeyError, ValueError):\n        return False\n\n    expected = hmac.new(\n        secret.encode(\"utf-8\"), timestamp.encode(\"utf-8\") + b\".\" + raw_body, hashlib.sha256\n    ).hexdigest()\n\n    return hmac.compare_digest(expected, signature)\n\n\napp = Flask(__name__)\n\n\n@app.post(\"/cibusy/webhook\")\ndef webhook():\n    if not verify(request.get_data(), request.headers.get(\"X-Cibusy-Signature\", \"\"), SECRET):\n        abort(400)\n\n    event = request.get_json()\n    # De-duplicate on event[\"id\"], then handle the event.\n    return \"\", 204\n```\n\n**C#** (ASP.NET Core)\n\n```csharp\nusing System.Security.Cryptography;\nusing System.Text;\nusing System.Text.Json;\n\nvar secret = builder.Configuration[\"Cibusy:WebhookSecret\"]!;   // whsec_...\n\napp.MapPost(\"/cibusy/webhook\", async (HttpRequest request) =>\n{\n    using var buffer = new MemoryStream();\n    await request.Body.CopyToAsync(buffer);\n    var rawBody = buffer.ToArray();                            // the exact bytes received\n\n    if (!IsValid(rawBody, request.Headers[\"X-Cibusy-Signature\"].ToString(), secret))\n        return Results.BadRequest();\n\n    var cibusyEvent = JsonSerializer.Deserialize<JsonElement>(rawBody);\n    // De-duplicate on cibusyEvent.GetProperty(\"id\"), then handle the event.\n    return Results.NoContent();\n});\n\nstatic bool IsValid(byte[] rawBody, string header, string secret)\n{\n    string? timestamp = null, signature = null;\n\n    foreach (var part in header.Split(','))\n    {\n        var pair = part.Trim().Split('=', 2);\n        if (pair.Length != 2) continue;\n        if (pair[0] == \"t\") timestamp = pair[1];\n        else if (pair[0] == \"v1\") signature = pair[1];\n    }\n\n    if (timestamp is null || signature is null || !long.TryParse(timestamp, out var seconds))\n        return false;\n\n    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - seconds) > 300)\n        return false;\n\n    var message = Encoding.UTF8.GetBytes(timestamp + \".\").Concat(rawBody).ToArray();\n    var expected = Convert.ToHexStringLower(HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), message));\n\n    return CryptographicOperations.FixedTimeEquals(\n        Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(signature));\n}\n```\n\n### Delivery and retries\n\n- **Answer `2xx` quickly.** A `2xx` status means the event is delivered. Anything else counts as a failure: another status, a redirect (which is not followed), a connection that takes more than 5 seconds to open, an answer that takes more than 10 seconds in all, or a connection error. Do the work after you have answered.\n- **Retries.** After a failure Cibusy tries again, up to 9 attempts for the same change of the order, waiting 30 seconds, then 1, 2, 4, 8, 16, 32 and 64 minutes between them: about two hours in all. It then stops trying for that change and sends nothing more until the order changes again. A sweep that runs every minute picks up a change that no delivery has been made for, which is why a first delivery can take up to about a minute when Cibusy is idle. Every attempt is signed afresh, with a `t` of its own.\n- **De-duplicate.** A retry, and rarely a delivery that was already received, brings the same `id` again. Remember the `id`s you have handled and ignore a repeat.\n- **Read the state, not the history.** An event carries the order as it was when that delivery was prepared. Deliveries about one order can arrive out of order: compare `createdAt` with what you hold, or when you are unsure which is newer read `GET /orders/{orderId}`, which is always the order as it is now. The same goes for a reservation and `GET /reservations/{reservationId}`.\n- **Reservations follow the same rules.** A `reservation.updated` event is retried on the same schedule and de-duplicated the same way. What differs is only when it is first sent: by the sweep, within about a minute.\n- **The address.** It must be `https://`, with a public host name or address. Addresses that point at private, loopback, link-local or cloud-metadata destinations are refused, both when the address is saved and when an event is sent, and a redirect is never followed. Do not rely on the address a delivery comes from: Cibusy publishes none, and the signature is what proves a delivery is genuine.\n\n### Testing\n\n`POST /webhooks/test` sends a `webhook.test` event to the key's address, signed as a real one is, and answers with what your server did. It needs the \"place orders\" or the \"take reservations\" permission:\n\n```json\n{ \"delivered\": true, \"statusCode\": 204, \"error\": null, \"durationMs\": 182 }\n```\n\nIt answers `200` whether or not your server accepted the event: `delivered` says which. `statusCode` is the status your server answered with, and is null when it did not answer at all. `error` is null when the event was delivered and otherwise says in a short sentence what went wrong. A key with no webhook address is answered `409` `PUBLIC_API_WEBHOOK_NOT_CONFIGURED`. The call waits for your server's answer, up to 10 seconds, and a test event is not retried. It counts against the key's budget of about 30 a minute.\n\n## Trying it here\n\nThe **Try it** console of this reference sends your request from your browser to the production API, with the key you paste into it. Use a key made for testing, and remember the warning above: an order sent from here is a real order.\n\n## Versioning\n\nThis is version 1, and every path starts with `/public/v1`. Under `/v1` nothing is removed and nothing is renamed: a property, a value or a status you rely on stays as it is. A change that would break that ships as a new version, `/v2`, beside this one.\n\nWhat can change under `/v1` is that something is **added**. A response may gain properties, and an enum (a status, a type, a unit) may gain values. Ignore the properties you do not know, and handle a value you do not know in a way that does not break you.\n\n## Support\n\nFor help with an integration, write to [support@cibusy.com](mailto:support@cibusy.com) from the venue's own address, and say which venue it is. Quote the `traceId` of an error when it has one, and the `id` of the order or reservation you mean.\n","contact":{"name":"Cibusy support","email":"support@cibusy.com"},"version":"v1"},"servers":[{"url":"https://api.cibusy.com","description":"Production"}],"paths":{"/public/v1/orders":{"get":{"tags":["Orders"],"summary":"Lists the orders placed through the API, newest first","description":"Lists the orders the API placed at the venues the key reaches, by any of the venue's keys — an order placed with a\nkey that has since been replaced is still listed. Orders the venue hid from its own order list are left out; they\ncan still be read by id. Each item is the same body `GET /orders/{orderId}` returns.\n\nExample request:\n\n    GET /public/v1/orders?status=open&pageSize=20\n    X-Api-Key: cbk_…\n\nRead a page at a time: while `nextCursor` is not null, ask again with `cursor` set to it. The list is\nnewest first, so an order placed while you are paging moves every later page along by one: an order can then\nappear on two pages, never on none.","operationId":"listOrders","parameters":[{"name":"venueId","in":"query","description":"Only this venue's orders. A headquarter's key can name one of its branches; leave it out to list the orders of every venue the key reaches.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"Which orders: `open` (neither closed nor cancelled, the default), `closed` or `all`.","schema":{"enum":["Open","Closed","All"],"type":"string","description":"Which orders `GET /orders` lists.","default":"Open"}},{"name":"cursor","in":"query","description":"The page to read, counted from 1. Take it from `nextCursor` of the previous page.","schema":{"type":"integer","format":"int32","default":1}},{"name":"pageSize","in":"query","description":"Orders per page: 1 to 100, 50 by default.","schema":{"type":"integer","format":"int32","default":50}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"One page of orders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOrderListDtoApiResponse"}}}},"400":{"description":"`INVALID_PAGE_NUMBER`, `INVALID_PAGE_SIZE` or `VALIDATION_ERROR` for a `status` that is not one of the three","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"INVALID_PAGE_NUMBER":{"summary":"INVALID_PAGE_NUMBER","value":{"success":false,"userMessage":"Page number must be greater than 0.","developerMessage":"The cursor is a page number and starts at 1, received: 0","errorCode":"INVALID_PAGE_NUMBER","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"INVALID_PAGE_SIZE":{"summary":"INVALID_PAGE_SIZE","value":{"success":false,"userMessage":"Invalid page size. Please use a value between 1 and 100.","developerMessage":"The page size must be between 1 and 100, received: 500","errorCode":"INVALID_PAGE_SIZE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"place orders\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_VENUE_NOT_FOUND`: `venueId` is not a venue this key can reach","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 120 requests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/orders/{orderId}":{"get":{"tags":["Orders"],"summary":"Reads one order as the venue has it now","description":"Poll it to follow an order, or rely on the webhook, which sends this same body whenever it changes. An order is\nreadable by any key of the venue it was placed at, and by a headquarter's key at any of its branches; an order\nnobody placed through the API, one at another venue and one that does not exist all answer 404.\n\n**status** is worked out from the order's lines and its bill: `Cancelled` when the order was voided or every\nline on it was struck off; `Completed` when the venue closed the bill; `OnTheWay` when a delivery has left;\notherwise by the lines still on it — `Served` when every one is served, `Ready` when every one is ready\nor served, `Preparing` once the kitchen has started on any of them, `Received` before that.\n\n**paymentStatus** is `Unpaid` when nothing has been paid at the venue, `Paid` when the bill is settled\nand `PartiallyPaid` in between. **totals** are in Turkish lira, VAT included; the customer pays the\n`remaining` amount at the venue.\n\nA dine-in order that joined a bill that was already open comes back as the whole bill, with the lines other\npeople ordered on it.","operationId":"getOrder","parameters":[{"name":"orderId","in":"path","description":"The order's id, from the answer to placing it.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOrderDtoApiResponse"}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"place orders\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_ORDER_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_ORDER_NOT_FOUND":{"summary":"PUBLIC_API_ORDER_NOT_FOUND","value":{"success":false,"userMessage":"Order not found.","developerMessage":"No order with this id was placed through the API at a venue this key can reach.","errorCode":"PUBLIC_API_ORDER_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 120 requests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}/orders":{"post":{"tags":["Orders"],"summary":"Places an order at a venue","description":"Send the order and the `Idempotency-Key` header. The response is `201 Created` with a\n`Location` header pointing at `GET /orders/{orderId}`. If your request times out or the connection\ndrops, send it again with the same key: you get the order the first call made, as the venue has it now, with\nstatus `200 OK`, and nothing is ordered twice — a retried order is also answered before the venue's opening\nhours and subscription are looked at again, since the order already exists. Make up a new key for a new order. A\nkey is remembered per API key and venue, and a key reused with a different basket returns the original order, not\na new one.\n\n**Keep the `addedLineIds` of the `201` answer.** It says exactly which of `order.lines` this call\nwrote, which matters on a dine-in order that joined a table's bill with lines other people ordered on it. A retry\ncannot say it exactly, because an order does not record which request wrote which line: on a `200`,\n`addedLineIds` is rebuilt as the lines put on the order within 15 seconds before the first call was recorded,\nso it can include a line somebody else added to the same bill in that window. If you never received the\n`201`, treat the ids of a retry as likely rather than certain.\n\nThe server prices the order: the lines at menu price with their extras, the venue's automatic campaigns, and\nthe venue's service fee. Use `POST /venues/{venueId}/orders/preview` to show the total beforehand.\n\nExample request:\n\n    POST /public/v1/venues/3fa85f64-5717-4562-b3fc-2c963f66afa6/orders\n    X-Api-Key: cbk_…\n    Idempotency-Key: 0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60\n\n    {\n      \"type\": \"Delivery\",\n      \"customer\": { \"name\": \"Ayşe Yılmaz\", \"phone\": \"+905321112233\", \"address\": \"Bağdat Cad. No 12 Daire 4, Kadıköy\" },\n      \"paymentMethod\": \"Card\",\n      \"note\": \"Please ring the bell.\",\n      \"lines\": [\n        {\n          \"productId\": \"0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60\",\n          \"portionId\": \"5b1c7d90-2a3e-4f6b-8c9d-0e1f2a3b4c5d\",\n          \"quantity\": 2,\n          \"note\": \"Well done.\",\n          \"extraIds\": [ \"c3d4e5f6-0a1b-4c2d-9e8f-7a6b5c4d3e2f\" ],\n          \"removedIngredientIds\": [ \"d4e5f6a7-1b2c-4d3e-8f9a-6b5c4d3e2f1a\" ]\n        }\n      ]\n    }\n\n**Dine-in** needs `tableId` and no customer: the round is added to the table's open bill, or opens one.\n**Takeaway** needs `customer.name` and `customer.phone`; **Delivery** also needs\n`customer.address`. A takeaway or delivery order carries no `tableId`. 1 to 30 lines, 1 to 20 of\neach, at most 20 extras and 20 removed ingredients on a line, a note of at most 300 characters and line\nnotes of at most 200.\n\nThe venue must be taking orders right now: it needs an active subscription (`PUBLIC_API_ORDERING_UNAVAILABLE`),\nno kitchen rush (`PLACE_IS_BUSY`) and to be inside its working hours (`OUTSIDE_WORKING_HOURS`). Every\nproduct must be one the menu lists and orderable: hidden products and portions sold by weight are refused\n(`PUBLIC_API_PRODUCT_UNAVAILABLE`), as are extras and removed ingredients that do not belong to the product\nor break its option groups (`PUBLIC_API_PRODUCT_OPTION_INVALID`). A product that is out of stock is\nrefused as unavailable too. Where an error names a part of the request, `validationErrors` says which, by path\n(`lines[1].extraIds`).\n\n`ORDER_CREATION_IN_PROGRESS` (409) means the same key is still being processed: send the request again with\nthe same key and receive the order.\n\nPlacing orders has its own budget of about 30 requests a minute per key, retries included. The budgets are\ncounted by each of Cibusy's servers for itself, so they are approximate: under load a key may be admitted\nsomewhat more than that.","operationId":"createOrder","parameters":[{"name":"venueId","in":"path","description":"The venue, one of the ids `GET /venues` lists.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","description":"A value you make up once per order, 8 to 64 letters, digits, '-' or '_' (a UUID works), and send again, unchanged, on every retry of that order. Required.","required":true,"schema":{"maxLength":64,"minLength":8,"pattern":"^[A-Za-z0-9_-]{8,64}$","type":"string","example":"0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"requestBody":{"description":"The order","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PublicCreateOrderRequest"}],"description":"An order a venue's site sends in, or a basket it wants priced first. It carries no prices: the server prices\nevery line from the venue's menu, applies the venue's campaigns and service fee, and answers with the total."},"examples":{"dineIn":{"summary":"Dine-in: a round for a table","description":"Added to the table's open bill, or opens one. No customer and no payment method: it is settled at the table.","value":{"type":"DineIn","tableId":"2f4e6a8c-0b1d-4c3e-9f5a-7b9d1e3c5a70","note":"A guest has a nut allergy.","lines":[{"productId":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10","portionId":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24","quantity":2,"note":"Well done.","extraIds":["f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"],"removedIngredientIds":["b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"]}]}},"takeaway":{"summary":"Takeaway: collected at the venue","description":"Needs the customer's name and phone number.","value":{"type":"Takeaway","customer":{"name":"Ayşe Yılmaz","phone":"+905321112233"},"paymentMethod":"Card","lines":[{"productId":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10","portionId":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24","quantity":2,"note":"Well done.","extraIds":["f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"],"removedIngredientIds":["b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"]}]}},"delivery":{"summary":"Delivery: brought to the customer","description":"Needs the customer's name, phone number and address.","value":{"type":"Delivery","customer":{"name":"Ayşe Yılmaz","phone":"+905321112233","address":"Bağdat Cad. No 12 Daire 4, Kadıköy"},"paymentMethod":"Cash","note":"Please ring the bell.","lines":[{"productId":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10","portionId":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24","quantity":2,"note":"Well done.","extraIds":["f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"],"removedIngredientIds":["b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"]}]}}}}},"required":true},"responses":{"201":{"description":"The order was placed. `Location` points at it; `addedLineIds` are exactly the lines this call put on it, so keep them","headers":{"Location":{"description":"Where the order can be read: `/public/v1/orders/{orderId}`.","required":true,"schema":{"type":"string","example":"/public/v1/orders/7c9e6679-7425-40de-944b-e07fc1f90ae7"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOrderPlacementDtoApiResponse"}}}},"200":{"description":"An idempotent retry: the order the same `Idempotency-Key` already made. Its `addedLineIds` is a best-effort reconstruction (see the remarks), so keep the ones from the `201`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOrderPlacementDtoApiResponse"}}}},"400":{"description":"`PUBLIC_API_IDEMPOTENCY_KEY_INVALID`, `PUBLIC_API_ORDER_INVALID` or `PUBLIC_API_PRODUCT_OPTION_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_IDEMPOTENCY_KEY_INVALID":{"summary":"PUBLIC_API_IDEMPOTENCY_KEY_INVALID","value":{"success":false,"userMessage":"Send a unique Idempotency-Key header of 8 to 64 letters, digits, '-' or '_' with every order.","developerMessage":"The Idempotency-Key header must hold 8 to 64 characters: letters, digits, '-' or '_'.","errorCode":"PUBLIC_API_IDEMPOTENCY_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_ORDER_INVALID":{"summary":"PUBLIC_API_ORDER_INVALID","value":{"success":false,"userMessage":"The order request is not valid. Check the order type, table, customer details and lines.","developerMessage":"The order request is not valid. customer.phone: Required. lines[0].quantity: Must be between 1 and 20.","errorCode":"PUBLIC_API_ORDER_INVALID","validationErrors":[{"field":"customer.phone","message":"Required.","attemptedValue":null},{"field":"lines[0].quantity","message":"Must be between 1 and 20.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_PRODUCT_OPTION_INVALID":{"summary":"PUBLIC_API_PRODUCT_OPTION_INVALID","value":{"success":false,"userMessage":"An extra or removed ingredient does not belong to its product, or the option choices are not valid.","developerMessage":"lines[0].extraIds[0]: Not an extra of this product.","errorCode":"PUBLIC_API_PRODUCT_OPTION_INVALID","validationErrors":[{"field":"lines[0].extraIds[0]","message":"Not an extra of this product.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"place orders\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_VENUE_NOT_FOUND`, `PUBLIC_API_TABLE_NOT_FOUND` or `PUBLIC_API_PRODUCT_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_TABLE_NOT_FOUND":{"summary":"PUBLIC_API_TABLE_NOT_FOUND","value":{"success":false,"userMessage":"Table not found at this venue.","developerMessage":"The table is not one of this venue's tables.","errorCode":"PUBLIC_API_TABLE_NOT_FOUND","validationErrors":[{"field":"tableId","message":"Not one of this venue's tables.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_PRODUCT_NOT_FOUND":{"summary":"PUBLIC_API_PRODUCT_NOT_FOUND","value":{"success":false,"userMessage":"A product or portion in the order was not found on this venue's menu.","developerMessage":"lines[0].productId: Not a product on this venue's menu.","errorCode":"PUBLIC_API_PRODUCT_NOT_FOUND","validationErrors":[{"field":"lines[0].productId","message":"Not a product on this venue's menu.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"409":{"description":"`PUBLIC_API_ORDERING_UNAVAILABLE`, `PLACE_IS_BUSY`, `OUTSIDE_WORKING_HOURS`, `PUBLIC_API_PRODUCT_UNAVAILABLE` or `ORDER_CREATION_IN_PROGRESS`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_ORDERING_UNAVAILABLE":{"summary":"PUBLIC_API_ORDERING_UNAVAILABLE","value":{"success":false,"userMessage":"This venue is not accepting orders right now.","developerMessage":"The venue has no active subscription.","errorCode":"PUBLIC_API_ORDERING_UNAVAILABLE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PLACE_IS_BUSY":{"summary":"PLACE_IS_BUSY","value":{"success":false,"userMessage":"This venue is very busy right now and cannot take new orders. Please try again shortly.","developerMessage":"Place 3fa85f64-5717-4562-b3fc-2c963f66afa6 declared a rush until 2026-10-01T18:45:00.0000000Z","errorCode":"PLACE_IS_BUSY","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"OUTSIDE_WORKING_HOURS":{"summary":"OUTSIDE_WORKING_HOURS","value":{"success":false,"userMessage":"This venue is closed right now. Please try again during its opening hours.","developerMessage":"Place 3fa85f64-5717-4562-b3fc-2c963f66afa6 is closed right now; today's hours are 11:00-23:30 with 15m grace","errorCode":"OUTSIDE_WORKING_HOURS","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_PRODUCT_UNAVAILABLE":{"summary":"PUBLIC_API_PRODUCT_UNAVAILABLE","value":{"success":false,"userMessage":"A product in the order cannot be ordered right now.","developerMessage":"lines[0].productId: The product is hidden from the menu.","errorCode":"PUBLIC_API_PRODUCT_UNAVAILABLE","validationErrors":[{"field":"lines[0].productId","message":"The product is hidden from the menu.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"ORDER_CREATION_IN_PROGRESS":{"summary":"ORDER_CREATION_IN_PROGRESS","value":{"success":false,"userMessage":"This order is already being processed. Please try again.","developerMessage":"Idempotency key '0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60' is being processed by a concurrent request for place 3fa85f64-5717-4562-b3fc-2c963f66afa6. Retry to receive the created order.","errorCode":"ORDER_CREATION_IN_PROGRESS","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 30 orders a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}/orders/preview":{"post":{"tags":["Orders"],"summary":"Prices a basket without ordering it","description":"Send the order you would place, without the `Idempotency-Key`, and read what it would cost: each line at menu\nprice with its extras, what the venue's automatic campaigns take off, the service fee, and the total. Placing the\nsame basket charges that total.\n\nExample request:\n\n    POST /public/v1/venues/3fa85f64-5717-4562-b3fc-2c963f66afa6/orders/preview\n    X-Api-Key: cbk_…\n\n    {\n      \"type\": \"DineIn\",\n      \"tableId\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n      \"lines\": [ { \"productId\": \"0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60\", \"portionId\": \"5b1c7d90-2a3e-4f6b-8c9d-0e1f2a3b4c5d\", \"quantity\": 2 } ]\n    }\n\nIt checks what placing the order checks about the basket — the shape, the menu and a dine-in table — and refuses\nwith the same errors, but not whether the venue is open or subscribed: a site can show prices while the\nkitchen is shut. It prices the basket on its own; when a dine-in round joins a bill that is already open,\ncampaigns that look at the whole bill can price the round differently once it is on it, and the placed order's\n`totals` are the figures that count. Previews count against the key's budget of about 120 requests a minute.","operationId":"previewOrder","parameters":[{"name":"venueId","in":"path","description":"The venue, one of the ids `GET /venues` lists.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"requestBody":{"description":"The basket, in the shape of an order","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PublicCreateOrderRequest"}],"description":"An order a venue's site sends in, or a basket it wants priced first. It carries no prices: the server prices\nevery line from the venue's menu, applies the venue's campaigns and service fee, and answers with the total."},"examples":{"dineIn":{"summary":"Dine-in: a round for a table","description":"Added to the table's open bill, or opens one. No customer and no payment method: it is settled at the table.","value":{"type":"DineIn","tableId":"2f4e6a8c-0b1d-4c3e-9f5a-7b9d1e3c5a70","note":"A guest has a nut allergy.","lines":[{"productId":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10","portionId":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24","quantity":2,"note":"Well done.","extraIds":["f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"],"removedIngredientIds":["b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"]}]}},"takeaway":{"summary":"Takeaway: collected at the venue","description":"Needs the customer's name and phone number.","value":{"type":"Takeaway","customer":{"name":"Ayşe Yılmaz","phone":"+905321112233"},"paymentMethod":"Card","lines":[{"productId":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10","portionId":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24","quantity":2,"note":"Well done.","extraIds":["f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"],"removedIngredientIds":["b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"]}]}},"delivery":{"summary":"Delivery: brought to the customer","description":"Needs the customer's name, phone number and address.","value":{"type":"Delivery","customer":{"name":"Ayşe Yılmaz","phone":"+905321112233","address":"Bağdat Cad. No 12 Daire 4, Kadıköy"},"paymentMethod":"Cash","note":"Please ring the bell.","lines":[{"productId":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10","portionId":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24","quantity":2,"note":"Well done.","extraIds":["f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"],"removedIngredientIds":["b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"]}]}}}}},"required":true},"responses":{"200":{"description":"The priced basket","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicOrderPreviewDtoApiResponse"}}}},"400":{"description":"`PUBLIC_API_ORDER_INVALID` or `PUBLIC_API_PRODUCT_OPTION_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_ORDER_INVALID":{"summary":"PUBLIC_API_ORDER_INVALID","value":{"success":false,"userMessage":"The order request is not valid. Check the order type, table, customer details and lines.","developerMessage":"The order request is not valid. customer.phone: Required. lines[0].quantity: Must be between 1 and 20.","errorCode":"PUBLIC_API_ORDER_INVALID","validationErrors":[{"field":"customer.phone","message":"Required.","attemptedValue":null},{"field":"lines[0].quantity","message":"Must be between 1 and 20.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_PRODUCT_OPTION_INVALID":{"summary":"PUBLIC_API_PRODUCT_OPTION_INVALID","value":{"success":false,"userMessage":"An extra or removed ingredient does not belong to its product, or the option choices are not valid.","developerMessage":"lines[0].extraIds[0]: Not an extra of this product.","errorCode":"PUBLIC_API_PRODUCT_OPTION_INVALID","validationErrors":[{"field":"lines[0].extraIds[0]","message":"Not an extra of this product.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"place orders\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_VENUE_NOT_FOUND`, `PUBLIC_API_TABLE_NOT_FOUND` or `PUBLIC_API_PRODUCT_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_TABLE_NOT_FOUND":{"summary":"PUBLIC_API_TABLE_NOT_FOUND","value":{"success":false,"userMessage":"Table not found at this venue.","developerMessage":"The table is not one of this venue's tables.","errorCode":"PUBLIC_API_TABLE_NOT_FOUND","validationErrors":[{"field":"tableId","message":"Not one of this venue's tables.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"PUBLIC_API_PRODUCT_NOT_FOUND":{"summary":"PUBLIC_API_PRODUCT_NOT_FOUND","value":{"success":false,"userMessage":"A product or portion in the order was not found on this venue's menu.","developerMessage":"lines[0].productId: Not a product on this venue's menu.","errorCode":"PUBLIC_API_PRODUCT_NOT_FOUND","validationErrors":[{"field":"lines[0].productId","message":"Not a product on this venue's menu.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"409":{"description":"`PUBLIC_API_PRODUCT_UNAVAILABLE`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_PRODUCT_UNAVAILABLE":{"summary":"PUBLIC_API_PRODUCT_UNAVAILABLE","value":{"success":false,"userMessage":"A product in the order cannot be ordered right now.","developerMessage":"lines[0].productId: The product is hidden from the menu.","errorCode":"PUBLIC_API_PRODUCT_UNAVAILABLE","validationErrors":[{"field":"lines[0].productId","message":"The product is hidden from the menu.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 120 requests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/reservations":{"get":{"tags":["Reservations"],"summary":"Lists the reservations taken through the API","description":"Lists the reservations the API took at the venues the key reaches, by any of the venue's keys — one taken with a\nkey that has since been replaced is still listed. A booking the venue took some other way, in the Cibusy app, on\nits page on cibusy.com or over the phone, is not. Each item is the same body\n`GET /reservations/{reservationId}` returns.\n\nExample request:\n\n    GET /public/v1/reservations?status=open&pageSize=20\n    X-Api-Key: cbk_…\n\n**The order depends on what is asked for.** `open` comes soonest first: the next table to arrive is at the\ntop. `closed` and `all` come latest first. Read a page at a time: while `nextCursor` is not null,\nask again with `cursor` set to it.","operationId":"listReservations","parameters":[{"name":"venueId","in":"query","description":"Only this venue's reservations. A headquarter's key can name one of its branches; leave it out to list the reservations of every venue the key reaches.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"Which reservations: `open` (pending, confirmed or seated, the default), `closed` (declined, cancelled, completed or a no-show) or `all`.","schema":{"enum":["Open","Closed","All"],"type":"string","description":"Which reservations a list returns.","default":"Open"}},{"name":"cursor","in":"query","description":"The page to read, counted from 1. Take it from `nextCursor` of the previous page.","schema":{"type":"integer","format":"int32","default":1}},{"name":"pageSize","in":"query","description":"Reservations per page: 1 to 100, 50 by default.","schema":{"type":"integer","format":"int32","default":50}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"One page of reservations","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicReservationListDtoApiResponse"}}}},"400":{"description":"`INVALID_PAGE_NUMBER`, `INVALID_PAGE_SIZE` or `VALIDATION_ERROR` for a `status` that is not one of the three","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"INVALID_PAGE_NUMBER":{"summary":"INVALID_PAGE_NUMBER","value":{"success":false,"userMessage":"Page number must be greater than 0.","developerMessage":"The cursor is a page number and starts at 1, received: 0","errorCode":"INVALID_PAGE_NUMBER","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"INVALID_PAGE_SIZE":{"summary":"INVALID_PAGE_SIZE","value":{"success":false,"userMessage":"Invalid page size. Please use a value between 1 and 100.","developerMessage":"The page size must be between 1 and 100, received: 500","errorCode":"INVALID_PAGE_SIZE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"take reservations\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_VENUE_NOT_FOUND`: `venueId` is not a venue this key can reach","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 120 requests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/reservations/{reservationId}":{"get":{"tags":["Reservations"],"summary":"Reads one reservation as the venue has it now","description":"Poll it to follow a reservation, or rely on the webhook, which sends this same body as\n`reservation.updated` whenever it changes. A reservation is readable by any key of the venue it was taken\nat, and by a headquarter's key at any of its branches; one nobody took through the API, one at another venue and\none that does not exist all answer 404.\n\n**status** is one word for where the booking stands: `Pending` while the venue has not answered,\n`Confirmed` once it has accepted (or at once, at a venue that confirms on the spot), `Declined` when it\nsaid no, `Cancelled` when it was called off — `cancelledBy` says by whom: the `Customer`, the\n`Venue`, or the `System` when the venue had not answered a request by the time it was for —\n`Seated` once the guest is checked in at the door, `Completed` when the visit is over and `NoShow`\nwhen the venue accepted and nobody came.\n\n**code** and **pageUrl** are the guest's way in. The page on cibusy.com shows the booking and, once it is\nconfirmed, the QR the venue scans at the door; the code is what the guest reads out if they have no screen to\nshow. Whoever holds either can see and cancel the booking, so give them to the guest only.","operationId":"getReservation","parameters":[{"name":"reservationId","in":"path","description":"The reservation's id, from the answer to taking it.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The reservation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicReservationDtoApiResponse"}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"take reservations\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_RESERVATION_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_RESERVATION_NOT_FOUND":{"summary":"PUBLIC_API_RESERVATION_NOT_FOUND","value":{"success":false,"userMessage":"Reservation not found.","developerMessage":"No reservation with this id was taken through the API at a venue this key can reach.","errorCode":"PUBLIC_API_RESERVATION_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 120 requests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}/reservation-availability":{"get":{"tags":["Reservations"],"summary":"Lists the times a venue can be booked at","description":"The venue's booking calendar: the same times its own booking page on cibusy.com offers, so a time listed here is\na time `POST /venues/{venueId}/reservations` accepts. Show a slot's `time`, which is on the venue's own\nclock, and send its `startsAt`.\n\nExample request:\n\n    GET /public/v1/venues/3fa85f64-5717-4562-b3fc-2c963f66afa6/reservation-availability?from=2026-10-09&days=7\n    X-Api-Key: cbk_…\n\nSlots are half an hour apart, within the hours the venue takes bookings in (its working hours, or the booking\nhours it narrowed them to), never less than `minNoticeMinutes` away and never past\n`lastBookableDate`. A venue that seats past midnight lists those late times under the evening they belong\nto. A day is `Closed` on the venue's day off or a date it marked as closed, and `HoursUnknown` when the\nvenue has entered no working hours for that weekday; neither has slots.\n\n**reservationsEnabled** is false for a venue that has switched bookings off: `days` is then empty and a\nbooking is refused. **confirmsAutomatically** says what a new booking will be: `Confirmed` at once, or\n`Pending` until the venue answers.\n\nThe calendar says when the venue seats, not how full it is: Cibusy does not count tables, and the venue declines\na request it has no room for.","operationId":"getAvailability","parameters":[{"name":"venueId","in":"path","description":"The venue, one of the ids `GET /venues` lists.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","description":"The first day wanted, on the venue's calendar, as `yyyy-MM-dd`. The venue's today when left out or earlier than it.","schema":{"type":"string","format":"date"}},{"name":"days","in":"query","description":"How many days: 7 when left out, and never more than 14 however many are asked for.","schema":{"type":"integer","format":"int32"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The venue's booking calendar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicReservationAvailabilityDtoApiResponse"}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"take reservations\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_VENUE_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 120 requests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}/reservations":{"post":{"tags":["Reservations"],"summary":"Takes a reservation at a venue","description":"Send the booking your site took. The response is `201 Created` with a `Location` header pointing at\n`GET /reservations/{reservationId}`.\n\nExample request:\n\n    POST /public/v1/venues/3fa85f64-5717-4562-b3fc-2c963f66afa6/reservations\n    X-Api-Key: cbk_…\n\n    {\n      \"startsAt\": \"2026-10-09T17:00:00.000Z\",\n      \"guests\": 4,\n      \"customer\": { \"name\": \"Ayşe Yılmaz\", \"phone\": \"+905321112233\" },\n      \"note\": \"A table by the window, if there is one.\"\n    }\n\n**Sending it twice is safe, and needs no key.** A guest cannot sit at two tables at once, so a booking for the\nsame phone number at the same venue for the same moment, while the first still stands, is the same booking: you\nget the one the first call made, as the venue has it now, with status `200 OK`, and the venue is not told a\nsecond time. If your request times out, send it again unchanged. A repeat is answered before the venue's rules are\nlooked at again, since the booking already exists.\n\n**Whether it is confirmed is the venue's setting.** At a venue that confirms bookings as they are made the\nanswer is `Confirmed`; otherwise it is `Pending` until somebody at the venue accepts or declines it, and\na request the venue has not answered by the time it was for is cancelled by the `System`.\n`GET /venues/{venueId}/reservation-availability` says which to expect, as `confirmsAutomatically`.\n\n**The venue's booking rules decide whether the time can be taken**, the same ones its own booking page holds a\nguest to: the venue takes bookings (`RESERVATIONS_DISABLED`), at most 20 guests (`TOO_MANY_GUESTS`), a\ntime in the future (`INVALID_RESERVATION_DATE`), at least 30 minutes away (`RESERVATION_TOO_SOON`), at\nmost 60 days ahead (`RESERVATION_TOO_FAR`) and one the venue seats at\n(`RESERVATION_TIME_UNAVAILABLE`). A time taken from the availability answer passes all of them. The venue\nalso needs an active subscription (`PUBLIC_API_RESERVATIONS_UNAVAILABLE`). Where an error names a part of\nthe request, `validationErrors` says which, by path (`customer.phone`).\n\n**Tell the guest yourself.** Cibusy sends no text message about a booking taken through the API. Give the\nguest the `pageUrl` or the `code` of the answer: it is what the venue checks them in with at the door.\n\nTaking and cancelling reservations share the budget of about 30 requests a minute per key that placing orders\nhas, repeats included. The budgets are counted by each of Cibusy's servers for itself, so they are approximate:\nunder load a key may be admitted somewhat more than that.","operationId":"createReservation","parameters":[{"name":"venueId","in":"path","description":"The venue, one of the ids `GET /venues` lists.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"requestBody":{"description":"The booking","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PublicCreateReservationRequest"}],"description":"A booking a venue's site took and sends in. The venue's own booking rules decide whether it can be taken:\n`GET /venues/{venueId}/reservation-availability` lists the times that will be accepted."}}}},"responses":{"201":{"description":"The reservation was taken. `Location` points at it","headers":{"Location":{"description":"Where the order can be read: `/public/v1/orders/{orderId}`.","required":true,"schema":{"type":"string","example":"/public/v1/orders/7c9e6679-7425-40de-944b-e07fc1f90ae7"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicReservationDtoApiResponse"}}}},"200":{"description":"A repeat: the reservation the same booking already made, as the venue has it now","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicReservationDtoApiResponse"}}}},"400":{"description":"`PUBLIC_API_RESERVATION_INVALID`, `TOO_MANY_GUESTS` or `INVALID_RESERVATION_DATE`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_RESERVATION_INVALID":{"summary":"PUBLIC_API_RESERVATION_INVALID","value":{"success":false,"userMessage":"The reservation request is not valid. Check the time, the number of guests and the customer details.","developerMessage":"The reservation request is not valid. guests: At least 1. customer.phone: Not a phone number.","errorCode":"PUBLIC_API_RESERVATION_INVALID","validationErrors":[{"field":"guests","message":"At least 1.","attemptedValue":null},{"field":"customer.phone","message":"Not a phone number.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"TOO_MANY_GUESTS":{"summary":"TOO_MANY_GUESTS","value":{"success":false,"userMessage":"Online reservations are for up to 20 guests. For a larger group, please call the venue.","developerMessage":"Online bookings are for at most 20 guests, got 24","errorCode":"TOO_MANY_GUESTS","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"INVALID_RESERVATION_DATE":{"summary":"INVALID_RESERVATION_DATE","value":{"success":false,"userMessage":"That time has already passed. Please pick a later time.","developerMessage":"Reservation time 2026-10-01T09:00:00.0000000Z is not in the future","errorCode":"INVALID_RESERVATION_DATE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"take reservations\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_VENUE_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"409":{"description":"`PUBLIC_API_RESERVATIONS_UNAVAILABLE`, `RESERVATIONS_DISABLED`, `RESERVATION_TOO_SOON`, `RESERVATION_TOO_FAR` or `RESERVATION_TIME_UNAVAILABLE`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_RESERVATIONS_UNAVAILABLE":{"summary":"PUBLIC_API_RESERVATIONS_UNAVAILABLE","value":{"success":false,"userMessage":"This venue is not taking reservations right now.","developerMessage":"The venue has no active subscription.","errorCode":"PUBLIC_API_RESERVATIONS_UNAVAILABLE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"RESERVATIONS_DISABLED":{"summary":"RESERVATIONS_DISABLED","value":{"success":false,"userMessage":"This venue isn't taking online reservations right now.","developerMessage":"Place 3fa85f64-5717-4562-b3fc-2c963f66afa6 does not take reservations","errorCode":"RESERVATIONS_DISABLED","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"RESERVATION_TOO_SOON":{"summary":"RESERVATION_TOO_SOON","value":{"success":false,"userMessage":"Reservations need at least 30 minutes' notice. Please pick a later time.","developerMessage":"Reservation time 2026-10-01T09:45:00.0000000Z is less than 30 minutes away","errorCode":"RESERVATION_TOO_SOON","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"RESERVATION_TOO_FAR":{"summary":"RESERVATION_TOO_FAR","value":{"success":false,"userMessage":"You can book up to 60 days ahead.","developerMessage":"Reservation date 2026-12-15 is more than 60 days ahead","errorCode":"RESERVATION_TOO_FAR","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}},"RESERVATION_TIME_UNAVAILABLE":{"summary":"RESERVATION_TIME_UNAVAILABLE","value":{"success":false,"userMessage":"The venue doesn't take reservations at that time. Please pick another time from the list.","developerMessage":"Place 3fa85f64-5717-4562-b3fc-2c963f66afa6 does not seat at 2026-10-09 16:00 local time","errorCode":"RESERVATION_TIME_UNAVAILABLE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 30 orders and reservations a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/reservations/{reservationId}/cancel":{"post":{"tags":["Reservations"],"summary":"Calls a reservation off","description":"For a guest who changes their mind on your site. It is the guest's cancellation, the same as one made on the\nreservation's page on cibusy.com: `cancelledBy` reads `Customer`, and the venue is told, with the\nreason when one was given.\n\nExample request:\n\n    POST /public/v1/reservations/9b2f6c1e-3d4a-4f5b-8c7d-1e2f3a4b5c6d/cancel\n    X-Api-Key: cbk_…\n\n    { \"reason\": \"We cannot make it on Friday after all.\" }\n\nA reservation can be called off while it stands (`Pending` or `Confirmed`), the guest has not been\nchecked in and its time has not come; `canCancel` on the reservation says so beforehand. Otherwise the\nanswer is `RESERVATION_NOT_CANCELLABLE`. Calling off a reservation that is already cancelled, by anybody,\nanswers `200` with it as it stands, so a request that was cut off can be sent again.","operationId":"cancelReservation","parameters":[{"name":"reservationId","in":"path","description":"The reservation's id, from the answer to taking it.","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"requestBody":{"description":"Why, in the guest's words. Optional, and so is the body.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PublicCancelReservationRequest"}],"description":"Why a reservation is being called off. The body is optional."}}}},"responses":{"200":{"description":"The reservation, cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicReservationDtoApiResponse"}}}},"400":{"description":"`PUBLIC_API_RESERVATION_INVALID`: the reason is longer than 250 characters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_RESERVATION_INVALID":{"summary":"PUBLIC_API_RESERVATION_INVALID","value":{"success":false,"userMessage":"The reservation request is not valid. Check the time, the number of guests and the customer details.","developerMessage":"The reservation request is not valid. guests: At least 1. customer.phone: Not a phone number.","errorCode":"PUBLIC_API_RESERVATION_INVALID","validationErrors":[{"field":"guests","message":"At least 1.","attemptedValue":null},{"field":"customer.phone","message":"Not a phone number.","attemptedValue":null}],"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was not created with the \"take reservations\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"`PUBLIC_API_RESERVATION_NOT_FOUND`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_RESERVATION_NOT_FOUND":{"summary":"PUBLIC_API_RESERVATION_NOT_FOUND","value":{"success":false,"userMessage":"Reservation not found.","developerMessage":"No reservation with this id was taken through the API at a venue this key can reach.","errorCode":"PUBLIC_API_RESERVATION_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"409":{"description":"`RESERVATION_NOT_CANCELLABLE`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"RESERVATION_NOT_CANCELLABLE":{"summary":"RESERVATION_NOT_CANCELLABLE","value":{"success":false,"userMessage":"This reservation can no longer be cancelled.","developerMessage":"A Seated reservation for 2026-10-09T17:00:00.0000000Z can no longer be cancelled.","errorCode":"RESERVATION_NOT_CANCELLABLE","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 30 orders and reservations a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues":{"get":{"tags":["Venues"],"summary":"List the venues the API key can read","description":"The key's own venue comes first. For a key created for a headquarter, each of the headquarter's branches\nfollows, ordered by name. A key created for a branch lists that branch only.\n\nUse a venue's `id` in every call that is about one venue.","operationId":"listVenues","parameters":[{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The venues the key can read, its own first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicVenueSummaryDtoListApiResponse"}}}},"401":{"description":"The `X-Api-Key` header is missing (`PUBLIC_API_KEY_MISSING`), or the key is invalid or has been revoked (`PUBLIC_API_KEY_INVALID`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"The key may not make this call (`PUBLIC_API_SCOPE_MISSING`). Every key created in the venue's panel may read venues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"429":{"description":"The key is over its budget of about 120 requests a minute. Wait the number of seconds in the `Retry-After` header","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}":{"get":{"tags":["Venues"],"summary":"Get one venue's details","description":"Name, address, map position, time zone, opening hours and the languages of the menu, and whether the venue\nwould take an order right now.\n\n`ordering.acceptingOrdersNow` is worked out on every call. It is true when the venue's subscription is\nactive, it has not declared a rush, and it is inside its opening hours (with the grace the venue allows\neither side). When it is false, `ordering.busyUntil` is set for a rush and null for a closed venue.","operationId":"getVenue","parameters":[{"name":"venueId","in":"path","description":"The venue's id, from the list of venues","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The venue's details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicVenueDtoApiResponse"}}}},"401":{"description":"The `X-Api-Key` header is missing (`PUBLIC_API_KEY_MISSING`), or the key is invalid or has been revoked (`PUBLIC_API_KEY_INVALID`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"The key may not make this call (`PUBLIC_API_SCOPE_MISSING`). Every key created in the venue's panel may read venues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"The venue does not exist, or this key cannot read it (`PUBLIC_API_VENUE_NOT_FOUND`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key is over its budget of about 120 requests a minute. Wait the number of seconds in the `Retry-After` header","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}/menu":{"get":{"tags":["Venues"],"summary":"Get a venue's menu","description":"The categories of the venue's menu and the products in them, with their portions, prices, extras, option\ngroups, removable ingredients, allergens and stock status. Everything the venue has hidden from its menu is\nleft out, and so is a category with nothing left in it. Categories and products come in the order the venue\nshows them. All prices are in Turkish lira and include VAT.\n\nThe menu does not depend on how the venue's QR menu is set up: it is returned whether or not the venue shows\nthe QR menu, and whether or not it has put a PDF menu in its place.\n\n**Language.** `lang` is the two-letter code of one of the venue's languages (`languages` in the venue's\ndetails); a region on the end (`en-US`) is ignored. A language the venue does not offer, or none, is answered\nin the venue's default language, and a venue with no language set up is answered in the text it wrote. The\n`language` in the answer is the language the text is actually in.\n\n**Campaigns.** A portion that a running campaign discounts carries `campaign` with the price to show. It is\nworked out on every call, so a happy hour begins and ends on time.\n\n**Caching.** The menu is kept for up to five minutes and refreshed at once when the venue edits it. The\nresponse carries an `ETag`: send it back in `If-None-Match` and an unchanged menu answers `304 Not Modified`\nwith no body, which does not need to be parsed again. The `ETag` stands for the menu in `data`, not for the\nenvelope around it, whose timestamp changes on every call. Always revalidate with the server rather than\nreuse a stored copy: the response says `Cache-Control: private, no-cache`.","operationId":"getMenu","parameters":[{"name":"venueId","in":"path","description":"The venue's id, from the list of venues","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"lang","in":"query","description":"The language of the menu's text, for example `en`. Leave it out for the venue's default language","schema":{"type":"string"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}},{"name":"If-None-Match","in":"header","description":"The `ETag` of the copy you already hold. While it is still the current version the answer is `304 Not Modified` with no body.","schema":{"type":"string","example":"\"3f1c9a6b2d8e4f70a5b3c1d9e8f7a6b5\""}}],"responses":{"200":{"description":"The venue's menu, with its `ETag`","headers":{"ETag":{"description":"The version of this answer's `data`: the first 32 hex digits of a SHA-256, in quotes. Send it back in `If-None-Match`.","required":true,"schema":{"type":"string","example":"\"3f1c9a6b2d8e4f70a5b3c1d9e8f7a6b5\""}},"Cache-Control":{"description":"Always `private, no-cache`: keep a copy, but ask the server whether it is still current before using it.","required":true,"schema":{"type":"string","example":"private, no-cache"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicMenuDtoApiResponse"}}}},"304":{"description":"The menu has not changed since the version named in `If-None-Match`. There is no body","headers":{"ETag":{"description":"The version of this answer's `data`: the first 32 hex digits of a SHA-256, in quotes. Send it back in `If-None-Match`.","required":true,"schema":{"type":"string","example":"\"3f1c9a6b2d8e4f70a5b3c1d9e8f7a6b5\""}},"Cache-Control":{"description":"Always `private, no-cache`: keep a copy, but ask the server whether it is still current before using it.","required":true,"schema":{"type":"string","example":"private, no-cache"}}}},"401":{"description":"The `X-Api-Key` header is missing (`PUBLIC_API_KEY_MISSING`), or the key is invalid or has been revoked (`PUBLIC_API_KEY_INVALID`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"The key may not make this call (`PUBLIC_API_SCOPE_MISSING`). Every key created in the venue's panel may read menus","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"The venue does not exist, or this key cannot read it (`PUBLIC_API_VENUE_NOT_FOUND`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key is over its budget of about 120 requests a minute. Wait the number of seconds in the `Retry-After` header","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/venues/{venueId}/tables":{"get":{"tags":["Venues"],"summary":"List a venue's tables","description":"The venue's floors, terraces and rooms, each with its tables, in the order the venue's till shows them. A\ndine-in order names one of these tables by its `id`.","operationId":"listTables","parameters":[{"name":"venueId","in":"path","description":"The venue's id, from the list of venues","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The venue's areas and their tables","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicTableAreaDtoListApiResponse"}}}},"401":{"description":"The `X-Api-Key` header is missing (`PUBLIC_API_KEY_MISSING`), or the key is invalid or has been revoked (`PUBLIC_API_KEY_INVALID`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"The key may not make this call (`PUBLIC_API_SCOPE_MISSING`). Every key created in the venue's panel may read tables","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"404":{"description":"The venue does not exist, or this key cannot read it (`PUBLIC_API_VENUE_NOT_FOUND`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_VENUE_NOT_FOUND":{"summary":"PUBLIC_API_VENUE_NOT_FOUND","value":{"success":false,"userMessage":"Venue not found, or this API key cannot access it.","developerMessage":"Venue not found, or this API key cannot access it.","errorCode":"PUBLIC_API_VENUE_NOT_FOUND","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key is over its budget of about 120 requests a minute. Wait the number of seconds in the `Retry-After` header","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}},"/public/v1/webhooks/test":{"post":{"tags":["Webhooks"],"summary":"Sends a test event to the key's webhook","description":"Sends one `webhook.test` event to the address set on the calling key, signed with the key's secret exactly\nas a real event is, and tells you how it went. Use it to check the address, the certificate and your signature\ncheck before an order depends on them. The call waits for your server's answer, so it takes as long as your server\ndoes, up to 10 seconds. Nothing about any order or reservation changes, and a test event is not retried.\n\n**This is also the reference for every webhook delivery.**\n\n**What your server receives.** An HTTPS `POST` to the address, with a JSON body and these headers (this is\nthe test event below):\n\n    Content-Type: application/json\n    User-Agent: Cibusy-Webhooks/1.0\n    X-Cibusy-Event-Id: evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34\n    X-Cibusy-Event-Type: webhook.test\n    X-Cibusy-Signature: t=1790847000,v1=0d704c590d7d639a6658090e4fbb189e4e4e0e410319f007cd509ad2e67c36c9\n\nThe body is an event: `{ \"id\", \"type\", \"createdAt\", \"data\" }`, written like every other response of this API\n(camelCase names, enums as their names, instants in UTC as `2026-10-01T09:30:00.000Z`). `type` is\n`order.updated`, whose `data` is the order exactly as `GET /orders/{orderId}` returns it,\n`reservation.updated`, whose `data` is the reservation exactly as\n`GET /reservations/{reservationId}` returns it, or `webhook.test`, whose `data` is\n`{ \"venueId\", \"keyPrefix\", \"message\" }`.\n\n    {\n      \"id\": \"evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34\",\n      \"type\": \"webhook.test\",\n      \"createdAt\": \"2026-10-01T09:30:00.000Z\",\n      \"data\": {\n        \"venueId\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n        \"keyPrefix\": \"cbk_a1B2c3D4\",\n        \"message\": \"This is a test event from Cibusy. No order has changed.\"\n      }\n    }\n\n**Check the signature.** `X-Cibusy-Signature` is `t=<unix seconds>,v1=<signature>`. The\nsignature is the lowercase hex of an HMAC-SHA256 whose key is the whole signing secret as the panel showed it —\nthe `whsec_` prefix included, taken as UTF-8 text — and whose message is `\"{t}.\" + rawBody`: the same\n`t` as in the header, a full stop, and the request body exactly as it arrived, byte for byte. Compute it over\nthe raw body, not over JSON you parsed and wrote out again: spacing, the order of properties and the escaping of\nnon-ASCII characters all change the signature. Compare in constant time, and refuse a `t` more than five\nminutes from your own clock, so that a captured delivery cannot be replayed. `t` is taken when each attempt is\nsent, so a retry carries a fresh one.\n\nTo test your check, this delivery is signed with the secret `whsec_PVJ2W1xHqfX6u1b0Jg9eT3nR5s8dKzYaLmQwEoCtUiA`\nat `t = 1790847000`, and its body, byte for byte, is the single line\n\n    {\"id\":\"evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34\",\"type\":\"webhook.test\",\"createdAt\":\"2026-10-01T09:30:00.000Z\",\"data\":{\"venueId\":\"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\"keyPrefix\":\"cbk_a1B2c3D4\",\"message\":\"This is a test event from Cibusy. No order has changed.\"}}\n\nwhich gives the `v1` signature in the headers above. In Node.js, with the raw body of the request in\n`rawBody` (a Buffer), the secret in `secret` and the header's value in `header`:\n\n    const [t, v1] = header.split(\",\").map(part => part.split(\"=\")[1]);\n    const expected = crypto.createHmac(\"sha256\", secret).update(`${t}.`).update(rawBody).digest(\"hex\");\n    const fresh = Math.abs(Date.now() / 1000 - Number(t)) <= 300;\n    const valid = fresh && v1.length === expected.length\n        && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));\n\n**Answer quickly.** Any `2xx` status within 10 seconds means delivered; the body of your answer is ignored.\nAnything else is a failure: another status, a redirect (redirects are not followed — give the final address), a\nrefused connection, a certificate that does not check out, no answer in time. Do the real work after you have\nanswered.\n\n**Failures are retried.** A failed event is sent again after 30 seconds, then after 1, 2, 4, 8, 16, 32 and 64\nminutes: 9 attempts at one state of the order over about two hours, and then not again until the order changes. Each\nretry carries the order as it is *now*. A sweep that runs once a minute also delivers the changes the first path\nmissed, so an event usually arrives within seconds but can take up to about a minute when the service is idle. An\norder is followed for 48 hours after it was placed, or until it is closed or cancelled and that last state was\ndelivered; after that its changes are not announced, and `GET /orders/{orderId}` still tells. A reservation\nis followed from the day it is made until 48 hours after the time it was for, or until it is declined, called off\nor over and that last state was delivered; its events are retried on the same schedule.\n\n**Expect repeats, and events out of order.** Delivery is at least once. The `id` of an `order.updated`\nevent belongs to one change of the order, from the state your webhook was last told about to the state now sent:\nevery retry of that change carries the same id (`X-Cibusy-Event-Id` carries it too), and every change has an\nid of its own, even one that takes the order back to a state it was in before. Remember the ids you have handled\nand ignore a repeat. Events about one order can arrive out of order; `createdAt` says which is newer, and\n`GET /orders/{orderId}` always says how the order stands now. A `reservation.updated` event's id works\nthe same way for a reservation, and `GET /reservations/{reservationId}` says how it stands now. A\n`webhook.test` event has a new id every time.\n\n**The address.** It must be a public `https://` address. Addresses that are loopback, private, link-local\nor otherwise not on the public internet are refused when it is set and again, by name resolution, at every delivery.","operationId":"sendTestEvent","parameters":[{"name":"Accept-Language","in":"header","description":"The language of `userMessage` in an error: `tr` (the default), `en`, `de`, `fr`, `it`, `es`, `ar` or `ru`. The first language in the header decides, without its region (`en-US` reads as `en`) and without weighing `q` values; a language Cibusy does not offer is answered in Turkish. It does not choose the language of a menu: that is `lang` on the menu call.","schema":{"type":"string","example":"en"}}],"responses":{"200":{"description":"The test event was sent. Look at `delivered`: it is false when your server answered with anything but a 2xx status or could not be reached, and `error` says why","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicWebhookTestResultDtoApiResponse"}}}},"401":{"description":"`PUBLIC_API_KEY_MISSING` or `PUBLIC_API_KEY_INVALID`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_KEY_MISSING":{"summary":"PUBLIC_API_KEY_MISSING","value":{"success":false,"userMessage":"An API key is required. Send it in the X-Api-Key header.","developerMessage":"No API key was sent. Send the key in the X-Api-Key header.","errorCode":"PUBLIC_API_KEY_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}},"PUBLIC_API_KEY_INVALID":{"summary":"PUBLIC_API_KEY_INVALID","value":{"success":false,"userMessage":"The API key is invalid or has been revoked.","developerMessage":"The API key is malformed, unknown or has been revoked.","errorCode":"PUBLIC_API_KEY_INVALID","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"403":{"description":"`PUBLIC_API_SCOPE_MISSING`: the key was created with neither the \"place orders\" nor the \"take reservations\" permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_SCOPE_MISSING":{"summary":"PUBLIC_API_SCOPE_MISSING","value":{"success":false,"userMessage":"This API key is not allowed to perform this action.","developerMessage":"The API key is valid but does not have the permission this endpoint requires.","errorCode":"PUBLIC_API_SCOPE_MISSING","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}},"409":{"description":"`PUBLIC_API_WEBHOOK_NOT_CONFIGURED`: no webhook address is set on this key. Set one in the venue's panel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"PUBLIC_API_WEBHOOK_NOT_CONFIGURED":{"summary":"PUBLIC_API_WEBHOOK_NOT_CONFIGURED","value":{"success":false,"userMessage":"No webhook address is set for this API key.","developerMessage":"The API key has no webhook address.","errorCode":"PUBLIC_API_WEBHOOK_NOT_CONFIGURED","validationErrors":null,"details":null,"timestamp":"2026-10-01T09:30:00.123Z","traceId":null}}}}}},"429":{"description":"The key's budget of about 30 orders, reservations and tests a minute is spent; wait `Retry-After` seconds","headers":{"Retry-After":{"description":"How many seconds to wait before calling again. Always 60: the budgets are counted per minute.","required":true,"schema":{"type":"integer","format":"int32","example":60}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"RATE_LIMIT_EXCEEDED":{"summary":"RATE_LIMIT_EXCEEDED","value":{"success":false,"userMessage":"Too many requests for this API key. Wait a minute and try again.","developerMessage":"Rate limit exceeded for policy. Please retry after 60 seconds.","errorCode":"RATE_LIMIT_EXCEEDED","retryAfter":60,"timestamp":"2026-10-01T09:30:00.1234567Z","traceId":"0HNF3N1G9B0TO:00000003"}}}}}}},"security":[{"ApiKey":[ ]}]}}},"components":{"schemas":{"ErrorResponse":{"required":["success","timestamp","traceId","userMessage","developerMessage","errorCode","validationErrors","details"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"userMessage":{"type":"string","description":"User-friendly error message for display purposes","nullable":true},"developerMessage":{"type":"string","description":"Detailed error message for developers/debugging","nullable":true},"errorCode":{"type":"string","description":"Error code for categorization","nullable":true},"validationErrors":{"type":"array","items":{"$ref":"#/components/schemas/ValidationError"},"description":"Validation errors if applicable","nullable":true},"details":{"description":"Additional error details","nullable":true}},"description":"Error response model for API error scenarios"},"OrderUpdatedWebhookEvent":{"required":["id","type","createdAt","data"],"type":"object","properties":{"id":{"type":"string","description":"The event's id, `evt_` and 32 lowercase hex characters. An `order.updated` or\n`reservation.updated` event has the same id every time the same change of the same order or reservation is\ndelivered again — after a failed attempt, say — so a receiver can recognise a repeat and ignore it. A change is\nthe step from the state the webhook was last told about to the state now sent, and every change has an id of its\nown: an order that goes back to a state it was in before is a new change, never a repeat. It differs for each\nkey's webhook too. A `webhook.test` event has a new id every time. Also sent in the\n`X-Cibusy-Event-Id` header.","example":"evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34"},"type":{"enum":["order.updated"],"type":"string","description":"The kind of event: `order.updated`, `reservation.updated` or `webhook.test`. Also sent in the `X-Cibusy-Event-Type` header.","example":"order.updated"},"createdAt":{"type":"string","description":"When this delivery was prepared, in UTC. A retry of the same event is prepared again, so it can differ between\nattempts; events about one order or reservation can arrive out of order, and this is the way to tell which is\nnewer.","format":"date-time","example":"2026-10-01T09:30:00.000Z"},"data":{"allOf":[{"$ref":"#/components/schemas/PublicOrderDto"}],"description":"The order as it was when this delivery was prepared, in the form `GET /orders/{orderId}` returns it."}},"description":"The body of an `order.updated` delivery: an order a key placed has changed. `id` is the same every time the same change to the same order is delivered again, so it is what to de-duplicate on."},"PublicCancelReservationRequest":{"type":"object","properties":{"reason":{"type":"string","description":"What the guest said, for the venue to read, at most 250 characters.","nullable":true,"example":"We cannot make it on Friday after all."}},"description":"Why a reservation is being called off. The body is optional."},"PublicCreateOrderRequest":{"required":["type","lines"],"type":"object","properties":{"type":{"enum":["DineIn","Takeaway","Delivery"],"type":"string","description":"How the order reaches the diner. Required.","example":"DineIn"},"tableId":{"type":"string","description":"The table a dine-in order is for, one of the ids `GET /venues/{venueId}/tables` lists. Required for\n`DineIn` and not allowed for `Takeaway` or `Delivery`. An id that is not one of the venue's\ntables answers 404.","format":"uuid","nullable":true,"example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"customer":{"allOf":[{"$ref":"#/components/schemas/PublicCustomerRequest"}],"description":"Who the order is for. Required for `Takeaway` (name and phone) and `Delivery` (name, phone and\naddress) and not allowed for `DineIn`: a party at a table is served where it sits.","nullable":true},"paymentMethod":{"enum":["Cash","Card","MealCard"],"type":"string","description":"How the customer says they will pay, for a takeaway or a delivery. Optional, and not allowed for\n`DineIn`, which is settled at the table. A hint for whoever hands the order over: nothing is charged\nthrough the API.","nullable":true,"example":"Card"},"note":{"type":"string","description":"A note for the kitchen about the whole order, at most 300 characters.","nullable":true,"example":"Please ring the bell, the door buzzer is broken."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderLineRequest"},"description":"The products ordered: 1 to 30 lines."}},"description":"An order a venue's site sends in, or a basket it wants priced first. It carries no prices: the server prices\nevery line from the venue's menu, applies the venue's campaigns and service fee, and answers with the total."},"PublicCreateReservationRequest":{"type":"object","properties":{"startsAt":{"type":"string","description":"When the table is for, as an instant in UTC. Take it from a slot's `startsAt` in the availability\nanswer. Required.","format":"date-time","nullable":true,"example":"2026-10-09T17:00:00.000Z"},"guests":{"type":"integer","description":"How many people are coming: 1 to 20. A larger party is booked by calling the venue. Required.","format":"int32","nullable":true,"example":4},"customer":{"allOf":[{"$ref":"#/components/schemas/PublicReservationCustomerRequest"}],"description":"Who the table is for. Required.","nullable":true},"note":{"type":"string","description":"A note for the venue, at most 250 characters.","nullable":true,"example":"A table by the window, if there is one."}},"description":"A booking a venue's site took and sends in. The venue's own booking rules decide whether it can be taken:\n`GET /venues/{venueId}/reservation-availability` lists the times that will be accepted."},"PublicCustomerDto":{"required":["name","phone","address"],"type":"object","properties":{"name":{"type":"string","description":"The customer's name.","nullable":true,"example":"Ayşe Yılmaz"},"phone":{"type":"string","description":"The customer's phone number, in international form.","nullable":true,"example":"+905321112233"},"address":{"type":"string","description":"Where a delivery goes.","nullable":true,"example":"Bağdat Cad. No 12 Daire 4, Kadıköy"}},"description":"Who a takeaway or delivery order is for, as the venue has it. Any part may be missing."},"PublicCustomerRequest":{"required":["name","phone"],"type":"object","properties":{"name":{"type":"string","description":"The customer's name, at most 100 characters. Required.","nullable":true,"example":"Ayşe Yılmaz"},"phone":{"type":"string","description":"The number to ring about the order, in international form or the way it is written locally\n(`+905321112233`, `0532 111 22 33`). At most 30 characters. Required.","nullable":true,"example":"+905321112233"},"address":{"type":"string","description":"Where a delivery goes, in the customer's own words, at most 300 characters. Required for `Delivery` and\nnot allowed for `Takeaway`.","nullable":true,"example":"Bağdat Cad. No 12 Daire 4, Kadıköy"}},"description":"The customer a takeaway or delivery order is for."},"PublicMenuCampaignDto":{"required":["campaignId","name","badgeText","discountedPrice","endsAt"],"type":"object","properties":{"campaignId":{"type":"string","description":"The campaign's id.","format":"uuid","example":"e2b4c6d8-1f3a-4b5c-9d7e-0a1b2c3d4e5f"},"name":{"type":"string","description":"The campaign's name.","example":"Öğle Menüsü İndirimi"},"badgeText":{"type":"string","description":"The short label the venue wants on the badge. Null when it set none.","nullable":true,"example":"%15 İndirim"},"discountedPrice":{"type":"number","description":"What one unit of the portion costs under the campaign, in lira, VAT included.","format":"double","example":242.25},"endsAt":{"type":"string","description":"The moment, in UTC, the campaign's current window ends, for a countdown. Null when it has no end.","format":"date-time","nullable":true,"example":"2026-10-01T15:00:00.000Z"}},"description":"A campaign that lowers a portion's price while it runs."},"PublicMenuCategoryDto":{"required":["id","name","photoUrl","displayOrder","products"],"type":"object","properties":{"id":{"type":"string","description":"The category's id.","format":"uuid","example":"0b3f1c52-6a1e-4d57-9a5b-2f6f3c1d8e90"},"name":{"type":"string","description":"The category's name, in the menu's language.","example":"Izgaralar"},"photoUrl":{"type":"string","description":"The address of the category's picture. Null when it has none.","nullable":true,"example":"https://storage.googleapis.com/cibusy/product_groups/photos/0b3f1c52-6a1e-4d57-9a5b-2f6f3c1d8e90.webp?v=1759312000"},"displayOrder":{"type":"integer","description":"The category's position on the menu, lowest first. The categories come already in this order.","format":"int32","example":1},"products":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuProductDto"},"description":"The category's products in the order the venue shows them. Never empty."}},"description":"A group of products on the menu: \"Izgaralar\", \"İçecekler\"."},"PublicMenuDto":{"required":["venueId","language","currency","categories"],"type":"object","properties":{"venueId":{"type":"string","description":"The venue the menu belongs to.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"language":{"type":"string","description":"The language the text of the menu is in, the two-letter code of one of the venue's languages. It is the one\nasked for with `lang` when the venue offers it, and the venue's default language otherwise. Null when\nthe venue has set up no language, and the text is then as the venue wrote it.","nullable":true,"example":"tr"},"currency":{"type":"string","description":"The currency of every price, an ISO 4217 code. Always `TRY`; prices include VAT.","example":"TRY"},"categories":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuCategoryDto"},"description":"The menu's categories in the order the venue shows them. A category with nothing visible in it is left out."}},"description":"A venue's menu as a structure a website can render: categories, the products in them and everything a guest\nchooses on a product. Only what the venue shows on its menu is in it."},"PublicMenuDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicMenuDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicMenuExtraDto":{"required":["id","name","price","optionGroupId"],"type":"object","properties":{"id":{"type":"string","description":"The extra's id, the one an order names.","format":"uuid","example":"f1a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8"},"name":{"type":"string","description":"The extra's name, in the menu's language.","example":"Ekstra Acı"},"price":{"type":"number","description":"What the extra adds to the price of the portion it is ordered with, in lira, VAT included. Zero for a free\nextra.","format":"double","example":15},"optionGroupId":{"type":"string","description":"The option group the extra is a choice in. Null for an extra that is not part of any group, which may\nbe added freely.","format":"uuid","nullable":true,"example":"c4d6e8f0-2a4c-4e6a-8c0e-1b3d5f7a9c2e"}},"description":"Something a guest may add to a product for a price, or one of the choices in an option group."},"PublicMenuOptionGroupDto":{"required":["id","name","selectionType","isRequired","displayOrder"],"type":"object","properties":{"id":{"type":"string","description":"The group's id, the value of `optionGroupId` on each of its choices.","format":"uuid","example":"c4d6e8f0-2a4c-4e6a-8c0e-1b3d5f7a9c2e"},"name":{"type":"string","description":"The group's name: what is being chosen.","example":"Pişirme Seviyesi"},"selectionType":{"enum":["Multiple","Single"],"type":"string","description":"`Single` when at most one choice may be picked, `Multiple` when any number may.","example":"Single"},"isRequired":{"type":"boolean","description":"True when the product cannot be ordered until a choice from the group is picked.","example":true},"displayOrder":{"type":"integer","description":"The group's position among the product's groups, lowest first. The groups come already in this order.","format":"int32","example":1}},"description":"A rule over some of a product's extras. The extras that name this group in their `optionGroupId` are the\nchoices in it."},"PublicMenuPortionDto":{"required":["id","name","price","isDefault","calories","weight","weightUnit","pricingUnit","orderable","campaign"],"type":"object","properties":{"id":{"type":"string","description":"The portion's id, the one an order names together with the product's id.","format":"uuid","example":"a8e1f6c3-2d94-4b70-b5a6-1c7d9e0f3a24"},"name":{"type":"string","description":"The portion's name, in the menu's language. A product with a single serving usually has one called\n\"Standart\"; show the product's name alone then.","example":"1 Porsiyon"},"price":{"type":"number","description":"The price, in lira, VAT included. For a portion sold by measure it is the price of one\n`pricingUnit`, and the portion cannot be ordered through the API.","format":"double","example":285},"isDefault":{"type":"boolean","description":"True for the portion the venue preselects.","example":true},"calories":{"type":"integer","description":"The energy of the portion in kilocalories. Null when the venue has not declared it.","format":"int32","nullable":true,"example":720},"weight":{"type":"number","description":"The weight or volume printed on the portion, a label to show beside its name, not something the price is\nworked out from. Null when the venue gave none; `weightUnit` is set whenever this is.","format":"double","nullable":true,"example":250},"weightUnit":{"enum":["Kilogram","Gram","Liter","Milliliter","Deciliter","Piece","Box","Package","Dozen","Portion","Cup","Tablespoon","Teaspoon"],"type":"string","description":"The unit of `weight`. Null when there is no weight.","nullable":true,"example":"Gram"},"pricingUnit":{"enum":["Kilogram","Gram","Liter","Milliliter","Deciliter","Piece","Box","Package","Dozen","Portion","Cup","Tablespoon","Teaspoon"],"type":"string","description":"The unit `price` is quoted per when the portion is sold by measure: `Kilogram` means the price is\nthat of a kilogram, and the venue weighs out what the guest takes. Null for an ordinary portion, whose price\nis for one serving.","nullable":true,"example":"Kilogram"},"orderable":{"type":"boolean","description":"False when the portion is sold by measure: it can be shown but not ordered through the API, because the\namount has to be weighed at the venue. True otherwise.","example":true},"campaign":{"allOf":[{"$ref":"#/components/schemas/PublicMenuCampaignDto"}],"description":"The discount the portion has right now, to show beside `price`. Null when there is none. Worked out on\nevery call, so a happy hour starts and ends on time. Only a plain discount on the portion itself shows here:\na percentage or an amount off, with no minimum basket and no coupon, that the venue shows on its menu.\nCombos, buy two pay for one and the like are taken into account when an order is priced. Every order is\npriced by the server, so nothing here is sent back with one.","nullable":true}},"description":"One size or serving of a product, and its price."},"PublicMenuProductDto":{"required":["id","name","description","contents","photoUrl","displayOrder","vatPercent","containsAlcohol","containsPorkDerivatives","acceptsMealCard","allergens","stockStatus","portions","optionGroups","extras","removableIngredients"],"type":"object","properties":{"id":{"type":"string","description":"The product's id, the one an order names.","format":"uuid","example":"5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10"},"name":{"type":"string","description":"The product's name, in the menu's language.","example":"Adana Kebap"},"description":{"type":"string","description":"A line about the product, in the menu's language. Null when the venue wrote none.","nullable":true,"example":"Acılı kıyma, közlenmiş domates ve biberle servis edilir."},"contents":{"type":"string","description":"What the product is made of, in the menu's language. Null when the venue wrote none.","nullable":true,"example":"Dana kıyma, kuyruk yağı, kırmızı toz biber, tuz."},"photoUrl":{"type":"string","description":"The address of the product's picture. Null when it has none.","nullable":true,"example":"https://storage.googleapis.com/cibusy/products/photos/3fa85f64-5717-4562-b3fc-2c963f66afa6/5d2c8a41-7b0e-4c36-8f1d-9e4a6b3c2d10.webp?v=1759312000"},"displayOrder":{"type":"integer","description":"The product's position in its category, lowest first. The products come already in this order.","format":"int32","example":1},"vatPercent":{"type":"integer","description":"The VAT rate in the prices, as a percentage: 0, 1, 10 or 20, Türkiye's brackets since July 2023. A product\nthe venue still has on a retired bracket (8 or 18 percent) reads as the bracket that replaced it, 10 or 20.","format":"int32","example":10},"containsAlcohol":{"type":"boolean","description":"True when the product contains alcohol. Show a label for it.","example":false},"containsPorkDerivatives":{"type":"boolean","description":"True when the product contains pork or something made from it. Show a label for it.","example":false},"acceptsMealCard":{"type":"boolean","description":"True when the venue takes meal cards for the product.","example":true},"allergens":{"type":"array","items":{"enum":["Gluten","Crustaceans","Eggs","Fish","Peanuts","Soybeans","Milk","Nuts","Celery","Mustard","Sesame","Sulphites","Lupin","Molluscs"],"type":"string","description":"EU mandatory food allergens (Annex II of Regulation (EU) No 1169/2011).\nStored as int in DB — never renumber existing values; only append."},"description":"The allergens the venue has declared for the product. Empty means none were declared, not that the product\nis certain to be free of them.","example":["Gluten","Sesame"]},"stockStatus":{"enum":["InStock","LowStock","OutOfStock"],"type":"string","description":"Whether the product is in stock, running low or sold out. Null when the venue does not track the product's\nstock; show no badge then. A product that is sold out stays on the menu, and an order for it is refused.","nullable":true,"example":"InStock"},"portions":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuPortionDto"},"description":"The sizes or servings the product comes in. An order names one of them."},"optionGroups":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuOptionGroupDto"},"description":"The rules that group some of the extras into choices: \"pick one level of cooking\". Empty when none of the\nextras is a choice."},"extras":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuExtraDto"},"description":"The extras a guest may add to the product, with their prices. An extra whose `optionGroupId` is set is a\nchoice in that group."},"removableIngredients":{"type":"array","items":{"$ref":"#/components/schemas/PublicMenuRemovableIngredientDto"},"description":"The ingredients a guest may ask to leave out of the product."}},"description":"A dish or drink on the menu, with the portions it comes in and the extras and changes a guest may ask for."},"PublicMenuRemovableIngredientDto":{"required":["id","name"],"type":"object","properties":{"id":{"type":"string","description":"The ingredient's id, the one an order names.","format":"uuid","example":"b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"},"name":{"type":"string","description":"The ingredient's name, in the menu's language.","example":"Soğan"}},"description":"An ingredient a guest may ask to be left out."},"PublicOrderDto":{"required":["id","number","venueId","type","status","paymentStatus","table","customer","paymentMethod","note","lines","totals","currency","createdAt","closedAt"],"type":"object","properties":{"id":{"type":"string","description":"The order's id. Use it with `GET /orders/{orderId}`.","format":"uuid","example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"},"number":{"type":"integer","description":"The number the venue calls the order by, counted from 1 within the venue. Show it to the customer; it is\nthe one printed on the kitchen ticket. Unique within one venue only.","format":"int32","example":57},"venueId":{"type":"string","description":"The venue the order was placed at.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"type":{"enum":["DineIn","Takeaway","Delivery"],"type":"string","description":"How the order reaches the diner.","example":"DineIn"},"status":{"enum":["Received","Preparing","Ready","Served","OnTheWay","Completed","Cancelled"],"type":"string","description":"Where the order has got to. See Cibusy.Application.PublicApi.Orders.DTOs.PublicOrderStatus for how it is worked out.","example":"Preparing"},"paymentStatus":{"enum":["Unpaid","PartiallyPaid","Paid"],"type":"string","description":"How much of the bill has been paid at the venue.","example":"Unpaid"},"table":{"allOf":[{"$ref":"#/components/schemas/PublicOrderTableDto"}],"description":"The table, for a dine-in order. Null for a takeaway or a delivery.","nullable":true},"customer":{"allOf":[{"$ref":"#/components/schemas/PublicCustomerDto"}],"description":"Who the order is for, for a takeaway or a delivery. Null for a dine-in order, and for an order nobody took\nthe customer's details for.","nullable":true},"paymentMethod":{"enum":["Cash","Card","MealCard"],"type":"string","description":"How the customer said they would pay, if they said. Null otherwise.","nullable":true,"example":"Card"},"note":{"type":"string","description":"The note for the kitchen the order was placed with. Null when there was none.","nullable":true,"example":"Please ring the bell."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderLineDto"},"description":"Every line on the order, including lines other people put on a table's bill and lines since cancelled. For a\ndine-in order that joined a bill that was already open, the lines of the whole bill are here: the ids a\nplacement call reports as `addedLineIds` say which of them it wrote."},"totals":{"allOf":[{"$ref":"#/components/schemas/PublicOrderTotalsDto"}],"description":"What the order comes to, and how much of it is paid."},"currency":{"type":"string","description":"The currency of every amount. Always `TRY`.","example":"TRY"},"createdAt":{"type":"string","description":"When the order was placed, in UTC.","format":"date-time","example":"2026-10-01T09:30:00Z"},"closedAt":{"type":"string","description":"When the venue closed the order, in UTC. Null while it is open, and for a cancelled order.","format":"date-time","nullable":true,"example":"2026-10-01T10:45:00Z"}},"description":"An order placed through the API, as the venue has it right now. The same body `GET /orders/{orderId}`\nreturns and the webhook's `order.updated` event carries. Money is in Turkish lira, VAT included, rounded\nto two decimals."},"PublicOrderDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicOrderDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicOrderExtraDto":{"required":["id","name","price"],"type":"object","properties":{"id":{"type":"string","description":"The extra, as the menu lists it.","format":"uuid","example":"c3d4e5f6-0a1b-4c2d-9e8f-7a6b5c4d3e2f"},"name":{"type":"string","description":"The extra's name.","example":"Extra cheese"},"price":{"type":"number","description":"What it cost per unit when the order was placed.","format":"double","example":15}},"description":"An extra on a line."},"PublicOrderLineDto":{"required":["id","productId","productName","portionId","portionName","quantity","unitPrice","total","status","extras","removedIngredients","note","orderedAt"],"type":"object","properties":{"id":{"type":"string","description":"The line's id. Stays the same for as long as the line is on the order.","format":"uuid","example":"9b2d4f6a-1c3e-4a5b-8d7f-0a1b2c3d4e5f"},"productId":{"type":"string","description":"The product, as the menu lists it.","format":"uuid","example":"0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60"},"productName":{"type":"string","description":"The product's name in the venue's own words.","example":"Menemen"},"portionId":{"type":"string","description":"The portion ordered, as the menu lists it.","format":"uuid","nullable":true,"example":"5b1c7d90-2a3e-4f6b-8c9d-0e1f2a3b4c5d"},"portionName":{"type":"string","description":"The portion's name as it read when the order was placed.","nullable":true,"example":"Normal"},"quantity":{"type":"integer","description":"How many of it.","format":"int32","example":2},"unitPrice":{"type":"number","description":"The price of one unit, extras included, as it was when the order was placed.","format":"double","example":180},"total":{"type":"number","description":"What the line comes to: `unitPrice × quantity`, before campaigns. 0 for a cancelled line and for one\nthe venue gave away.","format":"double","example":360},"status":{"enum":["Pending","Preparing","Ready","Served","Cancelled"],"type":"string","description":"Where the line has got to in the kitchen.","example":"Preparing"},"extras":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderExtraDto"},"description":"The extras on each unit."},"removedIngredients":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderRemovedIngredientDto"},"description":"The ingredients left out of each unit."},"note":{"type":"string","description":"The note for the kitchen about this line. Null when there was none.","nullable":true,"example":"Well done."},"orderedAt":{"type":"string","description":"When the line was put on the order, in UTC.","format":"date-time","example":"2026-10-01T09:30:00Z"}},"description":"One line of an order: a product in one portion with its extras and removed ingredients."},"PublicOrderLineRequest":{"required":["productId","portionId","quantity"],"type":"object","properties":{"productId":{"type":"string","description":"The product, one of the ids `GET /venues/{venueId}/menu` lists. Required.","format":"uuid","example":"0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60"},"portionId":{"type":"string","description":"The portion of that product. Required. A portion the venue sells by weight (`orderable: false` in the\nmenu) cannot be ordered.","format":"uuid","example":"5b1c7d90-2a3e-4f6b-8c9d-0e1f2a3b4c5d"},"quantity":{"type":"integer","description":"How many of it, 1 to 20.","format":"int32","example":2},"note":{"type":"string","description":"A note for the kitchen about this line, at most 200 characters.","nullable":true,"example":"Well done, no onions on the side."},"extraIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The extras chosen for each unit of this line — ids from the product's `extras` in the menu, at most 20,\neach at most once. An extra that is not the product's own, or a choice that breaks the product's option\ngroups (two sugar levels on one coffee, none where one is required), is refused."},"removedIngredientIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The ingredients left out of each unit of this line — ids from the product's `removableIngredients` in the\nmenu, at most 20, each at most once."}},"description":"One product of an order, in one portion, with the extras and removed ingredients chosen for it."},"PublicOrderListDto":{"required":["items","nextCursor"],"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderDto"},"description":"The page's orders. At most `pageSize` of them; empty past the last page."},"nextCursor":{"type":"integer","description":"The `cursor` to send for the next page, or null when this is the last. A cursor is a page number,\ncounted from 1.","format":"int32","nullable":true,"example":2}},"description":"One page of the orders placed through the API, newest first."},"PublicOrderListDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicOrderListDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicOrderPlacementDto":{"required":["order","addedLineIds"],"type":"object","properties":{"order":{"allOf":[{"$ref":"#/components/schemas/PublicOrderDto"}],"description":"The order. A dine-in round that joined a bill that was already open comes back as that whole bill, with the\nlines other people ordered on it."},"addedLineIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The ids of the lines this call put on the order, among `order.lines`. In the `201` answer they are\nexactly the lines this call wrote, so keep them. When the key is repeated (`200`), the order does not say\nwhich request wrote which line, so they are a best-effort reconstruction: the lines put on the order within 15\nseconds before the first call was recorded. On a dine-in order that joined a bill other people were adding to,\nthat can include a line somebody else added in that window."}},"description":"The answer to placing an order: the order as the venue has it, and which of its lines this call wrote (exactly in\nthe first answer, approximately in a retry's: see `addedLineIds`). Sent with 201 the first time an\n`Idempotency-Key` is used and with 200 when the key is repeated."},"PublicOrderPlacementDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicOrderPlacementDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicOrderPreviewDto":{"required":["lines","subtotal","discount","serviceFee","total","currency"],"type":"object","properties":{"lines":{"type":"array","items":{"$ref":"#/components/schemas/PublicOrderPreviewLineDto"},"description":"One entry for each line of the request, in the same order."},"subtotal":{"type":"number","description":"The lines at menu price with their extras, before campaigns.","format":"double","example":360},"discount":{"type":"number","description":"What the venue's campaigns take off. 0 when none apply.","format":"double","example":36},"serviceFee":{"type":"number","description":"The venue's service fee, charged on the food after campaigns. 0 when the venue charges none.","format":"double","example":32.4},"total":{"type":"number","description":"What the order would come to: `subtotal - discount + serviceFee`.","format":"double","example":356.4},"currency":{"type":"string","description":"The currency of every amount. Always `TRY`.","example":"TRY"}},"description":"A basket priced by the server before it is ordered: the lines at menu price with their extras, what the venue's\ncampaigns take off, the service fee and the total. Placing the same basket charges exactly this total."},"PublicOrderPreviewDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicOrderPreviewDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicOrderPreviewLineDto":{"required":["productId","portionId","quantity","unitPrice","lineTotal"],"type":"object","properties":{"productId":{"type":"string","description":"The product, as the request named it.","format":"uuid","example":"0f8e2c1a-7d44-4b0e-9d2a-5c3e7b1f9a60"},"portionId":{"type":"string","description":"The portion, as the request named it.","format":"uuid","example":"5b1c7d90-2a3e-4f6b-8c9d-0e1f2a3b4c5d"},"quantity":{"type":"integer","description":"How many of it.","format":"int32","example":2},"unitPrice":{"type":"number","description":"The price of one unit: the portion plus the extras chosen for it.","format":"double","example":180},"lineTotal":{"type":"number","description":"`unitPrice × quantity`, before campaigns.","format":"double","example":360}},"description":"One line of a priced basket."},"PublicOrderRemovedIngredientDto":{"required":["id","name"],"type":"object","properties":{"id":{"type":"string","description":"The ingredient, as the menu lists it.","format":"uuid","example":"d4e5f6a7-1b2c-4d3e-8f9a-6b5c4d3e2f1a"},"name":{"type":"string","description":"The ingredient's name.","example":"Onion"}},"description":"An ingredient left out of a line."},"PublicOrderTableDto":{"required":["id","name"],"type":"object","properties":{"id":{"type":"string","description":"The table's id, as `GET /venues/{venueId}/tables` lists it.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"name":{"type":"string","description":"The table's name.","example":"M5"}},"description":"The table a dine-in order is on."},"PublicOrderTotalsDto":{"required":["subtotal","discount","serviceFee","total","paid","remaining"],"type":"object","properties":{"subtotal":{"type":"number","description":"The lines at menu price with their extras, before campaigns. Cancelled lines are not counted.","format":"double","example":360},"discount":{"type":"number","description":"What the venue's campaigns and discounts take off.","format":"double","example":36},"serviceFee":{"type":"number","description":"The venue's service fee, charged on the food after campaigns. 0 when the venue charges none.","format":"double","example":32.4},"total":{"type":"number","description":"What the order comes to.","format":"double","example":356.4},"paid":{"type":"number","description":"How much of it has been paid at the venue.","format":"double","example":0},"remaining":{"type":"number","description":"How much is still owed. 0 once the bill is paid.","format":"double","example":356.4}},"description":"What an order comes to. `subtotal - discount + serviceFee = total`."},"PublicReservationAvailabilityDto":{"required":["venueId","reservationsEnabled","confirmsAutomatically","timeZone","maxGuests","minNoticeMinutes","lastBookableDate","days"],"type":"object","properties":{"venueId":{"type":"string","description":"The venue.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"reservationsEnabled":{"type":"boolean","description":"Whether the venue takes bookings at all. When it is false, `days` is empty and a booking is refused with\n`RESERVATIONS_DISABLED`.","example":true},"confirmsAutomatically":{"type":"boolean","description":"Whether the venue confirms a booking as it is made. When it is false a new booking is `Pending` until the\nvenue answers it.","example":false},"timeZone":{"type":"string","description":"The IANA name of the venue's time zone: whose clock each slot's `time` is on.","example":"Europe/Istanbul"},"maxGuests":{"type":"integer","description":"The largest party that can be booked through the API. A larger one calls the venue.","format":"int32","example":20},"minNoticeMinutes":{"type":"integer","description":"How many minutes ahead a booking has to be made, at the least.","format":"int32","example":30},"lastBookableDate":{"type":"string","description":"The last date, on the venue's calendar, a table can be booked for.","format":"date","example":"2026-11-30"},"days":{"type":"array","items":{"$ref":"#/components/schemas/PublicReservationDayDto"},"description":"The days asked for, in order. Empty when the venue takes no bookings."}},"description":"The times a venue can be booked at, day by day, and the rules a booking is held to. The same calendar the venue's\nown booking page on cibusy.com shows, so a time listed here is a time `POST …/reservations` accepts."},"PublicReservationAvailabilityDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicReservationAvailabilityDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicReservationCustomerDto":{"required":["name","phone"],"type":"object","properties":{"name":{"type":"string","description":"The guest's name.","nullable":true,"example":"Ayşe Yılmaz"},"phone":{"type":"string","description":"The guest's phone number, in international form.","nullable":true,"example":"+905321112233"}},"description":"Who a reservation is for, as the venue has it."},"PublicReservationCustomerRequest":{"type":"object","properties":{"name":{"type":"string","description":"The guest's name, at most 100 characters. Required.","nullable":true,"example":"Ayşe Yılmaz"},"phone":{"type":"string","description":"The guest's phone number, in international form or the way it is written locally (`+905321112233`,\n`0532 111 22 33`). At most 30 characters. Required: it is how the venue reaches the guest, and what tells\na repeated request from a second booking.","nullable":true,"example":"+905321112233"}},"description":"The guest a reservation is for."},"PublicReservationDayDto":{"required":["date","state","slots"],"type":"object","properties":{"date":{"type":"string","description":"The date, on the venue's calendar. A venue that seats past midnight lists those late times under the evening\nthey belong to.","format":"date","example":"2026-10-09"},"state":{"enum":["Open","Closed","HoursUnknown"],"type":"string","description":"Whether tables can be booked on this day.","example":"Open"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/PublicReservationSlotDto"},"description":"The times still bookable, earliest first. Empty unless the day is `Open`."}},"description":"One day of a venue's booking calendar."},"PublicReservationDto":{"required":["id","venueId","status","startsAt","guests","customer","note","code","pageUrl","canCancel","checkedInAt","cancelledAt","cancelledBy","cancellationReason","createdAt"],"type":"object","properties":{"id":{"type":"string","description":"The reservation's id. Use it with `GET /reservations/{reservationId}`.","format":"uuid","example":"9b2f6c1e-3d4a-4f5b-8c7d-1e2f3a4b5c6d"},"venueId":{"type":"string","description":"The venue the table is booked at.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"status":{"enum":["Pending","Confirmed","Declined","Cancelled","Seated","Completed","NoShow"],"type":"string","description":"Where the reservation has got to. See Cibusy.Application.PublicApi.Reservations.DTOs.PublicReservationStatus.","example":"Confirmed"},"startsAt":{"type":"string","description":"When the table is for, as an instant in UTC.","format":"date-time","example":"2026-10-09T17:00:00.000Z"},"guests":{"type":"integer","description":"How many people are coming.","format":"int32","example":4},"customer":{"allOf":[{"$ref":"#/components/schemas/PublicReservationCustomerDto"}],"description":"Who the table is for, as the venue has it."},"note":{"type":"string","description":"The note the reservation was made with. Null when there was none.","nullable":true,"example":"A table by the window, if there is one."},"code":{"type":"string","description":"The code the venue checks the guest in with at the door: eight characters the guest can read out, or show as\nthe QR on `pageUrl`. Whoever holds it can see and cancel the booking, so give it to the guest and to\nnobody else.","nullable":true,"example":"K7M2Q9XA"},"pageUrl":{"type":"string","description":"The reservation's own page on cibusy.com, which shows the booking and, once it is confirmed, the QR the venue\nscans at the door. Link to it or send it to the guest. Null for a venue with no public address.","nullable":true,"example":"https://cibusy.com/kavaklidere/reservation/K7M2Q9XA"},"canCancel":{"type":"boolean","description":"Whether the reservation can still be cancelled through the API: it stands, the guest has not been checked in\nand its time has not come.","example":true},"checkedInAt":{"type":"string","description":"When the guest was checked in at the door. Null until then.","format":"date-time","nullable":true,"example":null},"cancelledAt":{"type":"string","description":"When the reservation was called off. Null unless `status` is `Cancelled`.","format":"date-time","nullable":true,"example":null},"cancelledBy":{"enum":["Customer","Venue","System"],"type":"string","description":"Who called it off. Null unless `status` is `Cancelled`.","nullable":true,"example":null},"cancellationReason":{"type":"string","description":"What was given as the reason for calling it off, in the words of whoever gave it. Null unless `status` is\n`Cancelled`.","nullable":true,"example":null},"createdAt":{"type":"string","description":"When the reservation was made.","format":"date-time","example":"2026-10-01T09:30:00.000Z"}},"description":"A reservation taken through the API, as the venue has it right now. The same body\n`GET /reservations/{reservationId}` returns and the webhook's `reservation.updated` event carries."},"PublicReservationDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicReservationDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicReservationListDto":{"required":["items","nextCursor"],"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicReservationDto"},"description":"The reservations on this page."},"nextCursor":{"type":"integer","description":"The page to ask for next, as `cursor`. Null on the last page.","format":"int32","nullable":true,"example":2}},"description":"One page of the reservations taken through the API."},"PublicReservationListDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicReservationListDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicReservationSlotDto":{"required":["startsAt","time"],"type":"object","properties":{"startsAt":{"type":"string","description":"The time as an instant in UTC: what to send as `startsAt`.","format":"date-time","example":"2026-10-09T17:00:00.000Z"},"time":{"type":"string","description":"The same time on the venue's own clock, `HH:mm`: what to show the guest.","example":"20:00"}},"description":"A time a table can be booked for."},"PublicTableAreaDto":{"required":["id","name","displayOrder","tables"],"type":"object","properties":{"id":{"type":"string","description":"The area's id.","format":"uuid","example":"9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d"},"name":{"type":"string","description":"The area's name.","example":"Bahçe"},"displayOrder":{"type":"integer","description":"The area's position among the venue's areas, lowest first. The areas come already in this order.","format":"int32","example":1},"tables":{"type":"array","items":{"$ref":"#/components/schemas/PublicTableDto"},"description":"The area's tables, in the order the venue's till shows them: the ones the venue has arranged first, then\nthe rest by name with the numbers read as numbers (T2 before T10). Empty for an area with no tables yet."}},"description":"A floor, terrace or room of a venue, and the tables on it. A dine-in order names one of the tables."},"PublicTableAreaDtoListApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicTableAreaDto"},"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicTableDto":{"required":["id","name","code","capacity"],"type":"object","properties":{"id":{"type":"string","description":"The table's id, the one a dine-in order names.","format":"uuid","example":"2f4e6a8c-0b1d-4c3e-9f5a-7b9d1e3c5a70"},"name":{"type":"string","description":"The table's name as it is written on the till.","example":"B12"},"code":{"type":"string","description":"The code printed on the table's QR card. Null for a table that has none.","nullable":true,"example":"B12"},"capacity":{"type":"integer","description":"How many people the table seats. Null when the venue has not said.","format":"int32","nullable":true,"example":4}},"description":"A table."},"PublicVenueAddressDto":{"required":["line","district","city","country"],"type":"object","properties":{"line":{"type":"string","description":"The street address the venue entered. Null when it entered none.","nullable":true,"example":"Caferağa Mah. Moda Cad. No:42/A"},"district":{"type":"string","description":"The district's name. Null when the venue's district is no longer on file.","nullable":true,"example":"Kadıköy"},"city":{"type":"string","description":"The city's name. Null when the venue's city is no longer on file.","nullable":true,"example":"İstanbul"},"country":{"type":"string","description":"The country's name. Null when the venue's country is no longer on file.","nullable":true,"example":"Türkiye"}},"description":"A venue's address."},"PublicVenueDto":{"required":["id","name","username","type","parentVenueId","logoUrl","address","location","timeZone","workingHours","ordering","languages","currency"],"type":"object","properties":{"id":{"type":"string","description":"The venue's id. Every other call that is about one venue takes it in the path.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"name":{"type":"string","description":"The venue's name as its guests know it.","example":"Moda Kebap Evi"},"username":{"type":"string","description":"The username of the venue's page on Cibusy, the one in its menu link (`cibusy.com/modakebap`). Null when\nthe venue has not picked one.","nullable":true,"example":"modakebap"},"type":{"enum":["Independent","Headquarter","Branch"],"type":"string","description":"Whether the venue is independent, a headquarter or a branch.","example":"Branch"},"parentVenueId":{"type":"string","description":"The id of the headquarter a branch belongs to. Null for every venue that is not a branch. A key created for a\nbranch cannot read its headquarter, so the id is for telling the venues of one chain apart, not for a call.","format":"uuid","nullable":true,"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"},"logoUrl":{"type":"string","description":"The address of the venue's logo, an image. Null when the venue has not uploaded one.","nullable":true,"example":"https://storage.googleapis.com/cibusy/places/logos/3fa85f64-5717-4562-b3fc-2c963f66afa6.webp?v=1759312000"},"address":{"allOf":[{"$ref":"#/components/schemas/PublicVenueAddressDto"}],"description":"Where the venue is, in words."},"location":{"allOf":[{"$ref":"#/components/schemas/PublicVenueLocationDto"}],"description":"Where the venue is on the map. Null when the venue has not entered its coordinates.","nullable":true},"timeZone":{"type":"string","description":"The venue's time zone as an IANA identifier. The opening hours, `ordering.opensAt` and\n`ordering.closesAt` are clock times in this zone.","example":"Europe/Istanbul"},"workingHours":{"type":"array","items":{"$ref":"#/components/schemas/PublicWorkingHoursDto"},"description":"The opening hours the venue has entered, Monday first. A day the venue has not entered is not listed, and\nthe venue is then not judged against the clock on that day."},"ordering":{"allOf":[{"$ref":"#/components/schemas/PublicVenueOrderingDto"}],"description":"Whether the venue would take an order right now, and the clock behind the answer. Worked out on every call."},"languages":{"type":"array","items":{"$ref":"#/components/schemas/PublicVenueLanguageDto"},"description":"The languages the venue's menu is offered in, its default first. The menu endpoint accepts any of their\ncodes as `lang`. Empty for a venue that has set none up, whose menu is then only available as written."},"currency":{"type":"string","description":"The currency of every price, an ISO 4217 code. Always `TRY`; prices include VAT.","example":"TRY"}},"description":"Everything a venue's own website needs to introduce the venue and to know whether it is taking orders: the\nsummary plus the venue's address, hours, languages and ordering state."},"PublicVenueDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicVenueDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicVenueLanguageDto":{"required":["code","name","isDefault"],"type":"object","properties":{"code":{"type":"string","description":"The language's two-letter ISO 639-1 code, the value the menu endpoint's `lang` takes.","example":"tr"},"name":{"type":"string","description":"The language's name, for a language picker.","example":"Türkçe"},"isDefault":{"type":"boolean","description":"True for the language the menu is returned in when no `lang` is asked for.","example":true}},"description":"A language a venue's menu is offered in."},"PublicVenueLocationDto":{"required":["latitude","longitude"],"type":"object","properties":{"latitude":{"type":"number","description":"Degrees north of the equator, between -90 and 90.","format":"double","example":40.9876},"longitude":{"type":"number","description":"Degrees east of the prime meridian, between -180 and 180.","format":"double","example":29.0261}},"description":"A point on the map."},"PublicVenueOrderingDto":{"required":["acceptingOrdersNow","opensAt","closesAt","busyUntil","busyReason"],"type":"object","properties":{"acceptingOrdersNow":{"type":"boolean","description":"True when an order placed right now would be accepted by the venue's own rules: its subscription is active,\nit has not declared a rush, and it is inside its opening hours, with the grace the venue allows either side\n(the rule can be switched off, and a day the venue has not entered is never judged). When it is false,\n`busyUntil` tells a rush from a closed venue.","example":true},"opensAt":{"type":"string","description":"Today's opening time, `HH:mm` on the venue's clock, when the venue enforces its hours and has entered\nsome for today. Null otherwise, and always null while the venue is busy.","nullable":true,"example":"11:00"},"closesAt":{"type":"string","description":"Today's closing time, `HH:mm` on the venue's clock. Earlier than `opensAt` when the venue trades\npast midnight. Null under the same conditions as `opensAt`.","nullable":true,"example":"23:30"},"busyUntil":{"type":"string","description":"The moment, in UTC, a rush the venue declared ends. Not null means the venue is open but its kitchen has\nstopped taking orders until then, so a page can count down to it rather than say \"closed\". Null when the\nvenue is not in a rush.","format":"date-time","nullable":true,"example":"2026-10-01T18:45:00.000Z"},"busyReason":{"type":"string","description":"What the venue wrote about its rush, a sentence it meant for its guests. Null when there is no rush or the\nvenue gave no reason.","nullable":true,"example":"Mutfak yoğun, siparişler 30 dakika gecikebilir."}},"description":"Whether a venue is taking orders, and why not when it is not."},"PublicVenueSummaryDto":{"required":["id","name","username","type","parentVenueId"],"type":"object","properties":{"id":{"type":"string","description":"The venue's id. Every other call that is about one venue takes it in the path.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"name":{"type":"string","description":"The venue's name as its guests know it.","example":"Moda Kebap Evi"},"username":{"type":"string","description":"The username of the venue's page on Cibusy, the one in its menu link (`cibusy.com/modakebap`). Null when\nthe venue has not picked one.","nullable":true,"example":"modakebap"},"type":{"enum":["Independent","Headquarter","Branch"],"type":"string","description":"Whether the venue is independent, a headquarter or a branch.","example":"Branch"},"parentVenueId":{"type":"string","description":"The id of the headquarter a branch belongs to. Null for every venue that is not a branch. A key created for a\nbranch cannot read its headquarter, so the id is for telling the venues of one chain apart, not for a call.","format":"uuid","nullable":true,"example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"}},"description":"A venue the API key can read, in the short form the list of venues uses."},"PublicVenueSummaryDtoListApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicVenueSummaryDto"},"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicWebhookTestEventDataDto":{"required":["venueId","keyPrefix","message"],"type":"object","properties":{"venueId":{"type":"string","description":"The venue the API key belongs to.","format":"uuid","example":"3fa85f64-5717-4562-b3fc-2c963f66afa6"},"keyPrefix":{"type":"string","description":"The first characters of the API key that sent the test, as the venue's panel lists it. Not the key.","example":"cbk_a1B2c3D4"},"message":{"type":"string","description":"A line saying what this is.","example":"This is a test event from Cibusy. No order has changed."}},"description":"The `data` of a `webhook.test` event."},"PublicWebhookTestResultDto":{"required":["delivered","statusCode","error","durationMs"],"type":"object","properties":{"delivered":{"type":"boolean","description":"Whether the endpoint accepted the event: it answered with a 2xx status within the time allowed. When it is\nfalse, `statusCode` or `error` says why.","example":true},"statusCode":{"type":"integer","description":"The HTTP status the endpoint answered with. Null when it did not answer at all — it was unreachable or too slow.","format":"int32","nullable":true,"example":200},"error":{"type":"string","description":"Why the delivery failed, in a short sentence for a developer. Null when it was delivered. It never carries the\nsigning secret, the signature or the address's query string.","nullable":true,"example":"The endpoint answered HTTP 500."},"durationMs":{"type":"integer","description":"How long the attempt took, in milliseconds.","format":"int32","example":182}},"description":"What came of the test event `POST /webhooks/test` sent."},"PublicWebhookTestResultDtoApiResponse":{"required":["success","timestamp","traceId","data","message"],"type":"object","properties":{"success":{"type":"boolean","description":"Indicates if the operation was successful"},"timestamp":{"type":"string","description":"Response timestamp","format":"date-time"},"traceId":{"type":"string","description":"Trace ID for request tracking","nullable":true},"data":{"allOf":[{"$ref":"#/components/schemas/PublicWebhookTestResultDto"}],"description":"Response data"},"message":{"type":"string","description":"Success message","nullable":true}},"description":"Generic API response model with data"},"PublicWorkingHoursDto":{"required":["day","opensAt","closesAt","isClosed"],"type":"object","properties":{"day":{"enum":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"type":"string","description":"The day of the week.","example":"Monday"},"opensAt":{"type":"string","description":"The time the venue opens, `HH:mm` on the venue's clock. Null when the venue is closed that day.","nullable":true,"example":"11:00"},"closesAt":{"type":"string","description":"The time the venue closes, `HH:mm` on the venue's clock. Earlier than `opensAt` when the venue\ntrades past midnight, and then it is the morning after. Null when the venue is closed that day.","nullable":true,"example":"23:30"},"isClosed":{"type":"boolean","description":"True when the venue is shut that whole day.","example":false}},"description":"One day of a venue's opening hours."},"RateLimitErrorResponse":{"required":["success","userMessage","developerMessage","errorCode","retryAfter","timestamp","traceId"],"type":"object","properties":{"success":{"type":"boolean","description":"Always `false`."},"userMessage":{"type":"string","description":"A sentence for the end user, in the language of the request."},"developerMessage":{"type":"string","description":"For you, in English. Its wording may change; do not parse it."},"errorCode":{"type":"string","description":"Always `RATE_LIMIT_EXCEEDED`.","example":"RATE_LIMIT_EXCEEDED"},"retryAfter":{"type":"integer","description":"Seconds to wait. The same as the `Retry-After` header.","format":"int32","example":60},"timestamp":{"type":"string","description":"When the answer was written, in UTC.","format":"date-time"},"traceId":{"type":"string","description":"Identifies the request in Cibusy's logs. Quote it when you write to support."}},"description":"The answer to a call over the key's budget. It is written by the rate limiter, which gives the fields of the usual error body in a form of its own: there are no `validationErrors` or `details`, and `retryAfter` says how long to wait."},"ReservationUpdatedWebhookEvent":{"required":["id","type","createdAt","data"],"type":"object","properties":{"id":{"type":"string","description":"The event's id, `evt_` and 32 lowercase hex characters. An `order.updated` or\n`reservation.updated` event has the same id every time the same change of the same order or reservation is\ndelivered again — after a failed attempt, say — so a receiver can recognise a repeat and ignore it. A change is\nthe step from the state the webhook was last told about to the state now sent, and every change has an id of its\nown: an order that goes back to a state it was in before is a new change, never a repeat. It differs for each\nkey's webhook too. A `webhook.test` event has a new id every time. Also sent in the\n`X-Cibusy-Event-Id` header.","example":"evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34"},"type":{"enum":["reservation.updated"],"type":"string","description":"The kind of event: `order.updated`, `reservation.updated` or `webhook.test`. Also sent in the `X-Cibusy-Event-Type` header.","example":"reservation.updated"},"createdAt":{"type":"string","description":"When this delivery was prepared, in UTC. A retry of the same event is prepared again, so it can differ between\nattempts; events about one order or reservation can arrive out of order, and this is the way to tell which is\nnewer.","format":"date-time","example":"2026-10-01T09:30:00.000Z"},"data":{"allOf":[{"$ref":"#/components/schemas/PublicReservationDto"}],"description":"The reservation as it was when this delivery was prepared, in the form `GET /reservations/{reservationId}` returns it."}},"description":"The body of a `reservation.updated` delivery: a reservation a key took has changed. `id` is the same every time the same change to the same reservation is delivered again, so it is what to de-duplicate on."},"TestWebhookEvent":{"required":["id","type","createdAt","data"],"type":"object","properties":{"id":{"type":"string","description":"The event's id, `evt_` and 32 lowercase hex characters. An `order.updated` or\n`reservation.updated` event has the same id every time the same change of the same order or reservation is\ndelivered again — after a failed attempt, say — so a receiver can recognise a repeat and ignore it. A change is\nthe step from the state the webhook was last told about to the state now sent, and every change has an id of its\nown: an order that goes back to a state it was in before is a new change, never a repeat. It differs for each\nkey's webhook too. A `webhook.test` event has a new id every time. Also sent in the\n`X-Cibusy-Event-Id` header.","example":"evt_3f2a9c4e8b1d4f6a9e0c7b5d2a1f8e34"},"type":{"enum":["webhook.test"],"type":"string","description":"The kind of event: `order.updated`, `reservation.updated` or `webhook.test`. Also sent in the `X-Cibusy-Event-Type` header.","example":"webhook.test"},"createdAt":{"type":"string","description":"When this delivery was prepared, in UTC. A retry of the same event is prepared again, so it can differ between\nattempts; events about one order or reservation can arrive out of order, and this is the way to tell which is\nnewer.","format":"date-time","example":"2026-10-01T09:30:00.000Z"},"data":{"allOf":[{"$ref":"#/components/schemas/PublicWebhookTestEventDataDto"}],"description":"Whose test this is."}},"description":"The body of a `webhook.test` delivery, which `POST /webhooks/test` sends to check the address and the signature. It is the envelope of every event; no order has changed."},"ValidationError":{"required":["field","message","attemptedValue"],"type":"object","properties":{"field":{"type":"string","description":"Field name that failed validation"},"message":{"type":"string","description":"Validation error message"},"attemptedValue":{"description":"Value that was attempted to be set","nullable":true}},"description":"Validation error model"}},"securitySchemes":{"ApiKey":{"type":"apiKey","description":"The API key made in the venue's panel, sent in the `X-Api-Key` header of every call. Keep it on your server: it is the venue's credential and must never reach a browser or an app.","name":"X-Api-Key","in":"header"}}},"security":[{"ApiKey":[ ]}],"tags":[{"name":"Venues","description":"The venues an API key can read, with their details, menus and tables."},{"name":"Orders","description":"Orders: price a basket, place an order and follow it. Everything here needs a key created with the \"place orders\"\npermission, sent in the `X-Api-Key` header.\n\nThe server prices everything. A request names products, portions, extras and quantities and carries no prices; the\nvenue's menu, campaigns and service fee decide what it costs, and the answer says. Orders go straight to the\nkitchen, where the tickets print, and the venue's staff are told as they are for a QR menu order. Nothing is paid\nthrough the API: the customer pays at the venue, and `paymentMethod` is only a hint about how."},{"name":"Webhooks","description":"Webhooks: how the changes of an order or a reservation reach your server without you asking. Everything here\nneeds a key created with the \"place orders\" or the \"take reservations\" permission, sent in the `X-Api-Key`\nheader.\n\nA webhook belongs to an API key. The venue sets the address and reads the signing secret in its panel, on the key.\nFrom then on, every time an order that key placed (or added a round to) changes, Cibusy sends an\n`order.updated` event to that address. It usually arrives within seconds; when the service is idle it can take\nup to about a minute, because a sweep that runs once a minute is what delivers it then. A change is anything the\norder's JSON shows differently: its `status` or `paymentStatus`, its table, what it comes to or how much is\npaid, whether it is closed, a line's quantity or status, a line added or cancelled. A name or a note corrected on the\nvenue's side is not one. The first event for an order, with the state it was placed in, follows the order in the same\nway.\n\nA reservation that key took is announced the same way, with a `reservation.updated` event: when the venue\nconfirms or declines it, when it is called off by the guest, the venue or for want of an answer, when the guest is\nseated, and when the visit ends or nobody came. A reservation changes because a person decided something, not by\nthe second, so its events are sent by the sweep alone and arrive within about a minute. The first event, with the\nstate the reservation was made in, follows it in the same way."},{"name":"Reservations","description":"Reservations: the times a venue can be booked at, sending in a booking your site took, following it and calling it\noff. Everything here needs a key created with the \"take reservations\" permission, sent in the `X-Api-Key`\nheader.\n\nA booking you send lands where every other one does: on the venue's reservation list at the till and in the staff\napp, with the notification a guest's own booking raises. Whether it is confirmed at once or waits for the venue is\nthe venue's own setting, and the answer says which. **Your site tells the guest**: Cibusy sends no text message\nabout a booking taken through the API. What you need for that comes back in every answer, and on the webhook as\n`reservation.updated` when the venue answers a request, the booking is called off or the guest is seated."}]}