{
  "openapi": "3.1.0",
  "info": {
    "title": "Chappa POS API (for React devs)",
    "version": "2.0.0-but-with-pictures",
    "summary": "The exact same API. Now with rounded corners, bigger fonts, and fewer scary words.",
    "description": "## Hi 👋\n\nThis is the **whole** API. Two blocks. You send JSON, you get JSON back. Nothing re-renders. There is no `useEffect`. Nobody gets hydrated.\n\nIf you can write `fetch()`, you can use this. If you can't write `fetch()` yet — that's fine, a renderer is on the way that draws this whole thing as little colored blocks you drag around. Like Scratch. Like when you were eight and actually shipped something.\n\n## The two things you can do\n\n- 📖 **Look at the menu** → `POST /api/v2/client/getinfo`\n- 🍔 **Order food** → `POST /api/v2/client/order`\n\nThat's the entire framework. Sorry — *API*. Force of habit.\n\n## Logging in\n\nPut your token in a header called `X-API-Token`. A header. The part that isn't the body. You've met them — they live in the Network tab, between `npm install`s.\n\nLooking at the menu is **public**: no token needed. You only need the token to actually **order**, because ordering does something real in the real world.\n\n## What you get back\n\nEverything looks like this:\n\n```\n{ \"v\": 2, \"status\": 0, \"data\": { ...the good stuff... } }\n```\n\n`status` is `0` when it worked and `1` when it didn't. When it's `1` we put what went wrong in `error.msg`, in actual human sentences. No stack-trace archaeology. You're welcome.\n\n## Rate limiting\n\nThere is one. You won't reach it.",
    "contact": { "name": "Chappa POS", "url": "https://ceapa.cool" },
    "x-renderer": {
      "style": "scratch",
      "audience": "react-devs",
      "vibe": "kids-game",
      "theme": {
        "background": "#fff7e6",
        "blockFont": "rounded",
        "palette": {
          "read": "#4c97ff",
          "write": "#ff6680",
          "auth": "#ffab19",
          "ok": "#59c059",
          "oops": "#ff8c1a"
        }
      },
      "instructions": "Draw each endpoint as a draggable Scratch block. The request body snaps in as little input bubbles; the response pops out the other side. That is the entire mental model. You do not need a state manager for a hamburger."
    }
  },
  "servers": [
    { "url": "/", "description": "wherever this file is being served from" }
  ],
  "security": [
    { "ChappaApiToken": [] }
  ],
  "tags": [
    { "name": "Look", "description": "Reading things. Safe. Nothing explodes.", "x-color": "#4c97ff" },
    { "name": "Do", "description": "Doing things. A little more responsibility.", "x-color": "#ff6680" }
  ],
  "paths": {
    "/api/v2/client/getinfo": {
      "post": {
        "tags": ["Look"],
        "operationId": "lookAtTheMenu",
        "summary": "Look at the menu 📖",
        "description": "Ask for the parts of the venue you want — all at once, in **one** request. One. Not one request per component. Not one per `useQuery`. One.\n\nList what you want inside `query`. Want the menu **and** the categories? Send `{ \"query\": { \"items\": {}, \"categories\": {} } }`. Each thing you ask for comes back in `data` under the same name.\n\nThings you can ask for: `info` (the venue), `categories`, `items` (the menu), `tables`, `zones`, `events`. Use `{}` as the value — they're useful with no options at all.\n\nNo loading spinner required. We know you'll add three anyway.",
        "x-block": {
          "color": "#4c97ff",
          "shape": "hat",
          "emoji": "📖",
          "label": "when I want the menu",
          "say": "give me the venue. all of it. one request.",
          "difficulty": "you can do this"
        },
        "security": [
          { "ChappaApiToken": [] },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MenuRequest" },
              "examples": {
                "theUsual": {
                  "summary": "Give me the venue, its categories, and the menu",
                  "value": { "venue": 3, "query": { "info": {}, "categories": {}, "items": {} } }
                },
                "justTheMenu": {
                  "summary": "Honestly I just want the food list",
                  "value": { "venue": 3, "query": { "items": {} } }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Here's your stuff.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Ok" },
                "examples": {
                  "stuff": {
                    "summary": "Each key you asked for, filled in",
                    "value": {
                      "v": 2,
                      "status": 0,
                      "data": {
                        "info": { "name": "Demo Bistro" },
                        "categories": [ { "id": 1, "name": "Burgers" } ],
                        "items": [ { "id": 220, "name": "Classic Burger", "price": 32 } ]
                      },
                      "meta": {}
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Oops"
          }
        }
      }
    },
    "/api/v2/client/order": {
      "post": {
        "tags": ["Do"],
        "operationId": "orderFood",
        "summary": "Order food 🍔",
        "description": "Send a `table` and a list of `items`. Each item is `{ \"item\": <product id>, \"count\": <how many> }`. Add `\"notes\"` if the customer is particular.\n\nThe kitchen finds out **immediately**. You don't wire up a single websocket — we already did that part, in C++, while you were picking a state library.\n\n**Bonus, because we both know your users double-tap buttons:** add any number as `idempotency_key` and retry as much as you like. Same number = same order, no duplicates. It's that easy, and no, you don't need a reducer for it.",
        "x-block": {
          "color": "#ff6680",
          "shape": "stack",
          "emoji": "🍔",
          "label": "order food for table ( )",
          "say": "table plus a list of things. food appears. magic.",
          "difficulty": "still fine, breathe"
        },
        "security": [
          { "ChappaApiToken": [] }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/OrderRequest" },
              "examples": {
                "twoBurgers": {
                  "summary": "Two burgers, one with no onions",
                  "value": {
                    "table": 11,
                    "items": [
                      { "item": 220, "count": 2 },
                      { "item": 104, "count": 1, "notes": "no onions" }
                    ]
                  }
                },
                "retrySafe": {
                  "summary": "Same thing, but safe to retry forever",
                  "value": {
                    "table": 11,
                    "idempotency_key": 7,
                    "items": [
                      { "item": 220, "count": 1 }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order placed. The kitchen already knows.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Ok" },
                "examples": {
                  "placed": {
                    "summary": "Done",
                    "value": { "v": 2, "status": 0, "data": { "placed": true, "id_sep": 1 }, "meta": {} }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Oops"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ChappaApiToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Token",
        "description": "Your venue token. Goes in a header. Looking at the menu doesn't need it; ordering does."
      }
    },
    "responses": {
      "Oops": {
        "description": "You did something wrong, and we'll tell you what — in words.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Oops" },
            "examples": {
              "oops": {
                "summary": "Something was off about an item",
                "value": { "v": 2, "status": 1, "error": { "code": "INVALID_ITEM", "msg": "Format invalid pentru produs sau cantitate." } }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "MenuRequest": {
        "type": "object",
        "required": ["query"],
        "description": "Ask for the parts of the venue you want.",
        "properties": {
          "venue": {
            "type": "integer",
            "description": "Which venue (location id). Ask whoever gave you the token."
          },
          "query": {
            "type": "object",
            "description": "The bits you want. Keys: info, categories, items, tables, zones, events. Each value is {}.",
            "example": { "info": {}, "categories": {}, "items": {} }
          }
        }
      },
      "OrderRequest": {
        "type": "object",
        "required": ["table", "items"],
        "description": "A table, and the things on it.",
        "properties": {
          "table": { "type": "integer", "description": "Table number." },
          "items": {
            "type": "array",
            "minItems": 1,
            "description": "What to order. At least one.",
            "items": { "$ref": "#/components/schemas/OrderItem" }
          },
          "id_sep": {
            "type": "integer",
            "description": "Optional. Which sub-bill. Leave it out and we'll figure it out."
          },
          "idempotency_key": {
            "type": "integer",
            "description": "Optional. Any number. Reuse it to retry safely without ordering twice."
          }
        }
      },
      "OrderItem": {
        "type": "object",
        "required": ["item", "count"],
        "properties": {
          "item": { "type": "integer", "description": "Product id (you get these from the menu)." },
          "count": { "type": "integer", "minimum": 1, "description": "How many. A whole number you can type yourself — no `is-even`, no `is-odd`, no `is-number`, no eleven transitive dependencies. Just a number." },
          "notes": { "type": "string", "description": "Optional. e.g. \"no onions\"." }
        }
      },
      "Ok": {
        "type": "object",
        "required": ["v", "status", "data"],
        "description": "It worked.",
        "properties": {
          "v": { "type": "integer", "const": 2 },
          "status": { "type": "integer", "const": 0, "description": "0 means it worked." },
          "data": { "type": "object", "description": "The good stuff you asked for." },
          "meta": { "type": "object", "description": "Boring extra info you can ignore." }
        }
      },
      "Oops": {
        "type": "object",
        "required": ["v", "status", "error"],
        "description": "It didn't work, and here's why.",
        "properties": {
          "v": { "type": "integer", "const": 2 },
          "status": { "type": "integer", "const": 1, "description": "1 means you messed up." },
          "error": {
            "type": "object",
            "required": ["code", "msg"],
            "properties": {
              "code": { "type": "string", "description": "A short machine-readable label." },
              "msg": { "type": "string", "description": "What went wrong, in a real sentence." }
            }
          }
        }
      }
    }
  }
}
