{
  "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": "/",
      "description": "Same origin as this document"
    }
  ],
  "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.0,
                            "promo_value": 4.0,
                            "promo_percent": 10.0,
                            "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.0, "count": 1, "product_id": 880, "product_type": 2 },
                                    { "option_id": 32, "type": "product", "text_value": "Salata", "price": 3.0, "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.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.0, 6.0],
                            "promo_value": [4.0, 0.0],
                            "promo_percent": [10.0, 0.0],
                            "taxe_aditionale": [[{ "name": "Ambalaj", "price": 1.5 }], []],
                            "nutrition": [
                              { "energy_kj": 2510.0, "energy_kcal": 600.0, "fat": 32.0, "saturated": 12.0, "carbs": 45.0, "sugars": 6.0, "protein": 34.0, "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)." }
            }
          }
        }
      }
    }
  }
}
