{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"Chappa POS API","version":"2.0.0","summary":"Catalog, venue-state and ordering surface of the Chappa POS engine.","description":"## Overview\n\nThe **Chappa POS API** exposes a venue's live menu, floor state and ordering pipeline. Two endpoints cover the entire surface:\n\n- `POST /api/v2/client/getinfo` — a single batched, GraphQL-inspired reader. You ask for a set of *resources* in one round trip and the engine fans them out across its coroutine scheduler, resolving each on a per-event-loop pinned database connection and streaming the combined result into a single pre-sized output arena.\n- `POST /api/v2/client/order` — places an order (optionally with per-item configuration/modifiers) against a table.\n\n## Performance & telemetry (getinfo)\n\nResponses from `getinfo` are serialized directly into one contiguous, pre-reserved buffer — no intermediate document tree, numbers emitted via `to_chars` on a stack buffer. The engine reports execution telemetry in the response headers:\n\n- **`Server-Timing`** — per-resolver wall-clock with sub-millisecond resolution (`auth`, `resolve.<resource>`, `serialize`, `total`), rendered natively by browser devtools.\n- **`X-Chappa-Exec-Ns`** — total handler wall-clock in **nanoseconds**.\n- **`X-Chappa-Arena-Reserved` / `X-Chappa-Arena-Highwater`** — bytes reserved for the output arena vs. bytes actually used.\n\nClients can opt into denser encodings via the `r` render-flags bitfield: bit-packed item state words, a columnar (struct-of-arrays) layout for tabular resources, byte offsets of each resource within the response body, and a fixed arena reservation hint. These are optional — leave `r` unset to read plain JSON.\n\n## Envelope\n\nBoth endpoints share the same envelope. Success is `{ \"v\": 2, \"status\": 0, \"data\": …, \"meta\": { … } }` at HTTP 200; failures use a 4xx/5xx status with `{ \"v\": 2, \"status\": 1, \"error\": { \"code\", \"msg\", … } }`. The body `status` is `0` (success) or `1` (error); the specific category is given by the HTTP status and the `error.code`.\n\n## Authentication\n\nAll calls authenticate with a venue-scoped API token sent in the **`X-API-Token`** header. The token identifies the venue; the table is supplied per request via the `table` field where required.\n\n## Rate limiting\n\nThe gateway admits up to **50 requests per rolling 10 ms window** per source IP — bursts are metered at the same nanosecond resolution the engine reports its own timings in. Exceeding the window returns `429` with `X-RateLimit-Reset-Ns` counting down to the next admission; `Retry-After` is included for RFC 9110 conformance but, at this granularity, is effectively always `0`. In practice no real integration approaches the ceiling.","contact":{"name":"Chappa POS","url":"https://ceapa.cool"}},"servers":[{"url":"https://locatia-ta.ceapa.cool","description":"Înlocuiește cu adresa localului tău (ex: https://constanta.ceapa.cool)"}],"tags":[{"name":"Catalog & State","description":"Batched, multi-resource reads of the venue's menu and live state."},{"name":"Ordering","description":"Order placement against a table, with optional per-item configuration."}],"security":[{"ChappaApiToken":[]}],"paths":{"/api/v2/client/getinfo":{"post":{"tags":["Catalog & State"],"operationId":"getInfoV2","summary":"Batched multi-resource read","description":"Resolve any combination of venue resources in a single round trip. The request body's `query` object selects resources by key; each value is that resource's parameter object (use `{}` when it takes none). Resources are resolved concurrently and the response either fully succeeds or fails as a whole (no partial results).\n\n### Resources\n\n| Key | Auth | Table | Params | Returns |\n|-----|------|-------|--------|---------|\n| `info` | public | — | — | Venue profile (name, socials, contact) and loyalty-wheel state. |\n| `categories` | public | — | — | Menu categories, ordered. |\n| `items` | public | optional¹ | `target_nivel`, `fields` | Menu products: pricing, availability, bit-packed `flags`, modifiers, optional nutrition. |\n| `tables` | public | — | — | Floor-plan tables in client-visible zones. |\n| `zones` | public | — | — | Client-visible zones. |\n| `events` | public | — | — | Venue events. |\n| `featured_items` | public | — | — | Up to 9 best-selling pictured product ids. |\n| `menu_levels` | public | — | — | Behaviour levels exposed as standalone menus. |\n| `menu_valid_for` | token | — | — | Seconds the currently served menu remains valid. |\n| `orders` | token | required | — | Open orders for the table, grouped by product, with extras. |\n| `seps` | token | required | — | Active sub-bill separators for the table. |\n\n¹ `items` always returns a menu. With `target_nivel` it serves that behaviour level (no table-specific discounts). Without it, the level is the authenticated table's level — carrying that table's applied promotions — when a `table` is resolved; otherwise it falls back to the menu-visible (public) level closest to the behaviour-tree root. A venue with no public level is an error.\n\n### Render flags (`r`)\n\nA bitfield (integer, or a string like `\"0x8\"` / `\"0b1000\"`). Bits compose freely:\n\n| Bit | Value | Name | Effect |\n|-----|-------|------|--------|\n| 0 | 1 | `PACKED` | Omit the ergonomic per-item booleans (`available`, `app_visible`, `has_image`); the bit-packed `flags` word is then the sole authoritative state. |\n| 1 | 2 | `NUTR` | Compute and attach `nutrition` + `allergens` per item (recursive recipe walk; opt-in because it is expensive). |\n| 2 | 4 | `OFFSETS` | Add `meta.arena.offsets` — the absolute byte offset of every top-level resource value within the response body you received. |\n| 3 | 8 | `COLUMNAR` | Emit tabular resources as struct-of-arrays `{ \"n\": N, \"cols\": { … } }` instead of array-of-objects. No repeated keys; cache-friendly. Object resources (`info`, `menu_valid_for`) and the nested `orders` tree are unaffected. |\n\n### Item `flags` word\n\nEach item carries a `flags` integer; bits are independent:\n\n| Bit | Value | Meaning |\n|-----|-------|---------|\n| 0 | 1 | Available to order right now (stock + schedule). |\n| 1 | 2 | Visible in the client app. |\n| 2 | 4 | Has an image. |\n| 3 | 8 | Backed by a recipe. |\n| 4 | 16 | Nutrition data is available. |\n| 5 | 32 | A promotion is active right now. |\n| 6 | 64 | Has additional taxes attached. |\n| 7 | 128 | Has modifiers (configurable). |\n| 8 | 256 | Has a minimum-order constraint. |\n\n### Item field projection (`items.fields`)\n\nOptional bitfield (integer or hex/bin string) selecting which item columns to emit; `id` and `flags` are always present. `0` (default) emits everything.\n\n| Bit | Value | Column(s) |\n|-----|-------|-----------|\n| 1 | 2 | `id_categorie` |\n| 2 | 4 | `name` |\n| 3 | 8 | `description` |\n| 4 | 16 | `gramaj` |\n| 5 | 32 | `image_version` |\n| 6 | 64 | `available`, `app_visible` |\n| 7 | 128 | `price` |\n| 8 | 256 | `promo_value`, `promo_percent` |\n| 9 | 512 | `comanda_minima` |\n| 10 | 1024 | `prep_time_mins` |\n| 11 | 2048 | `dynamics` |\n| 12 | 4096 | `taxe_aditionale` |\n| 13 | 8192 | `nutrition`, `allergens` (also requires the `NUTR` render flag) |","security":[{"ChappaApiToken":[]},[]],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInfoRequest"},"examples":{"publicMenuByLevel":{"summary":"Public menu for a behaviour level (no token needed)","value":{"venue":3,"query":{"info":[],"categories":[],"items":{"target_nivel":1}}}},"tableTargetedMenu":{"summary":"Table-targeted menu with applied promotions + live orders","value":{"venue":3,"table":11,"query":{"info":[],"categories":[],"items":[],"orders":[],"seps":[]}}},"columnarWithOffsetsAndNutrition":{"summary":"Columnar + offsets + nutrition, projected item columns, arena hint","value":{"venue":3,"table":11,"query":{"items":{"fields":"0x21C6"},"tables":[],"zones":[]},"r":"0b1110","prealloc":131072}}}}}},"responses":{"200":{"description":"All requested resources resolved successfully.","headers":{"Server-Timing":{"$ref":"#/components/headers/ServerTiming"},"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"},"X-Chappa-Exec-Ns":{"$ref":"#/components/headers/XChappaExecNs"},"X-Chappa-Arena-Reserved":{"$ref":"#/components/headers/XChappaArenaReserved"},"X-Chappa-Arena-Highwater":{"$ref":"#/components/headers/XChappaArenaHighwater"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInfoSuccess"},"examples":{"arrayLayout":{"summary":"Default array-of-objects layout","value":{"v":2,"status":0,"data":{"info":{"local_name":"Ceapa Pub","allow_client_images":true,"allow_client_orders":true,"display_events":true,"instagram_user":"ceapa.pub","facebook_user":"ceapapub","tiktok_user":null,"contact_phone":"+40720000000","wheel_active":true,"wheel_seconds_to_change":4231,"wheel_used":true},"categories":[{"id":1,"name":"Bere"},{"id":6,"name":"Sucuri"},{"id":7,"name":"Mancare"}],"items":[{"id":104,"flags":207,"id_categorie":7,"name":"Burger Vita","description":"200g vita, cheddar, cartofi","gramaj":"350g","image_version":4,"available":true,"app_visible":true,"has_image":true,"price":39,"promo_value":4,"promo_percent":10,"comanda_minima":0,"prep_time_mins":18,"dynamics":{"elements":[{"element_id":12,"name":"Garnitura","min":1,"max":1,"options":[{"option_id":31,"type":"product","text_value":"Cartofi prajiti","price":0,"count":1,"product_id":880,"product_type":2},{"option_id":32,"type":"product","text_value":"Salata","price":3,"count":1,"product_id":881,"product_type":2}]},{"element_id":13,"name":"Observatii","min":0,"max":1,"options":[{"option_id":40,"type":"text","text_value":"Fara ceapa","price":0,"count":1}]}]},"taxe_aditionale":[{"name":"Ambalaj","price":1.5}]}]},"meta":{"schema":"client.getinfo/2","parallelism":3,"resources":["info","categories","items"],"render":{"packed":false,"nutr":false,"offsets":false,"columnar":false},"arena":{"reserved_bytes":23186}}}},"columnarWithOffsets":{"summary":"Columnar layout with byte offsets + nutrition","value":{"v":2,"status":0,"data":{"items":{"n":2,"cols":{"id":[104,220],"flags":[223,5],"name":["Burger Vita","Apa plata"],"price":[39,6],"promo_value":[4,0],"promo_percent":[10,0],"taxe_aditionale":[[{"name":"Ambalaj","price":1.5}],[]],"nutrition":[{"energy_kj":2510,"energy_kcal":600,"fat":32,"saturated":12,"carbs":45,"sugars":6,"protein":34,"salt":2.1},null],"allergens":[[1,7],[]]}},"zones":{"n":2,"cols":{"id":[1,2],"name":["Interior","Terasa"]}}},"meta":{"schema":"client.getinfo/2","parallelism":2,"resources":["items","zones"],"render":{"packed":false,"nutr":true,"offsets":true,"columnar":true},"arena":{"reserved_bytes":131072,"offsets_base":"body","offsets":{"items":26,"zones":412}}}}}}}}},"400":{"description":"Malformed request, unknown resource, missing venue, invalid behaviour level, or a required table was absent.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInfoError"},"examples":{"unknownResource":{"summary":"Unknown resource key","value":{"v":2,"status":1,"error":{"code":"UNKNOWN_RESOURCE","msg":"Resursa necunoscuta: menus","resource":"menus"}}},"tableRequired":{"summary":"Resource needs a table but none was resolved","value":{"v":2,"status":1,"error":{"code":"TABLE_REQUIRED","msg":"Masa necesara pentru 'orders'.","resource":"orders"}}},"venueRequired":{"summary":"No token and no venue in the body","value":{"v":2,"status":1,"error":{"code":"VENUE_REQUIRED","msg":"Lipseste venue-ul (local)."}}},"invalidLevel":{"summary":"Behaviour level not found / invalid","value":{"v":2,"status":1,"error":{"code":"INVALID_LEVEL","msg":"Nivelul de comportament nu a fost gasit pentru acest local.","resource":"items"}}}}}}},"401":{"description":"A resource required authentication but no valid token was presented.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInfoError"},"examples":{"authRequired":{"value":{"v":2,"status":1,"error":{"code":"AUTH_REQUIRED","msg":"Autentificare necesara pentru 'orders'.","resource":"orders"}}}}}}},"403":{"description":"The supplied context contradicts the token (venue/table mismatch) or the table does not belong to the venue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInfoError"},"examples":{"venueMismatch":{"value":{"v":2,"status":1,"error":{"code":"VENUE_MISMATCH","msg":"Venue-ul din context nu corespunde tokenului."}}},"tableNotInVenue":{"value":{"v":2,"status":1,"error":{"code":"TABLE_NOT_IN_VENUE","msg":"Masa nu a fost gasita pentru acest local."}}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Internal error while resolving a resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetInfoError"},"examples":{"internal":{"value":{"v":2,"status":1,"error":{"code":"INTERNAL","msg":"Eroare interna: ..."}}}}}}}}}},"/api/v2/client/order":{"post":{"tags":["Ordering"],"operationId":"addOrderV2","summary":"Place an order against a table","description":"Submit one or more products for a table. Products may carry a per-item `configuration` selecting modifier options (sizes, garnishes, free-text notes) defined by the product's `dynamics` (see the `items` resource).\n\n### Sub-bill (`id_sep`) resolution\n\nIf `id_sep` is omitted, the engine resolves it: when the table has no open sub-bill it uses `1`; when exactly one is open it uses that one; when **several** are open it cannot choose for you and returns a `409` whose `error.code` is `SEP_AMBIGUOUS` and `error.seps` lists the open `id_sep` values — resubmit the same order with one of them in `id_sep`.\n\n### Idempotency (optional)\n\nAttach a numeric `idempotency_key` to make a submission safe to retry. The key uniquely identifies one logical order on your side; reuse the exact same number when you resend after a timeout or network blip. Resubmitting with a key that already placed an order replays the original `200` result — identical body, plus an `X-Idempotent-Replay: 1` header — instead of placing a duplicate. If the original submission is still in flight, the retry gets `409 IDEMPOTENCY_IN_PROGRESS`; back off briefly and retry. Keys are scoped per table, so unrelated tables may reuse the same numbers freely. Omit the key to keep the legacy at-least-once behaviour.\n\n### Response model\n\nUses the same envelope as `getinfo`. Success is `{ \"v\": 2, \"status\": 0, \"data\": { … }, \"meta\": { … } }` at HTTP 200; failures use the matching HTTP status with `{ \"v\": 2, \"status\": 1, \"error\": { \"code\", \"msg\", … } }`. The body `status` is `0` or `1`.\n\nOn success, `data` is `{ \"placed\": true, \"id_sep\": <resolved sub-bill> }`, and the response carries the same telemetry headers as `getinfo` (`Server-Timing`, `X-Chappa-Exec-Ns`, `X-Chappa-Engine`).\n\nThe `error.code` determines the HTTP status:\n\n| `error.code` | HTTP | Meaning |\n|--------------|------|---------|\n| `BAD_REQUEST` | 400 | Body is not valid JSON. |\n| `NO_ITEMS` | 400 | No items supplied. |\n| `INVALID_ITEM` | 400 | An item is missing `item`/`count`, or `count` is not greater than 0. |\n| `INVALID_CONFIGURATION` | 400 | A selected element/option is unknown, or selection counts violate the element's min/max. |\n| `INVALID_IDEMPOTENCY_KEY` | 400 | `idempotency_key` was supplied but is not an integer. |\n| `INVALID_TOKEN` / `AUTH_REQUIRED` / `AUTH_ERROR` | 401 | Missing or invalid authentication. |\n| `PRODUCT_NOT_FOUND` | 404 | A referenced product does not exist. |\n| `TABLE_NOT_FOUND` | 404 | The table does not exist for this venue. |\n| `PRODUCT_UNAVAILABLE` | 409 | The product exists but is not orderable right now. |\n| `SEP_AMBIGUOUS` | 409 | Several sub-bills are open; `error.seps` lists the open `id_sep` values — resubmit with one in `id_sep`. |\n| `TABLE_NOT_ORDERABLE` | 409 | The table is a bar / quick-sell table; these do not accept orders via the API. |\n| `IDEMPOTENCY_IN_PROGRESS` | 409 | A submission carrying the same `idempotency_key` is still being processed; retry shortly. |\n| `INTERNAL` | 500 | Server-side failure (details are logged, not returned). |\n\nThe human, Romanian message is always in `error.msg`.","security":[{"ChappaApiToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddOrderRequest"},"examples":{"simpleItems":{"summary":"Two plain products on the table's default sub-bill","value":{"table":11,"items":[{"item":220,"count":2},{"item":104,"count":1,"notes":"Bine facut"}]}},"configuredItem":{"summary":"A configurable product with modifier selections","value":{"table":11,"id_sep":2,"items":[{"item":104,"count":1,"configuration":{"12":[{"option_id":32,"count":1}],"13":[{"option_id":40,"count":1}]},"notes":"Fara sos"}]}},"idempotentRetry":{"summary":"Retry-safe order carrying an idempotency_key","value":{"table":11,"idempotency_key":1737045600001,"items":[{"item":220,"count":2}]}}}}}},"responses":{"200":{"description":"Order placed (or replayed — see `X-Idempotent-Replay`).","headers":{"Server-Timing":{"$ref":"#/components/headers/ServerTiming"},"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"},"X-Chappa-Exec-Ns":{"$ref":"#/components/headers/XChappaExecNs"},"X-Idempotent-Replay":{"$ref":"#/components/headers/XIdempotentReplay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSuccess"},"examples":{"placed":{"summary":"Order placed","value":{"v":2,"status":0,"data":{"placed":true,"id_sep":2},"meta":{"schema":"client.order/2"}}}}}}},"400":{"description":"Malformed body or validation failure.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderError"},"examples":{"badRequest":{"summary":"Body is not valid JSON","value":{"v":2,"status":1,"error":{"code":"BAD_REQUEST","msg":"JSON invalid sau lipsa."}}},"noItems":{"summary":"No items supplied","value":{"v":2,"status":1,"error":{"code":"NO_ITEMS","msg":"Va rugam selectati minim un produs pentru comanda."}}},"invalidItem":{"summary":"Bad item shape or count","value":{"v":2,"status":1,"error":{"code":"INVALID_ITEM","msg":"Format invalid pentru produs sau cantitate."}}},"invalidConfiguration":{"summary":"Configuration out of allowed range","value":{"v":2,"status":1,"error":{"code":"INVALID_CONFIGURATION","msg":"Numarul de optiuni pentru 'Garnitura' trebuie sa fie intre 1 si 1."}}},"invalidIdempotencyKey":{"summary":"idempotency_key is not an integer","value":{"v":2,"status":1,"error":{"code":"INVALID_IDEMPOTENCY_KEY","msg":"Cheia de idempotenta trebuie sa fie un numar intreg."}}}}}}},"401":{"description":"Missing or invalid authentication.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderError"},"examples":{"invalidToken":{"summary":"Invalid API token","value":{"v":2,"status":1,"error":{"code":"INVALID_TOKEN","msg":"Token API invalid."}}}}}}},"404":{"description":"A referenced product, or the table itself, does not exist.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderError"},"examples":{"productNotFound":{"summary":"Unknown product","value":{"v":2,"status":1,"error":{"code":"PRODUCT_NOT_FOUND","msg":"Produsul nu a fost gasit."}}},"tableNotFound":{"summary":"Unknown table for this venue","value":{"v":2,"status":1,"error":{"code":"TABLE_NOT_FOUND","msg":"Masa nu a fost gasita pentru acest local."}}}}}}},"409":{"description":"The request conflicts with current state: the product is not orderable, the table is a bar / quick-sell table that does not accept API orders, several sub-bills are open and `id_sep` must be chosen, or a submission with the same `idempotency_key` is still in flight.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderError"},"examples":{"productUnavailable":{"summary":"Product not orderable right now","value":{"v":2,"status":1,"error":{"code":"PRODUCT_UNAVAILABLE","msg":"Produsul Burger Vita nu este disponibil momentan."}}},"tableNotOrderable":{"summary":"Bar / quick-sell table — orders via API not allowed","value":{"v":2,"status":1,"error":{"code":"TABLE_NOT_ORDERABLE","msg":"Aceasta masa este un bar si nu accepta comenzi prin aplicatie. Va rugam adresati-va personalului."}}},"sepAmbiguous":{"summary":"Several open sub-bills — choose one and resubmit","value":{"v":2,"status":1,"error":{"code":"SEP_AMBIGUOUS","msg":"Mai multe sub-conturi sunt deschise. Specificati 'id_sep'.","seps":[1,2,4]}}},"idempotencyInProgress":{"summary":"Same idempotency_key still being processed","value":{"v":2,"status":1,"error":{"code":"IDEMPOTENCY_IN_PROGRESS","msg":"O comanda cu aceasta cheie este deja in curs de procesare. Va rugam reincercati in scurt timp."}}}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"description":"Server-side failure. Details are logged, not returned.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderError"},"examples":{"internal":{"summary":"Internal error","value":{"v":2,"status":1,"error":{"code":"INTERNAL","msg":"A aparut o eroare interna. Va rugam incercati din nou."}}}}}}}}}}},"components":{"securitySchemes":{"ChappaApiToken":{"type":"apiKey","in":"header","name":"X-API-Token","description":"Venue-scoped API token. Identifies the venue; the table is provided per request via the `table` field where a resource requires it."}},"headers":{"ServerTiming":{"description":"Per-phase wall-clock in milliseconds (`auth`, `resolve.<resource>` for each resolved resource, `serialize`, `total`). Standard header; rendered natively by browser devtools.","schema":{"type":"string"},"example":"auth;dur=0.023, resolve.items;dur=3.070, resolve.categories;dur=0.180, serialize;dur=0.005, total;dur=3.126"},"XChappaEngine":{"description":"Engine identity / scheduling mode.","schema":{"type":"string"},"example":"chappa-core/2; arena; sched=fanout"},"XChappaExecNs":{"description":"Total handler wall-clock in nanoseconds.","schema":{"type":"integer","format":"int64"},"example":3125900},"XIdempotentReplay":{"description":"Present and set to `1` only when this response is a replay of an earlier order that carried the same `idempotency_key` — no new order was placed. Absent on first placement.","schema":{"type":"string","enum":["1"]},"example":"1"},"XChappaArenaReserved":{"description":"Bytes reserved up front for the single output arena (the larger of the size estimate and the `prealloc` hint).","schema":{"type":"integer","format":"int64"},"example":23186},"XChappaArenaHighwater":{"description":"Bytes actually written to the output arena (final response body length).","schema":{"type":"integer","format":"int64"},"example":22333},"XRateLimitLimit":{"description":"Requests admitted per limiter window, per source IP.","schema":{"type":"integer"},"example":50},"XRateLimitWindowNs":{"description":"Limiter window width in nanoseconds; bursts are metered over this rolling window.","schema":{"type":"integer","format":"int64"},"example":10000000},"XRateLimitResetNs":{"description":"Nanoseconds until the current window rolls and the full allowance is restored.","schema":{"type":"integer","format":"int64"},"example":7421300},"RetryAfter":{"description":"Seconds to wait before retrying (RFC 9110). At a 10 ms window this is effectively always `0`; read `X-RateLimit-Reset-Ns` for the real, sub-millisecond figure.","schema":{"type":"integer"},"example":0}},"responses":{"TooManyRequests":{"description":"Rate limit exceeded — more than 50 requests landed inside a single 10 ms window from this source IP. Back off and retry; `X-RateLimit-Reset-Ns` is the nanosecond countdown to the next admission.","headers":{"X-Chappa-Engine":{"$ref":"#/components/headers/XChappaEngine"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Window-Ns":{"$ref":"#/components/headers/XRateLimitWindowNs"},"X-RateLimit-Reset-Ns":{"$ref":"#/components/headers/XRateLimitResetNs"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"},"examples":{"rateLimited":{"summary":"Burst exceeded the 10 ms window allowance","value":{"v":2,"status":1,"error":{"code":"RATE_LIMITED","msg":"Prea multe cereri. Va rugam reincercati in scurt timp."}}}}}}}},"schemas":{"RenderFlags":{"description":"Render-flags bitfield. Integer, or a string in decimal / `0x` hex / `0b` binary. Bits: 1=PACKED, 2=NUTR, 4=OFFSETS, 8=COLUMNAR.","oneOf":[{"type":"integer","minimum":0},{"type":"string","pattern":"^(0[xX][0-9a-fA-F]+|0[bB][01]+|[0-9]+)$"}],"examples":[14,"0b1110","0x8"]},"FieldMask":{"description":"Item field-projection bitfield. Integer, or a string in decimal / `0x` hex / `0b` binary. `0` (default) emits all columns; `id` and `flags` are always present.","oneOf":[{"type":"integer","minimum":0},{"type":"string","pattern":"^(0[xX][0-9a-fA-F]+|0[bB][01]+|[0-9]+)$"}],"examples":[0,"0x21C6"]},"GetInfoRequest":{"type":"object","required":["query"],"additionalProperties":false,"properties":{"venue":{"type":["integer","null"],"minimum":1,"description":"Venue id. Optional when an `X-API-Token` identifies the venue (it is then derived from the token); required otherwise. If both are present they must match."},"table":{"type":["integer","null"],"minimum":1,"description":"Table id within the venue. Required by resources marked 'table required' (`orders`, `seps`, and the table-targeted `items` menu). Must belong to the venue."},"query":{"$ref":"#/components/schemas/GetInfoQuery"},"r":{"$ref":"#/components/schemas/RenderFlags"},"prealloc":{"type":"integer","minimum":0,"maximum":16777216,"description":"Output-arena reservation floor in bytes (clamped to 16 MiB). The engine reserves `max(size_estimate, prealloc)`."}}},"GetInfoQuery":{"type":"object","minProperties":1,"additionalProperties":false,"description":"Map of requested resources. Each key selects a resource; each value is that resource's parameter object (`{}` for resources that take none).","properties":{"info":{"$ref":"#/components/schemas/EmptyParams"},"categories":{"$ref":"#/components/schemas/EmptyParams"},"items":{"$ref":"#/components/schemas/ItemsParams"},"tables":{"$ref":"#/components/schemas/EmptyParams"},"zones":{"$ref":"#/components/schemas/EmptyParams"},"events":{"$ref":"#/components/schemas/EmptyParams"},"featured_items":{"$ref":"#/components/schemas/EmptyParams"},"menu_levels":{"$ref":"#/components/schemas/EmptyParams"},"menu_valid_for":{"$ref":"#/components/schemas/EmptyParams"},"orders":{"$ref":"#/components/schemas/EmptyParams"},"seps":{"$ref":"#/components/schemas/EmptyParams"}}},"EmptyParams":{"type":"object","additionalProperties":false,"description":"No parameters. Pass an empty object `{}`."},"ItemsParams":{"type":"object","additionalProperties":false,"description":"Parameters for the `items` resource.","properties":{"target_nivel":{"type":"integer","minimum":1,"description":"Behaviour-level id. When present, serves that level's public menu (no table-specific discounts). When absent, the level is the authenticated table's level (with that table's applied promotions) if a `table` is resolved, otherwise the menu-visible (public) level closest to the behaviour-tree root."},"fields":{"$ref":"#/components/schemas/FieldMask"}}},"GetInfoSuccess":{"type":"object","required":["v","status","data","meta"],"additionalProperties":false,"properties":{"v":{"type":"integer","const":2},"status":{"type":"integer","const":0},"data":{"$ref":"#/components/schemas/GetInfoData"},"meta":{"$ref":"#/components/schemas/Meta"}}},"GetInfoData":{"type":"object","additionalProperties":false,"description":"Holds exactly the resources that were requested in `query`. Tabular resources are an array of objects by default, or `{n,cols}` when the `COLUMNAR` render flag is set.","properties":{"info":{"$ref":"#/components/schemas/InfoResult"},"categories":{"$ref":"#/components/schemas/CategoriesResult"},"items":{"$ref":"#/components/schemas/ItemsResult"},"tables":{"$ref":"#/components/schemas/TablesResult"},"zones":{"$ref":"#/components/schemas/ZonesResult"},"events":{"$ref":"#/components/schemas/EventsResult"},"featured_items":{"$ref":"#/components/schemas/FeaturedItemsResult"},"menu_levels":{"$ref":"#/components/schemas/MenuLevelsResult"},"menu_valid_for":{"$ref":"#/components/schemas/MenuValidForResult"},"orders":{"$ref":"#/components/schemas/OrdersResult"},"seps":{"$ref":"#/components/schemas/SepsResult"}}},"Meta":{"type":"object","required":["schema","parallelism","resources","render","arena"],"additionalProperties":false,"properties":{"schema":{"type":"string","const":"client.getinfo/2"},"parallelism":{"type":"integer","description":"Number of resources fanned out concurrently."},"resources":{"type":"array","items":{"type":"string"},"description":"Resolved resource keys, in serialization order."},"render":{"type":"object","required":["packed","nutr","offsets","columnar"],"additionalProperties":false,"properties":{"packed":{"type":"boolean"},"nutr":{"type":"boolean"},"offsets":{"type":"boolean"},"columnar":{"type":"boolean"}}},"arena":{"type":"object","required":["reserved_bytes"],"additionalProperties":false,"properties":{"reserved_bytes":{"type":"integer","format":"int64","description":"Bytes reserved for the output arena."},"offsets_base":{"type":"string","const":"body","description":"Present only with the OFFSETS render flag. Indicates `offsets` are absolute byte positions within the response body."},"offsets":{"type":"object","additionalProperties":{"type":"integer","format":"int64"},"description":"Present only with the OFFSETS render flag. Maps each top-level resource key to the byte offset of its value within the response body."}}}}},"GetInfoError":{"type":"object","required":["v","status","error"],"additionalProperties":false,"properties":{"v":{"type":"integer","const":2},"status":{"type":"integer","const":1},"error":{"type":"object","required":["code","msg"],"additionalProperties":false,"properties":{"code":{"type":"string","description":"Stable machine code.","enum":["BAD_REQUEST","UNKNOWN_RESOURCE","VENUE_REQUIRED","INVALID_LEVEL","TABLE_REQUIRED","AUTH_REQUIRED","VENUE_MISMATCH","TABLE_MISMATCH","TABLE_NOT_IN_VENUE","INVALID_TABLE","INTERNAL"]},"msg":{"type":"string","description":"Human-readable message (Romanian)."},"resource":{"type":"string","description":"The resource key that triggered the error, when applicable."}}}}},"Columnar":{"type":"object","required":["n","cols"],"additionalProperties":false,"description":"Struct-of-arrays encoding of a tabular resource (COLUMNAR render flag). Every array under `cols` has length `n`, aligned by row index. Nested fields become columns of arrays/objects.","properties":{"n":{"type":"integer","description":"Row count."},"cols":{"type":"object","additionalProperties":{"type":"array"},"description":"Column name → array of `n` values."}}},"InfoResult":{"type":"object","description":"Venue profile and loyalty-wheel state.","additionalProperties":false,"properties":{"local_name":{"type":"string"},"allow_client_images":{"type":["boolean","null"]},"allow_client_orders":{"type":["boolean","null"]},"display_events":{"type":["boolean","null"]},"instagram_user":{"type":["string","null"]},"facebook_user":{"type":["string","null"]},"tiktok_user":{"type":["string","null"]},"contact_phone":{"type":["string","null"]},"wheel_active":{"type":"boolean","description":"Whether a loyalty-wheel campaign is active right now."},"wheel_seconds_to_change":{"type":["integer","null"],"description":"Seconds until the wheel's active state next changes, or null if not applicable."},"wheel_used":{"type":"boolean","description":"Whether the venue uses the loyalty wheel at all."}}},"ZoneItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"name":{"type":["string","null"]}}},"ZonesResult":{"description":"Client-visible zones.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/ZoneItem"}},{"$ref":"#/components/schemas/Columnar"}]},"CategoryItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"name":{"type":["string","null"]}}},"CategoriesResult":{"description":"Menu categories.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/CategoryItem"}},{"$ref":"#/components/schemas/Columnar"}]},"TableItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"name":{"type":["string","null"]},"bookable":{"type":["integer","null"]},"capacity":{"type":["integer","null"]},"posX1":{"type":["number","null"]},"posY1":{"type":["number","null"]},"posX2":{"type":["number","null"]},"posY2":{"type":["number","null"]},"id_zona":{"type":["integer","null"]}}},"TablesResult":{"description":"Floor-plan tables located in client-visible zones.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/TableItem"}},{"$ref":"#/components/schemas/Columnar"}]},"EventItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"nume":{"type":["string","null"],"description":"Event name."},"descriere":{"type":["string","null"],"description":"Event description."},"data":{"type":["string","null"],"description":"Event date."},"ora":{"type":["string","null"],"description":"Event time."},"repeating":{"type":["boolean","null"]},"day_bitfield":{"type":["integer","null"],"description":"Bitmask of recurring weekdays."}}},"EventsResult":{"description":"Venue events.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/EventItem"}},{"$ref":"#/components/schemas/Columnar"}]},"FeaturedItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer","description":"Product id."}}},"FeaturedItemsResult":{"description":"Up to 9 best-selling pictured product ids.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/FeaturedItem"}},{"$ref":"#/components/schemas/Columnar"}]},"MenuLevelItem":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"integer"},"name":{"type":["string","null"]}}},"MenuLevelsResult":{"description":"Behaviour levels exposed as standalone menus (usable as `items.target_nivel`).","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/MenuLevelItem"}},{"$ref":"#/components/schemas/Columnar"}]},"MenuValidForResult":{"type":"object","additionalProperties":false,"required":["menu_valid_for"],"properties":{"menu_valid_for":{"type":"integer","description":"Seconds the currently served menu remains valid."}}},"SepItem":{"type":"object","additionalProperties":false,"properties":{"id_sep":{"type":"integer","description":"Sub-bill separator id."}}},"SepsResult":{"description":"Active sub-bill separators for the table.","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/SepItem"}},{"$ref":"#/components/schemas/Columnar"}]},"OrderExtra":{"type":"object","additionalProperties":false,"properties":{"text":{"type":["string","null"],"description":"Extra/modifier label."},"default_price":{"type":"number","description":"Unit price before discounts."},"price":{"type":"number","description":"Group price after discounts."},"count":{"type":"integer"},"selected_status":{"type":"integer","description":"0 = pending/in-progress, 2 = sent/confirmed."}}},"OrderGroup":{"type":"object","additionalProperties":false,"properties":{"name":{"type":["string","null"],"description":"Product name."},"id_produs":{"type":"integer","description":"Product id."},"id_sep":{"type":"integer","description":"Sub-bill separator id."},"default_price":{"type":"number","description":"Unit price before discounts."},"price":{"type":"number","description":"Group price after discounts."},"count":{"type":"integer"},"selected_status":{"type":"integer","description":"0 = pending/in-progress, 2 = sent/confirmed."},"extras":{"type":"array","items":{"$ref":"#/components/schemas/OrderExtra"}}}},"OrdersResult":{"type":"array","description":"Open orders for the table, grouped by product / sub-bill / price / status. Always array-of-objects (the nested `extras` tree is not transposed by COLUMNAR).","items":{"$ref":"#/components/schemas/OrderGroup"}},"Tax":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"price":{"type":"number","description":"Tax value."}}},"Nutrition":{"type":"object","additionalProperties":false,"description":"Aggregated nutrition for one unit (present only with the NUTR render flag, for items backed by a recipe).","properties":{"energy_kj":{"type":"number"},"energy_kcal":{"type":"number"},"fat":{"type":"number"},"saturated":{"type":"number"},"carbs":{"type":"number"},"sugars":{"type":"number"},"protein":{"type":"number"},"salt":{"type":"number"}}},"DynamicOption":{"type":"object","additionalProperties":false,"description":"A selectable modifier option within an element.","required":["option_id","type"],"properties":{"option_id":{"type":"integer"},"type":{"type":"string","enum":["dynamic","product","text"],"description":"`product`/`dynamic` contribute an ingredient (and possibly a priced extra); `text` contributes a free-text note."},"text_value":{"type":"string","description":"Display label (and the note text for `text` options)."},"price":{"type":"number","description":"Surcharge for selecting this option."},"count":{"type":"number","description":"Intrinsic quantity of the option (e.g. 2x)."},"product_id":{"type":"integer","description":"Component product id (for `product`/`dynamic` options)."},"product_type":{"type":"integer","enum":[1,2,3],"description":"1 = internal, 2 = semi-prep, 3 = external."}}},"DynamicElement":{"type":"object","additionalProperties":false,"description":"A group of modifier options with selection bounds.","required":["element_id","options"],"properties":{"element_id":{"type":"integer"},"name":{"type":"string"},"min":{"type":"integer","description":"Minimum number of options that must be selected."},"max":{"type":"integer","description":"Maximum number of options that may be selected."},"options":{"type":"array","items":{"$ref":"#/components/schemas/DynamicOption"}}}},"Dynamics":{"type":"object","additionalProperties":false,"description":"A product's modifier definition. Use this to render configuration UI and to build the `configuration` payload when ordering.","properties":{"elements":{"type":"array","items":{"$ref":"#/components/schemas/DynamicElement"}}}},"ItemEntry":{"type":"object","additionalProperties":false,"description":"A menu product. `id` and `flags` are always present; all other fields are subject to the `items.fields` projection mask, the `PACKED` render flag (suppresses `available`/`app_visible`/`has_image`) and the `NUTR` render flag (gates `nutrition`/`allergens`).","required":["id","flags"],"properties":{"id":{"type":"integer"},"flags":{"type":"integer","description":"Bit-packed item state (see operation description). e.g. 207 = AVAILABLE|APP_VISIBLE|HAS_IMAGE|HAS_RECIPE|HAS_TAX."},"id_categorie":{"type":["integer","null"]},"name":{"type":["string","null"]},"description":{"type":["string","null"]},"gramaj":{"type":["string","null"],"description":"Portion size label."},"image_version":{"type":["integer","null"],"description":"Cache-busting image version."},"available":{"type":"boolean","description":"Orderable now (omitted under PACKED)."},"app_visible":{"type":"boolean","description":"Visible in the client app (omitted under PACKED)."},"has_image":{"type":"boolean","description":"Has an image (omitted under PACKED)."},"price":{"type":["number","null"]},"promo_value":{"type":["number","null"],"description":"Absolute discount applied right now."},"promo_percent":{"type":["number","null"],"description":"Percentage discount applied right now."},"comanda_minima":{"type":["integer","null"],"description":"Minimum order quantity."},"prep_time_mins":{"type":["integer","null"]},"dynamics":{"description":"Modifier definition, or null.","oneOf":[{"$ref":"#/components/schemas/Dynamics"},{"type":"null"}]},"taxe_aditionale":{"type":"array","description":"Additional taxes attached to the product.","items":{"$ref":"#/components/schemas/Tax"}},"nutrition":{"$ref":"#/components/schemas/Nutrition"},"allergens":{"type":"array","items":{"type":"integer"},"description":"Allergen codes (present only with NUTR)."}}},"ItemsResult":{"description":"Menu products. Array-of-objects by default, or `{n,cols}` under the COLUMNAR render flag (with nested `taxe_aditionale`/`nutrition`/`allergens` becoming columns of arrays/objects).","oneOf":[{"type":"array","items":{"$ref":"#/components/schemas/ItemEntry"}},{"$ref":"#/components/schemas/Columnar"}]},"AddOrderRequest":{"type":"object","required":["table","items"],"additionalProperties":false,"properties":{"table":{"type":"integer","minimum":1,"description":"Table id within your venue."},"id_sep":{"type":"integer","minimum":1,"description":"Sub-bill separator. Omit to let the engine resolve it (see operation description); when several are open the engine returns `409 SEP_AMBIGUOUS` with the open values listed in `error.seps`."},"idempotency_key":{"type":"integer","format":"int64","description":"Optional numeric de-duplication key. Pick a value that uniquely identifies this logical order on your side and reuse it when you retry. Resubmitting an order with the same `idempotency_key` for the same table replays the original result (HTTP 200, with `X-Idempotent-Replay: 1`) instead of placing a second order; if the original is still being processed the retry returns `409 IDEMPOTENCY_IN_PROGRESS`. The key is scoped per table, so unrelated tables may reuse the same numbers. Omit it for the legacy at-least-once behaviour."},"items":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/OrderItemInput"}}}},"OrderItemInput":{"type":"object","required":["item","count"],"additionalProperties":false,"properties":{"item":{"type":"integer","minimum":1,"description":"Product id to order."},"count":{"type":"integer","minimum":1,"description":"Quantity (must be > 0)."},"configuration":{"$ref":"#/components/schemas/OrderConfiguration"},"notes":{"type":"string","description":"Free-text note attached to this item (e.g. allergies, preparation). Applies to non-configured items; configured items derive notes from their selected `text` options."}}},"OrderConfiguration":{"type":"object","description":"Modifier selections for a configurable product. Keys are `element_id`s (as strings, matching the product's `dynamics`); each value is the list of selected options for that element.","propertyNames":{"pattern":"^[0-9]+$"},"additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/OrderConfigSelection"}}},"OrderConfigSelection":{"type":"object","required":["option_id","count"],"additionalProperties":false,"properties":{"option_id":{"type":"integer","description":"An `option_id` from the element's `options`."},"count":{"type":"integer","minimum":1,"description":"How many times this option is selected."}}},"OrderSuccess":{"type":"object","required":["v","status","data","meta"],"additionalProperties":false,"description":"Success envelope (HTTP 200) — same shape as `client.getinfo/2`.","properties":{"v":{"type":"integer","const":2},"status":{"type":"integer","const":0},"data":{"type":"object","required":["placed","id_sep"],"additionalProperties":false,"properties":{"placed":{"type":"boolean","const":true},"id_sep":{"type":"integer","description":"The sub-bill the order landed on (the value sent, or the one the engine resolved)."}}},"meta":{"type":"object","required":["schema"],"additionalProperties":false,"properties":{"schema":{"type":"string","const":"client.order/2"}}}}},"OrderError":{"type":"object","required":["v","status","error"],"additionalProperties":false,"description":"Error envelope — same shape as `client.getinfo/2`. The HTTP status carries the category; `error.code` is the stable machine discriminator.","properties":{"v":{"type":"integer","const":2},"status":{"type":"integer","const":1},"error":{"type":"object","required":["code","msg"],"additionalProperties":false,"properties":{"code":{"type":"string","description":"Stable machine code.","enum":["BAD_REQUEST","NO_ITEMS","INVALID_ITEM","INVALID_CONFIGURATION","INVALID_IDEMPOTENCY_KEY","INVALID_TOKEN","AUTH_REQUIRED","AUTH_ERROR","PRODUCT_NOT_FOUND","TABLE_NOT_FOUND","PRODUCT_UNAVAILABLE","TABLE_NOT_ORDERABLE","SEP_AMBIGUOUS","IDEMPOTENCY_IN_PROGRESS","INTERNAL"]},"msg":{"type":"string","description":"Human-readable message (Romanian)."},"seps":{"type":"array","items":{"type":"integer"},"description":"Present only for `SEP_AMBIGUOUS`: the open `id_sep` values to choose from."}}}}},"RateLimitError":{"type":"object","required":["v","status","error"],"additionalProperties":false,"description":"Error envelope returned when the limiter rejects a request (HTTP 429). Same shape as every other error.","properties":{"v":{"type":"integer","const":2},"status":{"type":"integer","const":1},"error":{"type":"object","required":["code","msg"],"additionalProperties":false,"properties":{"code":{"type":"string","const":"RATE_LIMITED"},"msg":{"type":"string","description":"Human-readable message (Romanian)."}}}}}}}}