{
  "openapi": "3.1.0",
  "info": {
    "title": "Quiqqy Mercnet Reports API",
    "version": "2.0.0",
    "summary": "Partner reporting and catalogue management for the Quiqqy merchant network: sales, orders, demand, store health, payments — plus full catalogue CRUD.",
    "description": "**Mercnet** is Quiqqy's merchant network — the stores, catalogues, and order flow behind every Quiqqy storefront and hub. The **Mercnet Reports API** is for **partners building on top of merchant data**: accounting and BI integrations, franchise dashboards, inventory and menu-management tools.\n\nIt has two halves:\n\n- **Reports** — read-only, aggregated, and export-friendly: sales summaries bucketed by day/week/month, a cursor-paginated order export, top items, hourly demand curves, store health, and payments summaries. All figures are computed server-side over settled order data.\n- **Catalogue** — full write access to a store's catalogue: menus, categories, and items (with variants, modifier groups, and per-item tax), plus a fast PATCH for availability and price changes.\n\nThis API deliberately does **not** expose users, merchant settings, or POS integrations — those live in the Online Ordering API and the merchant portal. Everything here is **store-scoped**: pass `store_id` (query parameter on collections, implied by the resource on single items) and your token must be authorized for that store's organization.\n\n## Authentication\n\nAll endpoints require a **partner access token** — a JWT obtained with your app's client credentials through the OAuth 2.0 client flow:\n\n1. Create an app in the Quiqqy developer portal and request the `reports.read` / `catalogue.write` scopes.\n2. Exchange `client_id` / `client_secret` at `POST /oauth2/token` (`grant_type: client_credentials`).\n3. Send `Authorization: Bearer <access_token>`; cache until `expires_in` and refresh.\n\n## Conventions\n\n- All monetary amounts are **integers in minor units** of the store currency (`1299` = $12.99).\n- All fields are `snake_case`.\n- Ids are opaque strings: `st_...` stores, `ord_...` orders, `itm_...` items, `cat_...` categories, `menu_...` menus. `external_id` fields carry the merchant POS ids where present.\n- Date ranges: `date_from` / `date_to` are ISO `YYYY-MM-DD` wall-clock days interpreted in `tz` (IANA zone, default `UTC`).\n- Large result sets use **cursor pagination**: pass the returned `next_cursor` back as `cursor` until `has_more` is `false`.\n- Errors: `{ \"error\": { \"code\": \"reports.range_too_large\", \"message\": \"...\" } }` with stable dot-namespaced codes.\n- Webhooks are signed with `X-Quiqqy-Signature` (HMAC-SHA256 hex of the raw body) and retried on a `1m / 5m / 30m / 2h / 6h` ladder (6 attempts total).",
    "contact": {
      "name": "Quiqqy Developer Platform",
      "url": "https://developers.quiqqy.ai",
      "email": "developers@quiqqy.ai"
    },
    "termsOfService": "https://developers.quiqqy.ai/terms"
  },
  "servers": [
    {
      "url": "https://api.quiqqy.ai/api/v2",
      "description": "Production"
    },
    {
      "url": "https://sandbox.quiqqy.ai/api/v2",
      "description": "Sandbox — seeded stores with synthetic order history"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Reports",
      "description": "Aggregated, read-only reporting over settled order data. All figures are computed server-side."
    },
    {
      "name": "Catalogue Items",
      "description": "Item CRUD, plus a fast PATCH for availability and price."
    },
    {
      "name": "Catalogue Categories",
      "description": "Category CRUD."
    },
    {
      "name": "Catalogue Menus",
      "description": "Menu CRUD — a menu groups categories/items with its own serving hours."
    }
  ],
  "paths": {
    "/reports/sales-summary": {
      "get": {
        "operationId": "getSalesSummary",
        "tags": ["Reports"],
        "summary": "Sales summary",
        "description": "Aggregated sales for a store over a date range, bucketed by `group_by` (`day`, `week`, or `month`). Returns overall totals plus a time series — gross sales, net sales (after discounts and refunds), order count, average order value, discounts, refunds, and tax. Weeks start on Monday in `tz`.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" },
          { "$ref": "#/components/parameters/DateFrom" },
          { "$ref": "#/components/parameters/DateTo" },
          {
            "name": "group_by",
            "in": "query",
            "required": false,
            "description": "Bucket size for the time series.",
            "schema": { "type": "string", "enum": ["day", "week", "month"], "default": "day" }
          },
          { "$ref": "#/components/parameters/Tz" }
        ],
        "responses": {
          "200": {
            "description": "Totals and series for the range.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SalesSummary" },
                "example": {
                  "store_id": "st_9x2ml7qa41ce",
                  "date_from": "2026-07-01",
                  "date_to": "2026-07-28",
                  "group_by": "week",
                  "tz": "America/New_York",
                  "currency": "USD",
                  "totals": {
                    "gross_sales": 1842500,
                    "net_sales": 1719800,
                    "orders": 612,
                    "average_order_value": 3011,
                    "discounts": 84200,
                    "refunds": 38500,
                    "tax": 152800,
                    "tips": 61200,
                    "delivery_fees": 42400
                  },
                  "series": [
                    {
                      "period_start": "2026-06-29",
                      "gross_sales": 448100,
                      "net_sales": 419300,
                      "orders": 149,
                      "average_order_value": 3007,
                      "discounts": 20400,
                      "refunds": 8400,
                      "tax": 37300
                    },
                    {
                      "period_start": "2026-07-06",
                      "gross_sales": 471200,
                      "net_sales": 441800,
                      "orders": 158,
                      "average_order_value": 2982,
                      "discounts": 21100,
                      "refunds": 8300,
                      "tax": 39100
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": {
            "description": "Invalid range or parameters.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": {
                    "code": "reports.range_too_large",
                    "message": "The date range may not exceed 366 days. Narrow date_from/date_to."
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/reports/orders": {
      "get": {
        "operationId": "listOrderRecords",
        "tags": ["Reports"],
        "summary": "Order export (cursor-paginated)",
        "description": "Flat, export-friendly order records for a store — one record per order with its totals, status, channel, fulfilment type, and line items. Designed for syncing into a warehouse or accounting system.\n\nPaginate with the cursor: request the first page without `cursor`, then pass back `next_cursor` until `has_more` is `false`. Records are ordered by `placed_at` ascending so an interrupted export can resume from the last cursor. Cursors are valid for 24 hours.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" },
          { "$ref": "#/components/parameters/DateFrom" },
          { "$ref": "#/components/parameters/DateTo" },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by terminal or current status.",
            "schema": {
              "type": "string",
              "enum": ["pending", "confirmed", "preparing", "ready", "out_for_delivery", "delivered", "cancelled", "failed"]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from the previous page's `next_cursor`.",
            "schema": { "type": "string", "examples": ["cur_eyJwbGFjZWRfYXQiOiIyMDI2LTA3LTE1In0"] }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 }
          },
          { "$ref": "#/components/parameters/Tz" }
        ],
        "responses": {
          "200": {
            "description": "A page of order records.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OrderRecordPage" },
                "example": {
                  "data": [
                    {
                      "order_id": "ord_7d4e2k91mqx0",
                      "order_number": "ORD-20260715-0032",
                      "store_id": "st_9x2ml7qa41ce",
                      "status": "delivered",
                      "fulfillment_type": "delivery",
                      "channel": "storefront",
                      "payment_status": "paid",
                      "payment_method": "card",
                      "currency": "USD",
                      "subtotal": 1849,
                      "tax": 164,
                      "delivery_fee": 200,
                      "discount": 0,
                      "tip": 150,
                      "total_amount": 2363,
                      "placed_at": "2026-07-15T18:22:10.000Z",
                      "completed_at": "2026-07-15T19:03:44.000Z",
                      "items": [
                        {
                          "item_id": "itm_aa11e0b2c9d4",
                          "external_id": "SKU-MARG",
                          "name": "Margherita Pizza (Large)",
                          "quantity": 1,
                          "unit_price": 1699,
                          "subtotal": 1849
                        }
                      ]
                    }
                  ],
                  "next_cursor": "cur_eyJwbGFjZWRfYXQiOiIyMDI2LTA3LTE1VDE4OjIyOjEwWiJ9",
                  "has_more": true
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": {
            "description": "Invalid parameters — including an expired or malformed cursor.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": {
                    "code": "reports.invalid_cursor",
                    "message": "The cursor is expired or malformed. Restart the export without a cursor."
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/reports/top-items": {
      "get": {
        "operationId": "getTopItems",
        "tags": ["Reports"],
        "summary": "Top items",
        "description": "Best-selling items for a store over a date range, ranked by gross sales (or quantity with `rank_by=quantity`). Each row carries the merchant `external_id` where the item was synced from a POS, so you can join back to the source catalogue.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" },
          { "$ref": "#/components/parameters/DateFrom" },
          { "$ref": "#/components/parameters/DateTo" },
          {
            "name": "rank_by",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["gross_sales", "quantity"], "default": "gross_sales" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 10 }
          },
          { "$ref": "#/components/parameters/Tz" }
        ],
        "responses": {
          "200": {
            "description": "Ranked items.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TopItemsReport" },
                "example": {
                  "store_id": "st_9x2ml7qa41ce",
                  "date_from": "2026-07-01",
                  "date_to": "2026-07-28",
                  "currency": "USD",
                  "data": [
                    {
                      "rank": 1,
                      "item_id": "itm_aa11e0b2c9d4",
                      "external_id": "SKU-MARG",
                      "name": "Margherita Pizza",
                      "category": "Pizza",
                      "quantity_sold": 214,
                      "orders": 187,
                      "gross_sales": 311400
                    },
                    {
                      "rank": 2,
                      "item_id": "itm_bb22f1c3d0e5",
                      "external_id": "SKU-PEPPERONI",
                      "name": "Pepperoni Pizza",
                      "category": "Pizza",
                      "quantity_sold": 168,
                      "orders": 150,
                      "gross_sales": 268800
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/reports/hourly-demand": {
      "get": {
        "operationId": "getHourlyDemand",
        "tags": ["Reports"],
        "summary": "Hourly demand",
        "description": "Demand curve by hour of day (0–23, wall-clock in `tz`) over the date range — order count and gross sales per hour, optionally split by weekday with `split_by=weekday`. Use it for staffing, prep planning, and kitchen-capacity throttling.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" },
          { "$ref": "#/components/parameters/DateFrom" },
          { "$ref": "#/components/parameters/DateTo" },
          {
            "name": "split_by",
            "in": "query",
            "required": false,
            "description": "`weekday` returns one row per (weekday, hour) pair instead of aggregating all days.",
            "schema": { "type": "string", "enum": ["none", "weekday"], "default": "none" }
          },
          { "$ref": "#/components/parameters/Tz" }
        ],
        "responses": {
          "200": {
            "description": "Per-hour demand.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HourlyDemandReport" },
                "example": {
                  "store_id": "st_9x2ml7qa41ce",
                  "date_from": "2026-07-01",
                  "date_to": "2026-07-28",
                  "tz": "America/New_York",
                  "currency": "USD",
                  "data": [
                    { "hour": 11, "orders": 42, "gross_sales": 98400 },
                    { "hour": 12, "orders": 96, "gross_sales": 231600 },
                    { "hour": 13, "orders": 71, "gross_sales": 172300 },
                    { "hour": 18, "orders": 88, "gross_sales": 264100 },
                    { "hour": 19, "orders": 104, "gross_sales": 318500 }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/reports/store-health": {
      "get": {
        "operationId": "getStoreHealth",
        "tags": ["Reports"],
        "summary": "Store health",
        "description": "Operational health for one store (with `store_id`) or every store your token can see (omit it): live open state, today's order count, order acceptance rate, average time-to-accept and prep time, cancellation rate with the leading cancellation reason, and the last order timestamp. Use it to surface at-risk locations before merchants notice.",
        "parameters": [
          {
            "name": "store_id",
            "in": "query",
            "required": false,
            "description": "Limit to one store. Omit to list every store the token is authorized for.",
            "schema": { "type": "string", "examples": ["st_9x2ml7qa41ce"] }
          },
          { "$ref": "#/components/parameters/Tz" }
        ],
        "responses": {
          "200": {
            "description": "Health rows, one per store.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StoreHealthReport" },
                "example": {
                  "generated_at": "2026-07-30T14:05:00.000Z",
                  "data": [
                    {
                      "store_id": "st_9x2ml7qa41ce",
                      "name": "Main Street Pizza",
                      "status": "active",
                      "is_open": true,
                      "orders_today": 38,
                      "acceptance_rate": 0.984,
                      "avg_time_to_accept_seconds": 41,
                      "avg_prep_time_minutes": 19,
                      "cancellation_rate": 0.011,
                      "top_cancellation_reason": "ITEM_OUT_OF_STOCK",
                      "last_order_at": "2026-07-30T13:52:18.000Z"
                    },
                    {
                      "store_id": "st_1bd83k2mv7pq",
                      "name": "Main Street Pizza — Brooklyn",
                      "status": "active",
                      "is_open": false,
                      "orders_today": 0,
                      "acceptance_rate": 0.913,
                      "avg_time_to_accept_seconds": 128,
                      "avg_prep_time_minutes": 24,
                      "cancellation_rate": 0.062,
                      "top_cancellation_reason": "POS_OFFLINE",
                      "last_order_at": "2026-07-29T21:40:03.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/reports/payments-summary": {
      "get": {
        "operationId": "getPaymentsSummary",
        "tags": ["Reports"],
        "summary": "Payments summary",
        "description": "Payment totals for a store over a date range, broken down by payment method and provider: transaction count, gross collected, refunded, and net. Reconcile these against your processor's settlement reports — all figures are in minor units.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" },
          { "$ref": "#/components/parameters/DateFrom" },
          { "$ref": "#/components/parameters/DateTo" },
          { "$ref": "#/components/parameters/Tz" }
        ],
        "responses": {
          "200": {
            "description": "Per-method payment totals.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PaymentsSummary" },
                "example": {
                  "store_id": "st_9x2ml7qa41ce",
                  "date_from": "2026-07-01",
                  "date_to": "2026-07-28",
                  "currency": "USD",
                  "totals": {
                    "transactions": 612,
                    "gross_amount": 1842500,
                    "refunded_amount": 38500,
                    "net_amount": 1804000
                  },
                  "data": [
                    {
                      "payment_method": "card",
                      "provider": "stripe",
                      "transactions": 498,
                      "gross_amount": 1567200,
                      "refunded_amount": 35100,
                      "net_amount": 1532100
                    },
                    {
                      "payment_method": "cash",
                      "provider": null,
                      "transactions": 114,
                      "gross_amount": 275300,
                      "refunded_amount": 3400,
                      "net_amount": 271900
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/catalogue/items": {
      "get": {
        "operationId": "listCatalogueItems",
        "tags": ["Catalogue Items"],
        "summary": "List items",
        "description": "Lists a store's catalogue items, cursor-paginated. Filter by category, menu, or availability.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" },
          {
            "name": "category_id",
            "in": "query",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "name": "menu_id",
            "in": "query",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "name": "available",
            "in": "query",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of items.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CatalogueItemPage" },
                "example": {
                  "data": [
                    {
                      "item_id": "itm_aa11e0b2c9d4",
                      "external_id": "SKU-MARG",
                      "store_id": "st_9x2ml7qa41ce",
                      "name": "Margherita Pizza",
                      "description": "Tomato, mozzarella, basil",
                      "price": 1299,
                      "currency": "USD",
                      "image_url": "https://cdn.quiqqy.ai/items/margherita.jpg",
                      "available": true,
                      "snoozed_until": null,
                      "product_type": "product",
                      "category_ids": ["cat_2d3e4f5a6b7c"],
                      "menu_ids": ["menu_1c2d3e4f5a6b"],
                      "variant_type": "stand_alone",
                      "variants": [
                        { "variant_id": "var_3e4f5a6b7c8d", "external_id": "SKU-MARG-SM", "name": "Small", "price": 1299, "available": true },
                        { "variant_id": "var_4f5a6b7c8d9e", "external_id": "SKU-MARG-LG", "name": "Large", "price": 1699, "available": true }
                      ],
                      "tax": { "name": "NYC Sales Tax", "rate": 8.875 },
                      "created_at": "2026-05-14T12:10:00.000Z",
                      "updated_at": "2026-07-29T18:40:12.000Z"
                    }
                  ],
                  "next_cursor": null,
                  "has_more": false
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "post": {
        "operationId": "createCatalogueItem",
        "tags": ["Catalogue Items"],
        "summary": "Create an item",
        "description": "Creates a catalogue item in the store given by the `store_id` query parameter. Supply your own stable `external_id` to make later upserts and order-line mapping deterministic. Items with `product_type: \"addon\"` are modifiers: hidden from storefront listings and only orderable inside a modifier group.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ItemWrite" },
              "example": {
                "external_id": "SKU-CALZONE",
                "name": "Calzone",
                "description": "Folded pizza with ricotta and ham",
                "price": 1149,
                "image_url": "https://cdn.pos.example.com/items/calzone.jpg",
                "available": true,
                "product_type": "product",
                "category_ids": ["cat_2d3e4f5a6b7c"],
                "menu_ids": ["menu_1c2d3e4f5a6b"],
                "tax": { "name": "NYC Sales Tax", "rate": 8.875 }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Item created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Item" },
                "example": {
                  "item_id": "itm_cc33a2d4e1f6",
                  "external_id": "SKU-CALZONE",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Calzone",
                  "description": "Folded pizza with ricotta and ham",
                  "price": 1149,
                  "currency": "USD",
                  "image_url": "https://cdn.pos.example.com/items/calzone.jpg",
                  "available": true,
                  "snoozed_until": null,
                  "product_type": "product",
                  "category_ids": ["cat_2d3e4f5a6b7c"],
                  "menu_ids": ["menu_1c2d3e4f5a6b"],
                  "variant_type": "none",
                  "variants": [],
                  "tax": { "name": "NYC Sales Tax", "rate": 8.875 },
                  "created_at": "2026-07-30T10:12:00.000Z",
                  "updated_at": "2026-07-30T10:12:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "An item with this `external_id` already exists in the store.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": {
                    "code": "catalogue.external_id_conflict",
                    "message": "An item with external_id SKU-CALZONE already exists in store st_9x2ml7qa41ce."
                  }
                }
              }
            }
          },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/catalogue/items/{item_id}": {
      "parameters": [
        { "$ref": "#/components/parameters/ItemIdPath" }
      ],
      "get": {
        "operationId": "getCatalogueItem",
        "tags": ["Catalogue Items"],
        "summary": "Get an item",
        "description": "Returns one catalogue item with full detail: price, availability and snooze state, variants, modifier groups, categories, menus, and tax.",
        "responses": {
          "200": {
            "description": "The item.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Item" },
                "example": {
                  "item_id": "itm_aa11e0b2c9d4",
                  "external_id": "SKU-MARG",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Margherita Pizza",
                  "description": "Tomato, mozzarella, basil",
                  "price": 1299,
                  "currency": "USD",
                  "image_url": "https://cdn.quiqqy.ai/items/margherita.jpg",
                  "available": true,
                  "snoozed_until": null,
                  "product_type": "product",
                  "category_ids": ["cat_2d3e4f5a6b7c"],
                  "menu_ids": ["menu_1c2d3e4f5a6b"],
                  "variant_type": "stand_alone",
                  "variants": [
                    { "variant_id": "var_3e4f5a6b7c8d", "external_id": "SKU-MARG-SM", "name": "Small", "price": 1299, "available": true },
                    { "variant_id": "var_4f5a6b7c8d9e", "external_id": "SKU-MARG-LG", "name": "Large", "price": 1699, "available": true }
                  ],
                  "tax": { "name": "NYC Sales Tax", "rate": 8.875 },
                  "created_at": "2026-05-14T12:10:00.000Z",
                  "updated_at": "2026-07-29T18:40:12.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "put": {
        "operationId": "replaceCatalogueItem",
        "tags": ["Catalogue Items"],
        "summary": "Replace an item",
        "description": "Full replace of an item's editable fields. Omitted optional fields are reset to their defaults; the variant set is replaced exactly (a dropped variant is deactivated). For availability or price alone, prefer the cheaper `PATCH`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ItemWrite" },
              "example": {
                "external_id": "SKU-MARG",
                "name": "Margherita Pizza",
                "description": "San Marzano tomato, fior di latte, basil",
                "price": 1399,
                "available": true,
                "product_type": "product",
                "category_ids": ["cat_2d3e4f5a6b7c"],
                "menu_ids": ["menu_1c2d3e4f5a6b"],
                "variant_type": "stand_alone",
                "variants": [
                  { "external_id": "SKU-MARG-SM", "name": "Small", "price": 1399 },
                  { "external_id": "SKU-MARG-LG", "name": "Large", "price": 1799 }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Item" },
                "example": {
                  "item_id": "itm_aa11e0b2c9d4",
                  "external_id": "SKU-MARG",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Margherita Pizza",
                  "description": "San Marzano tomato, fior di latte, basil",
                  "price": 1399,
                  "currency": "USD",
                  "available": true,
                  "snoozed_until": null,
                  "product_type": "product",
                  "category_ids": ["cat_2d3e4f5a6b7c"],
                  "menu_ids": ["menu_1c2d3e4f5a6b"],
                  "variant_type": "stand_alone",
                  "variants": [
                    { "variant_id": "var_3e4f5a6b7c8d", "external_id": "SKU-MARG-SM", "name": "Small", "price": 1399, "available": true },
                    { "variant_id": "var_4f5a6b7c8d9e", "external_id": "SKU-MARG-LG", "name": "Large", "price": 1799, "available": true }
                  ],
                  "created_at": "2026-05-14T12:10:00.000Z",
                  "updated_at": "2026-07-30T10:30:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "patch": {
        "operationId": "patchCatalogueItem",
        "tags": ["Catalogue Items"],
        "summary": "Update availability / price",
        "description": "Fast partial update for the hot fields: `available` (86ing), `snoozed_until` (timed 86 with auto-reactivation), and `price`. Only the fields you send are changed; the change applies to storefronts immediately.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ItemPatch" },
              "example": {
                "available": false,
                "snoozed_until": "2026-07-30T18:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated item.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Item" },
                "example": {
                  "item_id": "itm_aa11e0b2c9d4",
                  "external_id": "SKU-MARG",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Margherita Pizza",
                  "price": 1399,
                  "currency": "USD",
                  "available": false,
                  "snoozed_until": "2026-07-30T18:00:00.000Z",
                  "product_type": "product",
                  "category_ids": ["cat_2d3e4f5a6b7c"],
                  "menu_ids": ["menu_1c2d3e4f5a6b"],
                  "variant_type": "stand_alone",
                  "variants": [],
                  "created_at": "2026-05-14T12:10:00.000Z",
                  "updated_at": "2026-07-30T11:05:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "delete": {
        "operationId": "deleteCatalogueItem",
        "tags": ["Catalogue Items"],
        "summary": "Delete an item",
        "description": "Removes the item from the catalogue. Historical order records keep the item's name and price snapshot; reports are unaffected.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DeleteResponse" },
                "example": {
                  "status": "success",
                  "message": "Item deleted"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/catalogue/categories": {
      "get": {
        "operationId": "listCatalogueCategories",
        "tags": ["Catalogue Categories"],
        "summary": "List categories",
        "description": "Lists the store's categories, ordered by `sort_order`.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" }
        ],
        "responses": {
          "200": {
            "description": "Categories.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Category" }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "category_id": "cat_2d3e4f5a6b7c",
                      "external_id": "cat-pizza",
                      "store_id": "st_9x2ml7qa41ce",
                      "name": "Pizza",
                      "description": null,
                      "image_url": null,
                      "sort_order": 1,
                      "item_count": 12,
                      "created_at": "2026-05-14T12:05:00.000Z",
                      "updated_at": "2026-05-14T12:05:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "post": {
        "operationId": "createCatalogueCategory",
        "tags": ["Catalogue Categories"],
        "summary": "Create a category",
        "description": "Creates a category in the store given by the `store_id` query parameter.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CategoryWrite" },
              "example": {
                "external_id": "cat-desserts",
                "name": "Desserts",
                "description": "Sweet endings",
                "sort_order": 5
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Category created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Category" },
                "example": {
                  "category_id": "cat_8e9f0a1b2c3d",
                  "external_id": "cat-desserts",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Desserts",
                  "description": "Sweet endings",
                  "image_url": null,
                  "sort_order": 5,
                  "item_count": 0,
                  "created_at": "2026-07-30T10:40:00.000Z",
                  "updated_at": "2026-07-30T10:40:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/catalogue/categories/{category_id}": {
      "parameters": [
        { "$ref": "#/components/parameters/CategoryIdPath" }
      ],
      "get": {
        "operationId": "getCatalogueCategory",
        "tags": ["Catalogue Categories"],
        "summary": "Get a category",
        "description": "Returns one category.",
        "responses": {
          "200": {
            "description": "The category.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Category" },
                "example": {
                  "category_id": "cat_2d3e4f5a6b7c",
                  "external_id": "cat-pizza",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Pizza",
                  "description": null,
                  "image_url": null,
                  "sort_order": 1,
                  "item_count": 12,
                  "created_at": "2026-05-14T12:05:00.000Z",
                  "updated_at": "2026-05-14T12:05:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "put": {
        "operationId": "updateCatalogueCategory",
        "tags": ["Catalogue Categories"],
        "summary": "Update a category",
        "description": "Updates the category's name, description, image, or sort order.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CategoryWrite" },
              "example": {
                "name": "Wood-fired Pizza",
                "sort_order": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated category.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Category" },
                "example": {
                  "category_id": "cat_2d3e4f5a6b7c",
                  "external_id": "cat-pizza",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Wood-fired Pizza",
                  "description": null,
                  "image_url": null,
                  "sort_order": 1,
                  "item_count": 12,
                  "created_at": "2026-05-14T12:05:00.000Z",
                  "updated_at": "2026-07-30T10:45:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "delete": {
        "operationId": "deleteCatalogueCategory",
        "tags": ["Catalogue Categories"],
        "summary": "Delete a category",
        "description": "Removes the category. Items in it are not deleted — they just lose this category membership.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DeleteResponse" },
                "example": {
                  "status": "success",
                  "message": "Category deleted"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/catalogue/menus": {
      "get": {
        "operationId": "listCatalogueMenus",
        "tags": ["Catalogue Menus"],
        "summary": "List menus",
        "description": "Lists the store's menus with their serving hours and member counts.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" }
        ],
        "responses": {
          "200": {
            "description": "Menus.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["data"],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Menu" }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "menu_id": "menu_1c2d3e4f5a6b",
                      "external_id": "menu-main",
                      "store_id": "st_9x2ml7qa41ce",
                      "name": "Main Menu",
                      "description": "All-day menu",
                      "availability": {
                        "monday": [{ "open": "11:00", "close": "22:00" }],
                        "sunday": [{ "open": "12:00", "close": "21:00" }]
                      },
                      "category_ids": ["cat_2d3e4f5a6b7c"],
                      "item_count": 42,
                      "is_active": true,
                      "created_at": "2026-05-14T12:00:00.000Z",
                      "updated_at": "2026-07-29T18:40:12.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "post": {
        "operationId": "createCatalogueMenu",
        "tags": ["Catalogue Menus"],
        "summary": "Create a menu",
        "description": "Creates a menu in the store given by the `store_id` query parameter. A menu groups categories (and their items) with its own serving hours — e.g. a breakfast menu served 07:00–11:00.",
        "parameters": [
          { "$ref": "#/components/parameters/StoreIdQuery" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MenuWrite" },
              "example": {
                "external_id": "menu-breakfast",
                "name": "Breakfast",
                "description": "Served until 11am",
                "availability": {
                  "monday": [{ "open": "07:00", "close": "11:00" }],
                  "tuesday": [{ "open": "07:00", "close": "11:00" }]
                },
                "category_ids": ["cat_8e9f0a1b2c3d"],
                "is_active": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Menu created.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Menu" },
                "example": {
                  "menu_id": "menu_7b8c9d0e1f2a",
                  "external_id": "menu-breakfast",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Breakfast",
                  "description": "Served until 11am",
                  "availability": {
                    "monday": [{ "open": "07:00", "close": "11:00" }],
                    "tuesday": [{ "open": "07:00", "close": "11:00" }]
                  },
                  "category_ids": ["cat_8e9f0a1b2c3d"],
                  "item_count": 0,
                  "is_active": true,
                  "created_at": "2026-07-30T10:50:00.000Z",
                  "updated_at": "2026-07-30T10:50:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    },
    "/catalogue/menus/{menu_id}": {
      "parameters": [
        { "$ref": "#/components/parameters/MenuIdPath" }
      ],
      "get": {
        "operationId": "getCatalogueMenu",
        "tags": ["Catalogue Menus"],
        "summary": "Get a menu",
        "description": "Returns one menu with its serving hours and category membership.",
        "responses": {
          "200": {
            "description": "The menu.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Menu" },
                "example": {
                  "menu_id": "menu_1c2d3e4f5a6b",
                  "external_id": "menu-main",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Main Menu",
                  "description": "All-day menu",
                  "availability": {
                    "monday": [{ "open": "11:00", "close": "22:00" }]
                  },
                  "category_ids": ["cat_2d3e4f5a6b7c"],
                  "item_count": 42,
                  "is_active": true,
                  "created_at": "2026-05-14T12:00:00.000Z",
                  "updated_at": "2026-07-29T18:40:12.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "put": {
        "operationId": "updateCatalogueMenu",
        "tags": ["Catalogue Menus"],
        "summary": "Update a menu",
        "description": "Updates the menu's name, description, serving hours, category membership, or active flag.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MenuWrite" },
              "example": {
                "name": "Main Menu",
                "availability": {
                  "monday": [{ "open": "11:00", "close": "23:00" }]
                },
                "is_active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated menu.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Menu" },
                "example": {
                  "menu_id": "menu_1c2d3e4f5a6b",
                  "external_id": "menu-main",
                  "store_id": "st_9x2ml7qa41ce",
                  "name": "Main Menu",
                  "description": "All-day menu",
                  "availability": {
                    "monday": [{ "open": "11:00", "close": "23:00" }]
                  },
                  "category_ids": ["cat_2d3e4f5a6b7c"],
                  "item_count": 42,
                  "is_active": true,
                  "created_at": "2026-05-14T12:00:00.000Z",
                  "updated_at": "2026-07-30T11:00:00.000Z"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      },
      "delete": {
        "operationId": "deleteCatalogueMenu",
        "tags": ["Catalogue Menus"],
        "summary": "Delete a menu",
        "description": "Removes the menu. Its categories and items remain in the catalogue.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DeleteResponse" },
                "example": {
                  "status": "success",
                  "message": "Menu deleted"
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/Conflict" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/TooManyRequests" }
        }
      }
    }
  },
  "webhooks": {
    "catalogue.item_updated": {
      "post": {
        "operationId": "webhookCatalogueItemUpdated",
        "tags": ["Catalogue Items"],
        "summary": "catalogue.item_updated — an item changed outside your app",
        "description": "Sent when a catalogue item is created, updated, or deleted through another channel (the merchant portal, a POS sync, or another partner app), so your cached copy stays fresh. Not emitted for changes your own app made.\n\n**Signing & delivery (all events):** every delivery carries `X-Quiqqy-Signature` — HMAC-SHA256 of the **raw request body** using your webhook secret, lowercase hex. Verify with a constant-time compare before trusting the payload. Deliveries time out after 10 s; 3xx responses count as failures. Failed deliveries are retried on a `1m, 5m, 30m, 2h, 6h` ladder — **6 attempts total**. Deliveries can repeat: dedupe on `X-Quiqqy-Delivery-Id` / `event_id`.",
        "parameters": [
          { "$ref": "#/components/parameters/XQuiqqySignature" },
          { "$ref": "#/components/parameters/XQuiqqyEvent" },
          { "$ref": "#/components/parameters/XQuiqqyDeliveryId" },
          { "$ref": "#/components/parameters/XQuiqqyLocationId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ItemUpdatedEnvelope" },
              "example": {
                "event_type": "catalogue.item_updated",
                "event_id": "evt_e6f85d9a0b42",
                "store_id": "st_9x2ml7qa41ce",
                "timestamp": "2026-07-30T11:20:45.000Z",
                "data": {
                  "change": "updated",
                  "source": "merchant_portal",
                  "item": {
                    "item_id": "itm_aa11e0b2c9d4",
                    "external_id": "SKU-MARG",
                    "store_id": "st_9x2ml7qa41ce",
                    "name": "Margherita Pizza",
                    "price": 1399,
                    "currency": "USD",
                    "available": true,
                    "snoozed_until": null,
                    "product_type": "product",
                    "category_ids": ["cat_2d3e4f5a6b7c"],
                    "menu_ids": ["menu_1c2d3e4f5a6b"],
                    "variant_type": "none",
                    "variants": [],
                    "created_at": "2026-05-14T12:10:00.000Z",
                    "updated_at": "2026-07-30T11:20:45.000Z"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx within 10 seconds to acknowledge the delivery. Non-2xx (including 3xx) triggers the retry ladder."
          }
        }
      }
    },
    "catalogue.item_availability_changed": {
      "post": {
        "operationId": "webhookCatalogueAvailabilityChanged",
        "tags": ["Catalogue Items"],
        "summary": "catalogue.item_availability_changed — items 86'd or restored",
        "description": "Sent when item availability flips through another channel — a lighter, batched signal than `catalogue.item_updated` for the high-frequency 86 case. Same signing headers and retry ladder as all events.",
        "parameters": [
          { "$ref": "#/components/parameters/XQuiqqySignature" },
          { "$ref": "#/components/parameters/XQuiqqyEvent" },
          { "$ref": "#/components/parameters/XQuiqqyDeliveryId" },
          { "$ref": "#/components/parameters/XQuiqqyLocationId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AvailabilityChangedEnvelope" },
              "example": {
                "event_type": "catalogue.item_availability_changed",
                "event_id": "evt_f7a96e0b1c53",
                "store_id": "st_9x2ml7qa41ce",
                "timestamp": "2026-07-30T11:22:00.000Z",
                "data": {
                  "items": [
                    { "item_id": "itm_aa11e0b2c9d4", "external_id": "SKU-MARG", "available": false, "snoozed_until": "2026-07-30T18:00:00.000Z" },
                    { "item_id": "itm_cc33a2d4e1f6", "external_id": "SKU-CALZONE", "available": true, "snoozed_until": null }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Return any 2xx within 10 seconds to acknowledge the delivery."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "**Partner access token** (OAuth 2.0 client flow). Exchange your app's `client_id` / `client_secret` at `POST /oauth2/token` with `grant_type: client_credentials` to receive `{ access_token, expires_in }`. Request the `reports.read` scope for the Reports endpoints and `catalogue.write` for catalogue mutations. Tokens are scoped to your app's authorized organizations — a `store_id` outside them returns `404`. Cache the token until it expires."
      }
    },
    "parameters": {
      "StoreIdQuery": {
        "name": "store_id",
        "in": "query",
        "required": true,
        "description": "The store this request is scoped to. Your token must be authorized for the store's organization.",
        "schema": { "type": "string", "examples": ["st_9x2ml7qa41ce"] }
      },
      "DateFrom": {
        "name": "date_from",
        "in": "query",
        "required": true,
        "description": "Start of the range (inclusive), `YYYY-MM-DD` wall-clock in `tz`.",
        "schema": { "type": "string", "format": "date", "examples": ["2026-07-01"] }
      },
      "DateTo": {
        "name": "date_to",
        "in": "query",
        "required": true,
        "description": "End of the range (inclusive), `YYYY-MM-DD` wall-clock in `tz`.",
        "schema": { "type": "string", "format": "date", "examples": ["2026-07-28"] }
      },
      "Tz": {
        "name": "tz",
        "in": "query",
        "required": false,
        "description": "IANA timezone for wall-clock bucketing and hour extraction. Defaults to `UTC`.",
        "schema": { "type": "string", "default": "UTC", "examples": ["America/New_York"] }
      },
      "ItemIdPath": {
        "name": "item_id",
        "in": "path",
        "required": true,
        "description": "Catalogue item id.",
        "schema": { "type": "string", "examples": ["itm_aa11e0b2c9d4"] }
      },
      "CategoryIdPath": {
        "name": "category_id",
        "in": "path",
        "required": true,
        "description": "Category id.",
        "schema": { "type": "string", "examples": ["cat_2d3e4f5a6b7c"] }
      },
      "MenuIdPath": {
        "name": "menu_id",
        "in": "path",
        "required": true,
        "description": "Menu id.",
        "schema": { "type": "string", "examples": ["menu_1c2d3e4f5a6b"] }
      },
      "XQuiqqySignature": {
        "name": "X-Quiqqy-Signature",
        "in": "header",
        "required": true,
        "description": "HMAC-SHA256 of the raw request body using your webhook secret, lowercase hex. Verify with a constant-time compare against the raw bytes.",
        "schema": { "type": "string", "examples": ["a3f1c2e94b7d8065f4a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0"] }
      },
      "XQuiqqyEvent": {
        "name": "X-Quiqqy-Event",
        "in": "header",
        "required": true,
        "description": "The event type, mirroring `event_type` in the body.",
        "schema": { "type": "string", "enum": ["catalogue.item_updated", "catalogue.item_availability_changed"] }
      },
      "XQuiqqyDeliveryId": {
        "name": "X-Quiqqy-Delivery-Id",
        "in": "header",
        "required": true,
        "description": "Unique delivery id (equals `event_id`). Deliveries can repeat — use this for idempotency.",
        "schema": { "type": "string", "examples": ["evt_e6f85d9a0b42"] }
      },
      "XQuiqqyLocationId": {
        "name": "X-Quiqqy-Location-Id",
        "in": "header",
        "required": false,
        "description": "The merchant's external store id, when one is mapped.",
        "schema": { "type": "string", "examples": ["LITHOS-LOC-001"] }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, expired, or invalid access token.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "auth.invalid_token",
                "message": "The access token is missing, expired, or invalid."
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "The token is valid but lacks the required scope (`reports.read` / `catalogue.write`).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "auth.missing_scope",
                "message": "This operation requires the catalogue.write scope."
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The store or resource does not exist, or the token is not authorized for it (cross-tenant requests always return 404).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "resource.not_found",
                "message": "Store st_9x2ml7qa41ce was not found for this partner."
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "The request conflicts with the resource's current state.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "resource.conflict",
                "message": "The request conflicts with the current state of the resource."
              }
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "The body or query parameters are well-formed but failed validation.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "validation.failed",
                "message": "price must be an integer in minor units."
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded. Honour `Retry-After` and back off. Report endpoints have per-app hourly quotas.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": { "type": "integer" }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "rate_limit.exceeded",
                "message": "Too many requests. Retry after the indicated delay."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Every 4xx/5xx response uses this shape. `code` is a stable, dot-namespaced machine code — branch on it, not on `message`.",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable dot-namespaced error code, e.g. `auth.invalid_token`, `auth.missing_scope`, `reports.range_too_large`, `reports.invalid_cursor`, `catalogue.external_id_conflict`, `validation.failed`, `rate_limit.exceeded`.",
                "examples": ["reports.invalid_cursor"]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. May change between releases — do not parse."
              }
            }
          }
        }
      },
      "SalesTotals": {
        "type": "object",
        "description": "All amounts are integers in minor units.",
        "required": ["gross_sales", "net_sales", "orders", "average_order_value"],
        "properties": {
          "gross_sales": { "type": "integer", "description": "Sum of order totals before refunds." },
          "net_sales": { "type": "integer", "description": "Gross minus discounts and refunds." },
          "orders": { "type": "integer" },
          "average_order_value": { "type": "integer", "description": "Minor units, rounded." },
          "discounts": { "type": "integer" },
          "refunds": { "type": "integer" },
          "tax": { "type": "integer" },
          "tips": { "type": "integer" },
          "delivery_fees": { "type": "integer" }
        }
      },
      "SalesSeriesPoint": {
        "type": "object",
        "required": ["period_start", "gross_sales", "net_sales", "orders"],
        "properties": {
          "period_start": {
            "type": "string",
            "format": "date",
            "description": "First day of the bucket (`YYYY-MM-DD` in `tz`). Weeks start on Monday."
          },
          "gross_sales": { "type": "integer" },
          "net_sales": { "type": "integer" },
          "orders": { "type": "integer" },
          "average_order_value": { "type": "integer" },
          "discounts": { "type": "integer" },
          "refunds": { "type": "integer" },
          "tax": { "type": "integer" }
        }
      },
      "SalesSummary": {
        "type": "object",
        "required": ["store_id", "date_from", "date_to", "group_by", "currency", "totals", "series"],
        "properties": {
          "store_id": { "type": "string" },
          "date_from": { "type": "string", "format": "date" },
          "date_to": { "type": "string", "format": "date" },
          "group_by": { "type": "string", "enum": ["day", "week", "month"] },
          "tz": { "type": "string" },
          "currency": { "type": "string", "description": "ISO 4217 code.", "examples": ["USD"] },
          "totals": { "$ref": "#/components/schemas/SalesTotals" },
          "series": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SalesSeriesPoint" }
          }
        }
      },
      "OrderRecordItem": {
        "type": "object",
        "required": ["item_id", "name", "quantity", "unit_price", "subtotal"],
        "properties": {
          "item_id": { "type": "string" },
          "external_id": { "type": ["string", "null"], "description": "Merchant POS id, where synced." },
          "name": { "type": "string" },
          "quantity": { "type": "integer" },
          "unit_price": { "type": "integer", "description": "Minor units." },
          "subtotal": { "type": "integer", "description": "Minor units, including modifiers." }
        }
      },
      "OrderRecord": {
        "type": "object",
        "description": "One flat, export-friendly order record. All money fields are integers in minor units of `currency`.",
        "required": ["order_id", "order_number", "store_id", "status", "fulfillment_type", "payment_status", "currency", "subtotal", "total_amount", "placed_at", "items"],
        "properties": {
          "order_id": { "type": "string", "examples": ["ord_7d4e2k91mqx0"] },
          "order_number": { "type": "string" },
          "store_id": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["pending", "confirmed", "preparing", "ready", "out_for_delivery", "delivered", "cancelled", "failed"]
          },
          "fulfillment_type": { "type": "string", "enum": ["delivery", "pickup", "dine_in"] },
          "channel": {
            "type": "string",
            "enum": ["storefront", "hub", "kiosk", "pos", "aggregator"],
            "description": "Where the order originated."
          },
          "payment_status": { "type": "string", "enum": ["pending", "paid", "failed", "refunded"] },
          "payment_method": { "type": ["string", "null"], "examples": ["card"] },
          "currency": { "type": "string" },
          "subtotal": { "type": "integer" },
          "tax": { "type": "integer" },
          "delivery_fee": { "type": "integer" },
          "discount": { "type": "integer" },
          "tip": { "type": "integer" },
          "total_amount": { "type": "integer" },
          "cancellation_reason": { "type": ["string", "null"] },
          "placed_at": { "type": "string", "format": "date-time" },
          "completed_at": { "type": ["string", "null"], "format": "date-time" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/OrderRecordItem" }
          }
        }
      },
      "OrderRecordPage": {
        "type": "object",
        "required": ["data", "next_cursor", "has_more"],
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/OrderRecord" }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass back as `cursor` for the next page. `null` on the last page. Cursors expire after 24 hours."
          },
          "has_more": { "type": "boolean" }
        }
      },
      "TopItemsReport": {
        "type": "object",
        "required": ["store_id", "date_from", "date_to", "currency", "data"],
        "properties": {
          "store_id": { "type": "string" },
          "date_from": { "type": "string", "format": "date" },
          "date_to": { "type": "string", "format": "date" },
          "currency": { "type": "string" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["rank", "item_id", "name", "quantity_sold", "orders", "gross_sales"],
              "properties": {
                "rank": { "type": "integer", "minimum": 1 },
                "item_id": { "type": "string" },
                "external_id": { "type": ["string", "null"], "description": "Merchant POS id, where synced." },
                "name": { "type": "string" },
                "category": { "type": ["string", "null"] },
                "quantity_sold": { "type": "integer" },
                "orders": { "type": "integer", "description": "Distinct orders containing the item." },
                "gross_sales": { "type": "integer", "description": "Minor units." }
              }
            }
          }
        }
      },
      "HourlyDemandReport": {
        "type": "object",
        "required": ["store_id", "date_from", "date_to", "tz", "currency", "data"],
        "properties": {
          "store_id": { "type": "string" },
          "date_from": { "type": "string", "format": "date" },
          "date_to": { "type": "string", "format": "date" },
          "tz": { "type": "string" },
          "currency": { "type": "string" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["hour", "orders", "gross_sales"],
              "properties": {
                "weekday": {
                  "type": "string",
                  "enum": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"],
                  "description": "Present when `split_by=weekday`."
                },
                "hour": { "type": "integer", "minimum": 0, "maximum": 23, "description": "Wall-clock hour in `tz`." },
                "orders": { "type": "integer" },
                "gross_sales": { "type": "integer", "description": "Minor units." }
              }
            }
          }
        }
      },
      "StoreHealthRow": {
        "type": "object",
        "required": ["store_id", "name", "status", "is_open", "orders_today"],
        "properties": {
          "store_id": { "type": "string" },
          "name": { "type": "string" },
          "status": { "type": "string", "enum": ["active", "inactive"] },
          "is_open": { "type": "boolean", "description": "Live open state right now." },
          "orders_today": { "type": "integer", "description": "Orders placed today (wall-clock in `tz`)." },
          "acceptance_rate": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Share of orders accepted (trailing 28 days)."
          },
          "avg_time_to_accept_seconds": { "type": ["integer", "null"] },
          "avg_prep_time_minutes": { "type": ["integer", "null"] },
          "cancellation_rate": { "type": "number", "minimum": 0, "maximum": 1 },
          "top_cancellation_reason": {
            "type": ["string", "null"],
            "enum": ["ITEM_OUT_OF_STOCK", "STORE_CLOSED", "POS_OFFLINE", "CAPACITY", "PRICE_MISMATCH", "OTHER", null],
            "description": "Most frequent structured cancellation reason (trailing 28 days)."
          },
          "last_order_at": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "StoreHealthReport": {
        "type": "object",
        "required": ["generated_at", "data"],
        "properties": {
          "generated_at": { "type": "string", "format": "date-time" },
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/StoreHealthRow" }
          }
        }
      },
      "PaymentsSummaryRow": {
        "type": "object",
        "required": ["payment_method", "transactions", "gross_amount", "refunded_amount", "net_amount"],
        "properties": {
          "payment_method": {
            "type": "string",
            "enum": ["card", "digital_wallet", "cash", "bank_transfer"]
          },
          "provider": { "type": ["string", "null"], "examples": ["stripe"] },
          "transactions": { "type": "integer" },
          "gross_amount": { "type": "integer", "description": "Minor units." },
          "refunded_amount": { "type": "integer", "description": "Minor units." },
          "net_amount": { "type": "integer", "description": "Minor units." }
        }
      },
      "PaymentsSummary": {
        "type": "object",
        "required": ["store_id", "date_from", "date_to", "currency", "totals", "data"],
        "properties": {
          "store_id": { "type": "string" },
          "date_from": { "type": "string", "format": "date" },
          "date_to": { "type": "string", "format": "date" },
          "currency": { "type": "string" },
          "totals": {
            "type": "object",
            "required": ["transactions", "gross_amount", "refunded_amount", "net_amount"],
            "properties": {
              "transactions": { "type": "integer" },
              "gross_amount": { "type": "integer" },
              "refunded_amount": { "type": "integer" },
              "net_amount": { "type": "integer" }
            }
          },
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PaymentsSummaryRow" }
          }
        }
      },
      "ItemTax": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "examples": ["NYC Sales Tax"] },
          "code": { "type": "string" },
          "rate": { "type": "number", "description": "Percentage rate, e.g. `8.875`." },
          "components": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["agency", "rate"],
              "properties": {
                "agency": { "type": "string" },
                "rate": { "type": "number" },
                "compound": { "type": "boolean", "default": false }
              }
            }
          }
        }
      },
      "ItemVariant": {
        "type": "object",
        "required": ["variant_id", "external_id", "name", "price", "available"],
        "properties": {
          "variant_id": { "type": "string", "examples": ["var_3e4f5a6b7c8d"] },
          "external_id": { "type": ["string", "null"] },
          "name": { "type": "string", "examples": ["Large"] },
          "price": { "type": "integer", "description": "Minor units." },
          "stock": { "type": ["integer", "null"] },
          "available": { "type": "boolean" },
          "image_url": { "type": ["string", "null"], "format": "uri" }
        }
      },
      "ItemVariantWrite": {
        "type": "object",
        "required": ["external_id", "name", "price"],
        "properties": {
          "external_id": { "type": "string", "description": "Stable variant id — the upsert key." },
          "name": { "type": "string" },
          "price": { "type": "integer", "description": "Minor units." },
          "stock": { "type": "integer", "minimum": 0 },
          "available": { "type": "boolean", "default": true },
          "image_url": { "type": "string", "format": "uri" }
        }
      },
      "Item": {
        "type": "object",
        "description": "A catalogue item. Money fields are integers in minor units of `currency`.",
        "required": ["item_id", "store_id", "name", "price", "currency", "available", "product_type", "created_at", "updated_at"],
        "properties": {
          "item_id": { "type": "string", "examples": ["itm_aa11e0b2c9d4"] },
          "external_id": { "type": ["string", "null"], "description": "Merchant POS id, where synced or supplied on create." },
          "store_id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "price": { "type": "integer", "description": "Minor units." },
          "currency": { "type": "string", "examples": ["USD"] },
          "image_url": { "type": ["string", "null"], "format": "uri" },
          "available": { "type": "boolean" },
          "snoozed_until": { "type": ["string", "null"], "format": "date-time" },
          "product_type": {
            "type": "string",
            "enum": ["product", "addon"],
            "description": "`addon` items are modifiers — hidden from storefront listings."
          },
          "category_ids": {
            "type": "array",
            "items": { "type": "string" }
          },
          "menu_ids": {
            "type": "array",
            "items": { "type": "string" }
          },
          "variant_type": {
            "type": "string",
            "enum": ["none", "stand_alone", "consumption_ratio"]
          },
          "variants": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ItemVariant" }
          },
          "tax": {
            "oneOf": [
              { "$ref": "#/components/schemas/ItemTax" },
              { "type": "null" }
            ]
          },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "ItemWrite": {
        "type": "object",
        "required": ["name", "price"],
        "properties": {
          "external_id": { "type": "string", "description": "Your stable id for this item — strongly recommended." },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "price": { "type": "integer", "description": "Minor units." },
          "image_url": { "type": "string", "format": "uri" },
          "available": { "type": "boolean", "default": true },
          "product_type": { "type": "string", "enum": ["product", "addon"], "default": "product" },
          "category_ids": {
            "type": "array",
            "items": { "type": "string" }
          },
          "menu_ids": {
            "type": "array",
            "items": { "type": "string" }
          },
          "variant_type": { "type": "string", "enum": ["none", "stand_alone", "consumption_ratio"], "default": "none" },
          "variants": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ItemVariantWrite" },
            "description": "Replaces the variant set exactly — a dropped variant is deactivated."
          },
          "tax_exempt": { "type": "boolean" },
          "tax": { "$ref": "#/components/schemas/ItemTax" }
        }
      },
      "CatalogueItemPage": {
        "type": "object",
        "required": ["data", "next_cursor", "has_more"],
        "properties": {
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Item" }
          },
          "next_cursor": {
            "type": ["string", "null"],
            "description": "Pass back as `cursor` for the next page. `null` on the last page."
          },
          "has_more": { "type": "boolean" }
        }
      },
      "ItemPatch": {
        "type": "object",
        "description": "At least one field must be sent.",
        "minProperties": 1,
        "properties": {
          "available": { "type": "boolean", "description": "`false` = 86 the item; `true` restores it and clears any snooze." },
          "snoozed_until": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Off sale until this time, then auto-reactivates. `null` clears an existing snooze."
          },
          "price": { "type": "integer", "description": "Minor units." }
        }
      },
      "Category": {
        "type": "object",
        "required": ["category_id", "store_id", "name", "sort_order", "created_at", "updated_at"],
        "properties": {
          "category_id": { "type": "string", "examples": ["cat_2d3e4f5a6b7c"] },
          "external_id": { "type": ["string", "null"] },
          "store_id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "image_url": { "type": ["string", "null"], "format": "uri" },
          "sort_order": { "type": "integer" },
          "item_count": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "CategoryWrite": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "external_id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "image_url": { "type": "string", "format": "uri" },
          "sort_order": { "type": "integer" },
          "tax_exempt": { "type": "boolean" },
          "tax": { "$ref": "#/components/schemas/ItemTax" }
        }
      },
      "MenuAvailability": {
        "type": "object",
        "description": "Serving hours per weekday. An absent weekday means the menu is not served that day; an absent object means always available.",
        "properties": {
          "monday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "tuesday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "wednesday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "thursday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "friday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "saturday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } },
          "sunday": { "type": "array", "items": { "$ref": "#/components/schemas/TimeRange" } }
        }
      },
      "TimeRange": {
        "type": "object",
        "required": ["open", "close"],
        "properties": {
          "open": { "type": "string", "description": "`HH:MM`, 24-hour, store-local.", "examples": ["11:00"] },
          "close": { "type": "string", "examples": ["22:00"] }
        }
      },
      "Menu": {
        "type": "object",
        "required": ["menu_id", "store_id", "name", "is_active", "created_at", "updated_at"],
        "properties": {
          "menu_id": { "type": "string", "examples": ["menu_1c2d3e4f5a6b"] },
          "external_id": { "type": ["string", "null"] },
          "store_id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "availability": {
            "oneOf": [
              { "$ref": "#/components/schemas/MenuAvailability" },
              { "type": "null" }
            ]
          },
          "category_ids": {
            "type": "array",
            "items": { "type": "string" }
          },
          "item_count": { "type": "integer" },
          "is_active": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "MenuWrite": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "external_id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "availability": { "$ref": "#/components/schemas/MenuAvailability" },
          "category_ids": {
            "type": "array",
            "items": { "type": "string" }
          },
          "is_active": { "type": "boolean", "default": true }
        }
      },
      "DeleteResponse": {
        "type": "object",
        "required": ["status", "message"],
        "properties": {
          "status": { "type": "string", "const": "success" },
          "message": { "type": "string" }
        }
      },
      "MercnetWebhookEnvelope": {
        "type": "object",
        "description": "Common envelope for Mercnet webhook deliveries. Signature/delivery rules: `X-Quiqqy-Signature` = HMAC-SHA256(webhook_secret, raw body) lowercase hex; 10 s timeout; retries at 1m/5m/30m/2h/6h (6 attempts); dedupe on `event_id`.",
        "required": ["event_type", "event_id", "store_id", "data", "timestamp"],
        "properties": {
          "event_type": { "type": "string" },
          "event_id": { "type": "string", "description": "Unique event id — equals `X-Quiqqy-Delivery-Id`. Dedupe on it." },
          "store_id": { "type": "string" },
          "data": { "type": "object", "additionalProperties": true },
          "timestamp": { "type": "string", "format": "date-time" }
        }
      },
      "ItemUpdatedEnvelope": {
        "allOf": [
          { "$ref": "#/components/schemas/MercnetWebhookEnvelope" },
          {
            "type": "object",
            "properties": {
              "event_type": { "type": "string", "const": "catalogue.item_updated" },
              "data": {
                "type": "object",
                "required": ["change", "item"],
                "properties": {
                  "change": { "type": "string", "enum": ["created", "updated", "deleted"] },
                  "source": {
                    "type": "string",
                    "enum": ["merchant_portal", "pos_sync", "partner_api"],
                    "description": "The channel that made the change."
                  },
                  "item": { "$ref": "#/components/schemas/Item" }
                }
              }
            }
          }
        ]
      },
      "AvailabilityChangedEnvelope": {
        "allOf": [
          { "$ref": "#/components/schemas/MercnetWebhookEnvelope" },
          {
            "type": "object",
            "properties": {
              "event_type": { "type": "string", "const": "catalogue.item_availability_changed" },
              "data": {
                "type": "object",
                "required": ["items"],
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["item_id", "available"],
                      "properties": {
                        "item_id": { "type": "string" },
                        "external_id": { "type": ["string", "null"] },
                        "available": { "type": "boolean" },
                        "snoozed_until": { "type": ["string", "null"], "format": "date-time" }
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      }
    }
  }
}
