{
  "openapi": "3.1.1",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "Blizzfull API",
    "version": "2.8.0",
    "summary": "Read a restaurant's menu, keep it in sync, and put orders into its kitchen.",
    "description": "The Blizzfull API connects point-of-sale systems, ordering partners and AI agents to Blizzfull restaurants.\n\n## Getting access\nEvery request sends an API user and token. Ask your Blizzfull contact for a key; you get a sandbox key first. There are two kinds:\n- **Ordering key**, for a partner that places orders for one restaurant, such as an AI phone agent. It reads that restaurant's storefront and menu feed, places orders, and reads back the orders it placed. Nothing else: not the restaurant's other orders, its settings or its reports.\n- **Restaurant key**, for point-of-sale and menu integrations. It belongs to one restaurant (or to a partner's restaurants) and can be narrowed to the calls it needs.\n\n## Conventions\n- **Base URL:** `https://apiv2.blizzfull.com`.\n- **Envelope:** a successful response is `{\"response\": ...}`.\n- **Errors:** a refusal is the HTTP status with `{\"response\": \"<what to fix>\"}`. A failure inside the order or menu engine is HTTP 400 with `{\"type\": \"exception\", \"message\": ..., \"code\": ...}`.\n- **Money:** orders (StandardOrder) use integer cents. Menus use dollars: numbers when you send them, decimal strings in the feed.\n- **Time:** ISO 8601, UTC.\n- **IDs:** Blizzfull's IDs. A menu import uses your own IDs, and Blizzfull remembers the mapping.\n- **Rate limits:** 30 requests a minute per key and from each IP address, and 30 order or menu creations every 5 minutes per key. Over a limit you get HTTP 429 with `Retry-After`: wait that long, then send the request again (an order with the same `tempID`).\n- **Compatibility:** fields are added without notice; ignore the ones you don't use.\n\n## Machine-readable\nThis document and its schemas are served from `https://apiv2.blizzfull.com/schema/`: `openapi.json`, `standard-order.schema.json`, `menu-feed.schema.json` and `blizzfullMenuSchema.json` (JSON Schema 2020-12).",
    "contact": {
      "name": "Blizzfull developer support",
      "url": "https://blizzfull.com/developer"
    }
  },
  "servers": [
    {
      "url": "https://apiv2.blizzfull.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiUser": [],
      "ApiToken": []
    }
  ],
  "tags": [
    {
      "name": "Menu",
      "description": "Read a restaurant's live menu, or send yours to Blizzfull."
    },
    {
      "name": "Restaurant",
      "description": "The restaurant's storefront: where it is, when it takes pickup orders and how long they take."
    },
    {
      "name": "Orders",
      "description": "Place orders into a restaurant and read them back as StandardOrder."
    },
    {
      "name": "Point of sale",
      "description": "For point-of-sale partners: pick up new orders, marketplace orders included, and run them through to done."
    },
    {
      "name": "Store",
      "description": "For point-of-sale partners: pause ordering, set busy mode, and turn third-party ordering on and off."
    },
    {
      "name": "Order feed",
      "description": "For point-of-sale partners: every order at your restaurant the moment it changes, over a websocket."
    }
  ],
  "paths": {
    "/items/getFullMenu": {
      "get": {
        "operationId": "getMenuFeed",
        "tags": [
          "Menu"
        ],
        "summary": "Get the live menu",
        "description": "The restaurant's whole menu as customers see it now: categories, items, sizes, option groups and options, with what is sold out. Everything you need to build an order is here: an item's `id`, its sizes' `id`, and each option group's `linkageID` with its options' `id`.\n\nRead it at the start of a session and again when an order is refused for an unknown item; it can be large (a few MB for a big menu).",
        "parameters": [
          {
            "name": "restaurantID",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Your key's restaurant."
          },
          {
            "name": "locationID",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            },
            "description": "Always 1."
          }
        ],
        "responses": {
          "200": {
            "description": "The menu.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/MenuFeed"
                    }
                  }
                },
                "example": {
                  "response": [
                    {
                      "id": "26900",
                      "name": "Combos",
                      "shortName": "combos",
                      "description": "",
                      "parent": "0",
                      "position": "0",
                      "specialType": null,
                      "active": true,
                      "displayOnly": false,
                      "secret": false,
                      "isMenu": false,
                      "availableTimeData": null,
                      "availableFrom": null,
                      "availableUntil": null,
                      "media": null,
                      "children": null,
                      "items": [
                        {
                          "id": 166519,
                          "name": "Hungry Combo",
                          "description": "Tenders or wings, a flavor, a side and a drink.",
                          "category": "26900",
                          "categoryName": "Combos",
                          "categoryShortName": "combos",
                          "price": "16.50",
                          "inactive": false,
                          "inactiveEnds": null,
                          "sizeable": false,
                          "sizes": null,
                          "media": null,
                          "spicy": false,
                          "vegetarian": false,
                          "gf": false,
                          "keto": false,
                          "alcoholic": false,
                          "taxExempt": false,
                          "orderTypeRestriction": "",
                          "options": [],
                          "fooditems": [
                            {
                              "id": 221801,
                              "name": "Hungry Combo",
                              "description": "",
                              "options": [
                                {
                                  "linkageID": "216385",
                                  "id": "97001",
                                  "componentID": "221801",
                                  "title": "Choose a style",
                                  "phrase": "",
                                  "kitchenname": "",
                                  "type": "TYPE_SINGLE_SELECT",
                                  "required": true,
                                  "position": "1",
                                  "defaultIndex": 0,
                                  "extraProps": null,
                                  "sizeEffects": null,
                                  "items": [
                                    {
                                      "id": "441415",
                                      "name": "6 Tenders",
                                      "cost": 0,
                                      "default": 0,
                                      "unavailableUntil": "0",
                                      "position": 1,
                                      "media": null,
                                      "subOptions": null
                                    },
                                    {
                                      "id": "441416",
                                      "name": "8 Wings",
                                      "cost": 0,
                                      "default": 0,
                                      "unavailableUntil": "0",
                                      "position": 2,
                                      "media": null,
                                      "subOptions": null
                                    }
                                  ]
                                },
                                {
                                  "linkageID": "216386",
                                  "id": "97002",
                                  "componentID": "221801",
                                  "title": "Choose a flavor",
                                  "phrase": "",
                                  "kitchenname": "",
                                  "type": "TYPE_SINGLE_SELECT",
                                  "required": true,
                                  "position": "2",
                                  "defaultIndex": 0,
                                  "extraProps": null,
                                  "sizeEffects": null,
                                  "items": [
                                    {
                                      "id": "441417",
                                      "name": "Lemon Pepper",
                                      "cost": 0,
                                      "default": 0,
                                      "unavailableUntil": "0",
                                      "position": 1,
                                      "media": null,
                                      "subOptions": null
                                    },
                                    {
                                      "id": "441418",
                                      "name": "Mango Habanero",
                                      "cost": 0.5,
                                      "default": 0,
                                      "unavailableUntil": "0",
                                      "position": 2,
                                      "media": null,
                                      "subOptions": null
                                    }
                                  ]
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/restaurants/{restaurantId}": {
      "get": {
        "operationId": "getStorefront",
        "tags": [
          "Restaurant"
        ],
        "summary": "Get the storefront",
        "description": "What your callers need to know about the restaurant: where it is, its phone number, when it takes pickup orders, how long they take, and its sales tax rate.\n\nPlacing an order does not check these. Check `takingOrders`, `closedUntil` and `hoursDataPickup` before you offer pickup, and quote `busyModePickupTime` while `busyMode` is on (otherwise `pickupTime`).",
        "parameters": [
          {
            "name": "restaurantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Your key's restaurant."
          }
        ],
        "responses": {
          "200": {
            "description": "The storefront.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/Storefront"
                    }
                  }
                },
                "example": {
                  "response": {
                    "id": "1234",
                    "name": "Example Wings",
                    "description": "Fresh wings and tenders, breaded to order.",
                    "phone": "5555550100",
                    "streetAddress": "100 Main Street",
                    "city": "Bakersfield",
                    "state": "CA",
                    "zip": "93306",
                    "geoLocation": {
                      "latitude": 35.3926,
                      "longitude": -118.9663
                    },
                    "timeZone": "America/Los_Angeles",
                    "taxRate": 8.25,
                    "takingOrders": "1",
                    "closedUntil": 0,
                    "hoursDataPickup": [
                      [
                        [
                          [
                            660,
                            1260
                          ]
                        ],
                        [
                          [
                            660,
                            1260
                          ]
                        ],
                        [
                          [
                            660,
                            1260
                          ]
                        ],
                        [
                          [
                            660,
                            1260
                          ]
                        ],
                        [
                          [
                            660,
                            1260
                          ]
                        ],
                        [
                          [
                            660,
                            1320
                          ]
                        ],
                        [
                          [
                            660,
                            1320
                          ]
                        ]
                      ],
                      []
                    ],
                    "pickupTime": "15",
                    "busyMode": false,
                    "busyModePickupTime": 25,
                    "pickupInstructions": ""
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/menu/import": {
      "post": {
        "operationId": "importMenu",
        "tags": [
          "Menu"
        ],
        "summary": "Import a menu",
        "description": "Creates or updates the restaurant's menu from your menu, keyed by your IDs: importing the same IDs again updates the same records.\n\nThis call does not check the menu against the schema first, and a menu missing a required field can stop part-way. Validate against `blizzfullMenuSchema.json` before you send. A menu sent without any earlier sync replaces the restaurant's menu.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MenuImport"
              },
              "example": {
                "categories": [
                  {
                    "id": "wings",
                    "name": "Wings",
                    "items": [
                      {
                        "id": "w6",
                        "name": "6 Wings",
                        "price": 9.99,
                        "tags": [
                          "spicy"
                        ],
                        "groups": [
                          {
                            "id": "flavors",
                            "required": true
                          }
                        ]
                      }
                    ]
                  }
                ],
                "option_groups": [
                  {
                    "id": "flavors",
                    "name": "Flavor",
                    "type": "TYPE_SINGLE_SELECT",
                    "options": [
                      {
                        "id": "buffalo",
                        "name": "Buffalo",
                        "price": 0
                      },
                      {
                        "id": "lemon-pepper",
                        "name": "Lemon Pepper",
                        "price": 0.5
                      }
                    ]
                  }
                ],
                "sizes": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Imported.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "response": {
                      "type": "string",
                      "enum": [
                        "success"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "412": {
            "$ref": "#/components/responses/Refused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/menu/importExternalPOSMenu/{posName}": {
      "post": {
        "operationId": "importPointOfSaleMenu",
        "tags": [
          "Menu",
          "Point of sale"
        ],
        "summary": "Send your point of sale's menu",
        "description": "For a point-of-sale partner installed at the restaurant. Checks the menu against the schema, then stores it so the restaurant can map its Blizzfull menu to your items. When your Blizzfull contact has made your system the menu's source of truth, the menu is also imported into the live menu.\n\n- **The first import to the live menu removes everything the payload leaves out.** Try it on your sandbox first.\n- **Your IDs:** orders fetched with `posToMap` carry your item, size and choice IDs. Give every choice an ID that is unique on the menu and has no hyphen: an order names a choice by its ID alone. `/items/getFullMenu` gives Blizzfull's IDs.\n- **Sold out:** an import sets what's sold out, so send it every time (see `/items/availability`).",
        "parameters": [
          {
            "name": "posName",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your integration's name at Blizzfull.",
            "example": "4soft"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MenuImport"
              },
              "example": {
                "categories": [
                  {
                    "id": "wings",
                    "name": "Wings",
                    "items": [
                      {
                        "id": "w6",
                        "name": "6 Wings",
                        "price": 9.99,
                        "tags": [
                          "spicy"
                        ],
                        "groups": [
                          {
                            "id": "flavors",
                            "required": true
                          }
                        ]
                      }
                    ]
                  }
                ],
                "option_groups": [
                  {
                    "id": "flavors",
                    "name": "Flavor",
                    "type": "TYPE_SINGLE_SELECT",
                    "options": [
                      {
                        "id": "buffalo",
                        "name": "Buffalo",
                        "price": 0
                      },
                      {
                        "id": "lemon-pepper",
                        "name": "Lemon Pepper",
                        "price": 0.5
                      }
                    ]
                  }
                ],
                "sizes": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored (and imported, when your system is the menu's source of truth).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "response": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "412": {
            "description": "Refused: the JSON is malformed or doesn't match the schema (`errors` lists each problem), the integration is missing or disabled, or `enforceSyncCompatibility` doesn't match.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders": {
      "post": {
        "operationId": "createOrder",
        "tags": [
          "Orders"
        ],
        "summary": "Place an order",
        "description": "Needs an ordering key. Places a pickup order at your key's restaurant, exactly as you send it: your lines, choices and prices are the order. Blizzfull adds tax, submits it to the restaurant like any online order (their tablet, printers and point of sale take it from there), and returns it as a StandardOrder with the order number and total.\n\nThe customer pays at pickup. The order shows in the restaurant's reports as a phone order.\n\n**Retries are safe:** send a `tempID`. If a request times out, send the same body again; an order already placed with that `tempID` in the last 24 hours comes back (HTTP 200) instead of a second one being made.\n\n**Building lines:** take each item's `id`, size `id` and choices from the menu feed. `totalPrice` is the line's total in cents (item, size and options, times quantity) and becomes the line's price. Every line needs its `name` and `totalPrice`, so an item the menu no longer has is still recorded as the caller ordered it.\n\n**Refused, not dropped (HTTP 412):** payments (`transactions`), `extraCharges`, a line's `customPrice` or `taxExempt`, rewards and gift cards, and an item from another restaurant's menu. Any field not listed below is ignored.\n\nThe response's `customer` is the name and phone you sent; there is no customer account behind it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              },
              "example": {
                "tempID": "kp1001",
                "type": "PICKUP",
                "isFuture": true,
                "fulfillmentTime": "2026-10-28T19:30:00-07:00",
                "customer": {
                  "name": "Dana Rivera",
                  "phone": "(661) 555-0177"
                },
                "note": "Called in by phone",
                "items": [
                  {
                    "id": {
                      "internal": 166519
                    },
                    "name": "Hungry Combo",
                    "quantity": 1,
                    "totalPrice": 1650,
                    "optionSelections": [
                      {
                        "optionGroupId": "216385",
                        "selections": [
                          {
                            "optionItemId": "441415",
                            "selected": 1
                          }
                        ]
                      },
                      {
                        "optionGroupId": "216386",
                        "selections": [
                          {
                            "optionItemId": "441417",
                            "selected": 1
                          }
                        ]
                      }
                    ],
                    "note": "Sauce on the side"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Placed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                },
                "example": {
                  "response": {
                    "order": {
                      "id": {
                        "internal": "2303306",
                        "external": null
                      },
                      "status": "SUBMITTED",
                      "type": "PICKUP",
                      "sourcePlatform": "phone",
                      "isFuture": true,
                      "pickupTime": "2026-10-29T02:30:00+00:00",
                      "fulfillmentTime": "2026-10-29T02:30:00+00:00",
                      "taxRate": "8.2500",
                      "processingVia": "manual",
                      "merchant": {
                        "name": "Example Wings",
                        "id": {
                          "internal": "1234",
                          "external": null
                        },
                        "timezone": "America/Los_Angeles"
                      },
                      "customer": {
                        "id": "",
                        "name": "Dana Rivera",
                        "phone": "+16615550177",
                        "phoneExt": ""
                      },
                      "items": [
                        {
                          "quantity": 1,
                          "orderItemID": 1,
                          "id": {
                            "internal": 166519,
                            "external": null
                          },
                          "name": "Hungry Combo",
                          "note": "Sauce on the side",
                          "unitPrice": 1650,
                          "totalPrice": 1650,
                          "taxRate": 8.25,
                          "optionSelections": [
                            {
                              "optionGroupId": "216385",
                              "selections": [
                                {
                                  "optionItemId": "441415",
                                  "selected": 1
                                }
                              ]
                            },
                            {
                              "optionGroupId": "216386",
                              "selections": [
                                {
                                  "optionItemId": "441417",
                                  "selected": 1
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "totals": {
                        "subtotal": 1650,
                        "tip": 0,
                        "extraCharge": 0,
                        "deliveryFee": 0,
                        "tax": 136,
                        "dspWithheldTax": 0,
                        "total": 1786,
                        "discount": 0,
                        "paid": 0,
                        "balanceOwed": 1786
                      },
                      "transactions": []
                    }
                  }
                }
              }
            }
          },
          "200": {
            "description": "A retry with a `tempID` already used: the order the first request placed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/EngineError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "412": {
            "$ref": "#/components/responses/Refused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "get": {
        "operationId": "listOrders",
        "tags": [
          "Point of sale"
        ],
        "summary": "List orders by day",
        "description": "A restaurant's Blizzfull orders by the day they were placed, in any status, newest first: for order history, and for catching up after your system was down. Orders come in Blizzfull's internal format, as `/orders/poll` gives them without `posToMap`. Marketplace orders have their own list, `/orders/marketplace`.",
        "parameters": [
          {
            "name": "restaurantID",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "The restaurant's ID."
          },
          {
            "name": "dateFilter",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2},[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            },
            "description": "`<first day>,<last day>`: the restaurant's own days, both included.",
            "example": "2026-11-01,2026-11-03"
          },
          {
            "name": "statusFilter",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "notopen",
                "submitted",
                "closed",
                "canceled"
              ],
              "default": "notopen"
            },
            "description": "`notopen` is every placed order."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of orders.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/OrderList"
                    }
                  }
                }
              }
            }
          },
          "412": {
            "description": "`dateFilter` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "dateFilter required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/{orderId}/getWithRestaurantStd": {
      "get": {
        "operationId": "getOrder",
        "tags": [
          "Orders"
        ],
        "summary": "Get an order",
        "description": "One order as a StandardOrder, with the restaurant details a receipt needs. Use it to check an order's status (`SUBMITTED`, `ACCEPTED`, `FULFILLED`, `CANCELED`) and ready time.\n\nAn ordering key reads only the orders it placed, without the payment staff take at pickup (`transactions` is empty; `totals` still shows what is paid and owed). A restaurant key reads any order at its restaurant.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(ex-)?[1-9][0-9]*$"
            },
            "description": "The order number (`id.internal`), or `ex-<n>` for a marketplace order."
          }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "type": "object",
                      "properties": {
                        "order": {
                          "$ref": "#/components/schemas/StandardOrder"
                        },
                        "restaurant": {
                          "type": "object",
                          "description": "The restaurant's name, address, time zone and receipt settings.",
                          "properties": {
                            "restaurantID": {
                              "type": "string",
                              "description": "The Blizzfull restaurant ID."
                            },
                            "name": {
                              "type": "string",
                              "description": "The restaurant's name."
                            },
                            "streetAddress": {
                              "type": "string",
                              "description": "Street address."
                            },
                            "city": {
                              "type": "string",
                              "description": "City."
                            },
                            "state": {
                              "type": "string",
                              "description": "Two-letter state code."
                            },
                            "zip": {
                              "type": "string",
                              "description": "ZIP code."
                            },
                            "timeZone": {
                              "type": "string",
                              "description": "IANA time zone, for showing the order's times locally."
                            },
                            "websiteURL": {
                              "type": "string",
                              "description": "The restaurant's website."
                            }
                          }
                        },
                        "receiptLocale": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "additionalReceiptLocale": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/poll": {
      "get": {
        "operationId": "pollOrders",
        "tags": [
          "Point of sale"
        ],
        "summary": "Pick up new orders",
        "description": "Orders submitted (or accepted) since a time you give, for a point of sale to take into its own ticket flow. Poll every few seconds with the time of the newest order you have.\n\nOrders come in Blizzfull's internal order format; with `posToMap`, each is transformed into your integration's ticket format.",
        "parameters": [
          {
            "name": "orderList",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+(,[01])?$"
            },
            "description": "`<unix seconds>,<0|1>`: orders submitted after that time, or with `,1`, orders accepted after it.",
            "example": "1793142409,0"
          },
          {
            "name": "posToMap",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Your integration's name: returns each order in your ticket format, using the restaurant's item mapping."
          },
          {
            "name": "receiveExternalOrders",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1
              ]
            },
            "description": "1 also returns marketplace orders Blizzfull relays, when they arrive. Requires `posToMap`. `/orders/marketplace` lists them by day, and the order feed says when one changes."
          },
          {
            "name": "returnRaw",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1
              ]
            },
            "description": "1 returns the order list itself, without the `response.orderList.data` wrapping."
          }
        ],
        "responses": {
          "200": {
            "description": "The new orders; `orderList` is absent when there are none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "response": {
                      "type": "object",
                      "properties": {
                        "orderList": {
                          "type": "object",
                          "properties": {
                            "data": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "description": "An order in Blizzfull's internal format, or your ticket format with `posToMap`."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "`posToMap` names an integration the restaurant doesn't have.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "412": {
            "$ref": "#/components/responses/Refused"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/{orderId}/accept": {
      "post": {
        "operationId": "acceptOrder",
        "tags": [
          "Point of sale"
        ],
        "summary": "Accept an order",
        "description": "Confirms an order on the restaurant's behalf with a ready time. The customer is told it was accepted, and a delivery order's courier is booked. Accepting an accepted order changes nothing, so two systems watching one restaurant can't accept it twice.\n\nA marketplace order is the marketplace's to accept, and it usually arrives accepted. Here, this call only says your system has it: a ready time isn't used, and it answers `status` instead of the times below. One the marketplace canceled, or that is done, answers 409.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(ex-)?[1-9][0-9]*$"
            },
            "description": "The order number (`id.internal`), or `ex-<n>` for a marketplace order."
          }
        ],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "readyTime": {
                    "type": "integer",
                    "description": "Minutes until the order is ready. 0 uses the restaurant's default."
                  },
                  "eta": {
                    "type": "string",
                    "description": "When a delivery courier should pick up, ISO 8601 UTC."
                  },
                  "busyMode": {
                    "type": "integer",
                    "enum": [
                      -1,
                      0,
                      1
                    ],
                    "description": "1 turns the restaurant's busy mode on, -1 turns it off, 0 (or left out) leaves it as it is."
                  },
                  "bypassDelayedTime": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ],
                    "description": "1 starts a scheduled order now instead of at its scheduled time."
                  },
                  "bypassCourier": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ],
                    "description": "1 accepts a delivery order without booking a courier."
                  }
                }
              },
              "example": {
                "readyTime": 20
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "readyTime": {
                    "type": "integer",
                    "description": "Minutes until the order is ready. 0 uses the restaurant's default."
                  },
                  "eta": {
                    "type": "string",
                    "description": "When a delivery courier should pick up, ISO 8601 UTC."
                  },
                  "busyMode": {
                    "type": "integer",
                    "enum": [
                      -1,
                      0,
                      1
                    ],
                    "description": "1 turns the restaurant's busy mode on, -1 turns it off, 0 (or left out) leaves it as it is."
                  },
                  "bypassDelayedTime": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ],
                    "description": "1 starts a scheduled order now instead of at its scheduled time."
                  },
                  "bypassCourier": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ],
                    "description": "1 accepts a delivery order without booking a courier."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "response": {
                      "type": "object",
                      "properties": {
                        "accepted": {
                          "type": "integer",
                          "enum": [
                            1
                          ]
                        },
                        "estimatedReadyTime": {
                          "type": [
                            "integer",
                            "string"
                          ],
                          "description": "Unix seconds."
                        },
                        "readyTimeInputValue": {
                          "type": [
                            "integer",
                            "string"
                          ]
                        },
                        "pickupETA": {
                          "type": [
                            "integer",
                            "boolean"
                          ]
                        },
                        "lastModified": {
                          "type": [
                            "integer",
                            "string"
                          ]
                        },
                        "delivery": {
                          "type": "object",
                          "description": "The booked delivery, for delivery orders."
                        },
                        "status": {
                          "type": "string",
                          "description": "For a marketplace order: its status now, as StandardOrder names it."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "409": {
            "description": "A marketplace order that was canceled or is done.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "This marketplace order is canceled."
                }
              }
            }
          }
        }
      }
    },
    "/pubsub/token": {
      "get": {
        "operationId": "getOrderFeedToken",
        "tags": [
          "Order feed"
        ],
        "summary": "Connect to the order feed",
        "description": "Hear about every order at your restaurant the moment it changes, instead of polling for it. Blizzfull sends each change to your point of sale on an [Ably](https://ably.com) websocket channel, and you fetch the order it names.\n\nConnect with an Ably client library:\n- **Auth URL:** this call, `https://apiv2.blizzfull.com/pubsub/token`, with your API user and token as `authHeaders`. A key that serves several restaurants adds `restaurantID` in `authParams`. The library gets a token (good for 24 hours) and a new one before it expires.\n- **Channel:** `restaurant.{restaurantId}.pos`. The token can subscribe to it and nothing else.\n\nEach message's `name` is its event, and its `data.payload` names the order (the other fields are for tracing):\n- `ORDER_SUBMITTED`: a new order.\n- `ORDER_ACCEPTED`: the order was accepted, with `etaMinutes` when a ready time was given.\n- `ORDER_UPDATED`: the order changed.\n- `ORDER_COMPLETED`: the order is ready.\n- `ORDER_CANCELED`: the order was canceled, with `reason` when there is one.\n- `EXTERNAL_ORDER_RECEIVED`, `EXTERNAL_ORDER_UPDATED`: a marketplace order Blizzfull relays, named by `externalOrderId`.\n- `OPEN_ORDER_SUBMITTED`, `OPEN_ORDER_CLOSED`: a table check guests order from a QR code: a round went to the kitchen, or the check was paid.\n\nAn order event's payload has `orderId`, `restaurantId` and `status`, and usually `submittedTime` and `lastModified` (Unix seconds) and `surface` (where the order was placed, such as `web` or `phone`).\n\n```json\n{\n  \"v\": 1,\n  \"topic\": \"restaurant.1234.pos\",\n  \"event\": \"ORDER_SUBMITTED\",\n  \"ts\": 1793575105425,\n  \"seq\": 12,\n  \"actor\": {\"id\": \"system\", \"role\": \"system\"},\n  \"correlation\": \"3f2b8c1e-7d4a-4e9b-a1c5-0d6e8f9a2b47\",\n  \"payload\": {\n    \"orderId\": 6550001,\n    \"restaurantId\": 1234,\n    \"status\": \"submitted\",\n    \"submittedTime\": 1793575105,\n    \"lastModified\": 1793575105,\n    \"surface\": \"web\"\n  }\n}\n```\n\nOn every message, get the order (`GET /orders/{orderId}`, or `GET /orders/ex-{externalOrderId}` for a marketplace order) and do what it needs.\n\n- **Answering:** the feed only tells you. Hearing about an order doesn't pick it up or accept it: call `/orders/{orderId}/accept` and `/orders/{orderId}/status` as usual.\n- **Missed messages:** Ably delivers what you missed when it reconnects within two minutes, and nothing older. After longer, or at startup, catch up with `/orders` and `/orders/marketplace`, then follow the feed. Keep polling `/orders/poll` slowly, about once a minute, as a safety net.\n- **More than one system:** every connection gets every message, so a new system can watch alongside an old one. Decide in your own software which one accepts. Accepting twice changes nothing.\n- **Staying connected:** the Ably library sends its own heartbeats and reconnects. You send nothing.\n\nThe order feed has to be switched on for your restaurant, or this call answers 403: ask your Blizzfull contact. To test, place orders on your sandbox restaurant.",
        "parameters": [
          {
            "name": "restaurantID",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Only for a key that serves several restaurants: the restaurant to listen to."
          }
        ],
        "responses": {
          "200": {
            "description": "An Ably token, as Ably's libraries expect from an auth URL: not wrapped in `response`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderFeedToken"
                },
                "example": {
                  "token": "xVLyHw.EXAMPLE-TOKEN",
                  "keyName": "xVLyHw.EXAMPLE",
                  "issued": 1793568068468,
                  "expires": 1793654468468,
                  "capability": "{\"restaurant.1234.pos\":[\"subscribe\"]}",
                  "clientId": "apiuser:42"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The key may not make this call, or the order feed is not switched on for its restaurant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "The order feed is not switched on for this restaurant."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/{orderId}": {
      "get": {
        "operationId": "getOrderTicket",
        "tags": [
          "Order feed",
          "Point of sale"
        ],
        "summary": "Get an order in your ticket format",
        "description": "The order a message names, in your ticket format (the same format `/orders/poll` returns with `posToMap`), with your item IDs on its lines. For a marketplace order, use `ex-` and the message's `externalOrderId`: `/orders/ex-284417`.\n\nFor a table check, send the kitchen every line whose `orderItemID` you haven't sent, and close the check when `status` is `closed`. Line numbers only grow and are never reused, so getting the same order twice changes nothing. Keep each line's `orderItemID` on your own check, so a restart doesn't send a line twice.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(ex-)?[1-9][0-9]*$"
            },
            "description": "The message's `orderId`, or `ex-` and its `externalOrderId` for a marketplace order."
          },
          {
            "name": "posToMap",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your integration's name, as for `/orders/poll`: the order comes in your ticket format, using the restaurant's item mapping."
          }
        ],
        "responses": {
          "200": {
            "description": "The order (shortened here: it has every field your poll's orders have).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/TicketOrder"
                    }
                  }
                },
                "example": {
                  "response": {
                    "id": "6550001",
                    "status": "submitted",
                    "accepted": false,
                    "type": "take-out",
                    "tableID": "",
                    "items": [
                      {
                        "orderItemID": 1,
                        "itemID": 166519,
                        "externalID": "HC-01",
                        "externalSizeID": null,
                        "name": "Hungry Combo",
                        "quantity": 2,
                        "specialInstructions": "Sauce on the side",
                        "externalOptionMapMask": [
                          [
                            {
                              "externalID": "STYLE-WINGS",
                              "sel": 1
                            }
                          ],
                          [
                            {
                              "externalID": "FLAVOR-LEMON-PEPPER",
                              "sel": 1
                            }
                          ]
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "412": {
            "description": "`posToMap` names an integration the restaurant doesn't have.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/marketplace": {
      "get": {
        "operationId": "listMarketplaceOrders",
        "tags": [
          "Point of sale"
        ],
        "summary": "List marketplace orders by day",
        "description": "A restaurant's marketplace orders (DoorDash, Uber Eats and the others Blizzfull relays) by the day they arrived, newest first: for catching up after your system was down, and for history. `/orders/poll` gives the new ones as they come, and the order feed says when one changes.\n\nA marketplace takes its own payment and refunds its own guests. It accepted the order before Blizzfull received it, so these orders usually arrive `accepted`.",
        "parameters": [
          {
            "name": "dateFilter",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2},[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            },
            "description": "`<first day>,<last day>`: the restaurant's own days, both included, 31 days at most.",
            "example": "2026-11-01,2026-11-03"
          },
          {
            "name": "statusFilter",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "notopen",
                "SUBMITTED",
                "ACCEPTED",
                "FULFILLED",
                "CANCELED"
              ],
              "default": "notopen"
            },
            "description": "`notopen` is all of them."
          },
          {
            "name": "posToMap",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Your integration's name, as for `/orders/poll`: each order comes with your item IDs on its lines."
          },
          {
            "name": "restaurantID",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "For a key that serves several restaurants: which one."
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of marketplace orders, each in the same format as `/orders/poll`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/OrderList"
                    }
                  }
                },
                "example": {
                  "response": {
                    "result": [
                      {
                        "id": "ex-284417",
                        "status": "accepted"
                      }
                    ],
                    "totalFound": 1
                  }
                }
              }
            }
          },
          "404": {
            "description": "`posToMap` names an integration the restaurant doesn't have.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "412": {
            "description": "`dateFilter` or `statusFilter` is not valid, or the key serves several restaurants and `restaurantID` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "dateFilter must be <first day>,<last day> as YYYY-MM-DD"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/{orderId}/status": {
      "post": {
        "operationId": "setOrderStatus",
        "tags": [
          "Point of sale"
        ],
        "summary": "Say where an order is now",
        "description": "Moves an accepted order on from your kitchen.\n\n| | Pickup or dine-in | Delivery | Marketplace order |\n|---|---|---|---|\n| `READY` | The order closes, and the customer is told it's ready | Changes nothing, so the customer isn't told it's on its way while it's still in the kitchen | The marketplace is told it's ready for pickup |\n| `OUT_FOR_DELIVERY` | 409 | The order closes, and the customer is told it's on its way | 409: the marketplace runs its own deliveries |\n| `FULFILLED` | The order closes, if it hasn't | The order closes, if it hasn't | The marketplace is told it's done |\n\nThe customer gets a text when the restaurant has switched on ready texts. Repeating a status changes nothing. Your order feed says `ORDER_COMPLETED` when an order closes.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(ex-)?[1-9][0-9]*$"
            },
            "description": "The order number (`id.internal`), or `ex-<n>` for a marketplace order."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "READY",
                      "OUT_FOR_DELIVERY",
                      "FULFILLED"
                    ]
                  }
                }
              },
              "example": {
                "status": "READY"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order's status now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/OrderStatusResult"
                    }
                  }
                },
                "example": {
                  "response": {
                    "status": "FULFILLED",
                    "lastModified": 1793742138
                  }
                }
              }
            }
          },
          "409": {
            "description": "The order isn't accepted yet, was canceled, or can't take this status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "Accept the order first."
                }
              }
            }
          },
          "412": {
            "description": "`status` is not one of these.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "502": {
            "description": "A marketplace order: the marketplace didn't confirm it, so nothing changed. Send it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/{orderId}/cancel": {
      "post": {
        "operationId": "cancelOrder",
        "tags": [
          "Point of sale"
        ],
        "summary": "Cancel an order",
        "description": "Cancels an order and gives back everything paid, on the payments made. The customer is emailed when the order has their email, a booked courier is canceled, and your order feed says `ORDER_CANCELED`.\n\nA marketplace order is canceled at the marketplace, which refunds its guest. Your feed says `EXTERNAL_ORDER_UPDATED`.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^(ex-)?[1-9][0-9]*$"
            },
            "description": "The order number (`id.internal`), or `ex-<n>` for a marketplace order."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Kept on the order."
                  },
                  "cancelType": {
                    "type": "integer",
                    "enum": [
                      1,
                      2
                    ],
                    "default": 1,
                    "description": "1 refunds what was paid. 2 cancels without refunding."
                  }
                }
              },
              "example": {
                "reason": "Customer called to cancel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order's status now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/OrderStatusResult"
                    }
                  }
                },
                "example": {
                  "response": {
                    "status": "CANCELED",
                    "lastModified": 1793742138
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/EngineError"
          },
          "409": {
            "description": "The order was already canceled, or was never placed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "This order was already canceled."
                }
              }
            }
          },
          "412": {
            "description": "`reason` is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "reason required"
                }
              }
            }
          },
          "502": {
            "description": "A marketplace order: the marketplace didn't confirm it, so nothing changed. Send it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/orders/{orderId}/refund": {
      "post": {
        "operationId": "refundOrder",
        "tags": [
          "Point of sale"
        ],
        "summary": "Give part of an order back",
        "description": "Changes the order, then gives the difference back on the payments made, newest first, so the order stays balanced:\n\n- **`items`** take lines off, or part of a line's quantity.\n- **`amount`** comes off as a discount, in cents and tax included, to within a cent.\n\nA card still held before capture is charged less. Any other card is refunded on its original charge. Cash is recorded, and the answer's `cash` says what to hand back. To give back everything, cancel the order. A marketplace refunds its own orders, so a marketplace order answers 409.\n\nYour order feed says `ORDER_UPDATED`.",
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]*$"
            },
            "description": "The order number (`id.internal`)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Kept in the order's log."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "orderItemID",
                        "quantity"
                      ],
                      "properties": {
                        "orderItemID": {
                          "type": "integer",
                          "description": "The line's `orderItemID`."
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "How many to take off. All of them removes the line."
                        }
                      }
                    }
                  },
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Cents to give back, tax included."
                  }
                }
              },
              "examples": {
                "items": {
                  "summary": "One of a line",
                  "value": {
                    "reason": "Out of wings",
                    "items": [
                      {
                        "orderItemID": 1,
                        "quantity": 1
                      }
                    ]
                  }
                },
                "amount": {
                  "summary": "An amount",
                  "value": {
                    "reason": "Cold fries",
                    "amount": 300
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What went back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/RefundResult"
                    }
                  }
                },
                "example": {
                  "response": {
                    "refunded": 300,
                    "cash": 0,
                    "pending": 0,
                    "willRetry": false,
                    "lastModified": 1793742138
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/EngineError"
          },
          "409": {
            "description": "The order was canceled or was never placed, or it is a marketplace order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "A marketplace refunds its own orders."
                }
              }
            }
          },
          "412": {
            "description": "`reason`, `items` or `amount` isn't valid, or `amount` is more than was paid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "Send items or amount. To refund everything, cancel the order."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/items/availability": {
      "get": {
        "operationId": "getSoldOut",
        "tags": [
          "Menu"
        ],
        "summary": "List what's sold out",
        "description": "Every item and option choice sold out now, with your IDs from your menu import. Items hidden from the menu aren't listed: only what's sold out.",
        "parameters": [
          {
            "name": "posName",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The integration whose menu import gave Blizzfull your IDs, as in `/menu/importExternalPOSMenu/{posName}`."
          },
          {
            "name": "restaurantID",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "For a key that serves several restaurants: which one."
          }
        ],
        "responses": {
          "200": {
            "description": "What's sold out.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/SoldOut"
                    }
                  }
                },
                "example": {
                  "response": {
                    "soldOut": [
                      {
                        "type": "item",
                        "externalID": "HC-01",
                        "id": 166519,
                        "name": "Hungry Combo",
                        "until": 1793742138
                      },
                      {
                        "type": "option",
                        "externalID": "FLAVOR-LEMON-PEPPER",
                        "group": "FLAVORS",
                        "id": 441382,
                        "name": "Lemon Pepper",
                        "until": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "`posName` names an integration the restaurant doesn't have.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "setAvailability",
        "tags": [
          "Menu"
        ],
        "summary": "Mark items sold out, or back",
        "description": "Marks items and option choices sold out (86) or available, by your IDs from your menu import, without sending the whole menu. Customers see it at once on the restaurant's own ordering, and the marketplaces are asked to fetch the menu again.\n\nA menu import sets these too: an item's `active` says whether it's sold out, and a choice's `inactiveEnds`. So when your system is the menu's source of truth, send what's sold out in every import, or the import puts it back on sale.",
        "parameters": [
          {
            "name": "posName",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The integration whose menu import gave Blizzfull your IDs."
          },
          {
            "name": "restaurantID",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "For a key that serves several restaurants: which one."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "minItems": 1,
                "maxItems": 200,
                "items": {
                  "$ref": "#/components/schemas/AvailabilityChange"
                }
              },
              "example": [
                {
                  "type": "item",
                  "externalID": "HC-01",
                  "available": false,
                  "until": 1793742138
                },
                {
                  "type": "option",
                  "externalID": "FLAVOR-LEMON-PEPPER",
                  "available": true
                }
              ]
            }
          }
        },
        "responses": {
          "200": {
            "description": "What changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/AvailabilityResult"
                    }
                  }
                },
                "example": {
                  "response": {
                    "updated": [
                      {
                        "type": "item",
                        "externalID": "HC-01"
                      },
                      {
                        "type": "option",
                        "externalID": "FLAVOR-LEMON-PEPPER"
                      }
                    ],
                    "notFound": [],
                    "marketplaceMenuRefreshed": true
                  }
                }
              }
            }
          },
          "404": {
            "description": "`posName` names an integration the restaurant doesn't have.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "412": {
            "description": "A change is not valid. The message says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "until is a future time in Unix seconds, for a sold-out change (change 0)"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/restaurants/{restaurantId}/ordering": {
      "get": {
        "operationId": "getOrdering",
        "tags": [
          "Store"
        ],
        "summary": "Read whether the restaurant is taking orders",
        "description": "Whether ordering is paused, and whether busy mode is on. The restaurant's hours are separate: `orderingEnabled` is true when nothing is paused, even while the restaurant is closed.",
        "parameters": [
          {
            "name": "restaurantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "The restaurant's ID."
          }
        ],
        "responses": {
          "200": {
            "description": "The state of ordering.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/OrderingState"
                    }
                  }
                },
                "example": {
                  "response": {
                    "takingOrders": true,
                    "closedUntil": null,
                    "orderingEnabled": true,
                    "busyMode": {
                      "on": false,
                      "pickupMinutes": 40,
                      "deliveryMinutes": 70
                    },
                    "pickupMinutes": 20,
                    "deliveryMinutes": {
                      "min": 30,
                      "max": 45
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "setOrdering",
        "tags": [
          "Store"
        ],
        "summary": "Pause ordering, or set busy mode",
        "description": "Stops or slows online orders right away, without waiting for an order to accept. Send what you want to change.\n\nWhere the restaurant syncs its pauses to the marketplaces, a pause and its end reach them too: every marketplace pauses and resumes with Blizzfull ordering. `/restaurants/{restaurantId}/marketplaces` pauses them on their own.",
        "parameters": [
          {
            "name": "restaurantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "The restaurant's ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "takingOrders": {
                    "type": "boolean",
                    "description": "False pauses ordering until it is turned back on. True takes orders again, and ends a timed pause."
                  },
                  "closedUntil": {
                    "description": "Pauses ordering until this time, in Unix seconds, or `\"restOfDay\"` until the restaurant next opens tomorrow or later. Null ends a timed pause.",
                    "oneOf": [
                      {
                        "type": "integer"
                      },
                      {
                        "type": "string",
                        "enum": [
                          "restOfDay"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "busyMode": {
                    "type": "object",
                    "description": "Longer quoted times. It ends when you turn it off, or overnight.",
                    "required": [
                      "on"
                    ],
                    "properties": {
                      "on": {
                        "type": "boolean"
                      },
                      "pickupMinutes": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 255,
                        "description": "The pickup quote while busy (not extra minutes). Required the first time."
                      },
                      "deliveryMinutes": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 255,
                        "description": "The delivery quote while busy."
                      }
                    }
                  }
                }
              },
              "examples": {
                "pause": {
                  "summary": "Pause until turned back on",
                  "value": {
                    "takingOrders": false
                  }
                },
                "timed": {
                  "summary": "Pause for the rest of the day",
                  "value": {
                    "closedUntil": "restOfDay"
                  }
                },
                "resume": {
                  "summary": "Take orders again",
                  "value": {
                    "takingOrders": true
                  }
                },
                "busy": {
                  "summary": "Busy mode",
                  "value": {
                    "busyMode": {
                      "on": true,
                      "pickupMinutes": 40,
                      "deliveryMinutes": 70
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The state of ordering now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/OrderingState"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "`restOfDay`, and the restaurant's hours don't say when it opens next. Send a time.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "The restaurant's hours don't say when it opens next. Send closedUntil as a time."
                }
              }
            }
          },
          "412": {
            "description": "A value is not valid. The message says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "takingOrders is true or false"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/restaurants/{restaurantId}/marketplaces": {
      "get": {
        "operationId": "getMarketplaces",
        "tags": [
          "Store"
        ],
        "summary": "Read third-party ordering",
        "description": "The marketplaces (DoorDash, Uber Eats and the others) that order at the restaurant's locations, and which are paused. This is what Blizzfull last sent them: a pause made on a marketplace's own tablet doesn't show here.",
        "parameters": [
          {
            "name": "restaurantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "The restaurant's ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Each location and its marketplaces.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/MarketplaceState"
                    }
                  }
                },
                "example": {
                  "response": {
                    "locations": [
                      {
                        "id": "1234",
                        "name": "Kin Da",
                        "connected": true,
                        "marketplaces": [
                          {
                            "marketplace": "doordash",
                            "name": "DoorDash",
                            "paused": true,
                            "pausedUntil": 1793742138,
                            "changedAt": 1793738538
                          },
                          {
                            "marketplace": "uber",
                            "name": "Uber Eats",
                            "paused": false,
                            "pausedUntil": null,
                            "changedAt": null
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "The restaurant doesn't take marketplace orders through Blizzfull.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "operationId": "setMarketplaces",
        "tags": [
          "Store"
        ],
        "summary": "Turn third-party ordering off or on",
        "description": "Pauses or resumes one marketplace, or all of them, at one location or all of them. Blizzfull ordering carries on.\n\nWhere the restaurant syncs its pauses to the marketplaces, pausing and resuming all of Blizzfull ordering (`/restaurants/{restaurantId}/ordering`) also resumes every marketplace paused here.",
        "parameters": [
          {
            "name": "restaurantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "The restaurant's ID."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "paused"
                ],
                "properties": {
                  "paused": {
                    "type": "boolean"
                  },
                  "marketplace": {
                    "type": "string",
                    "enum": [
                      "doordash",
                      "uber",
                      "grubhub",
                      "jet",
                      "deliveroo",
                      "skipthedishes",
                      "flipdish",
                      "chownow"
                    ],
                    "description": "Left out: all of them."
                  },
                  "until": {
                    "type": "integer",
                    "description": "For a pause: when it comes back on its own, in Unix seconds. Left out: until you resume it."
                  },
                  "location": {
                    "type": "string",
                    "description": "One location's `id`, for a restaurant with virtual brands. Left out: all of them."
                  }
                }
              },
              "examples": {
                "one": {
                  "summary": "Pause Uber Eats for an hour",
                  "value": {
                    "paused": true,
                    "marketplace": "uber",
                    "until": 1793742138
                  }
                },
                "all": {
                  "summary": "Pause every marketplace",
                  "value": {
                    "paused": true
                  }
                },
                "resume": {
                  "summary": "Resume every marketplace",
                  "value": {
                    "paused": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Each location and its marketplaces now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "$ref": "#/components/schemas/MarketplaceState"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The restaurant doesn't take marketplace orders through Blizzfull.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "412": {
            "description": "A value is not valid. The message says which.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "paused is required: true or false"
                }
              }
            }
          },
          "502": {
            "description": "The marketplace connection didn't confirm the change at a location. Send it again. The message names any location it went through at.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                },
                "example": {
                  "response": "The marketplace connection didn't confirm the change at location 1234. Send it again."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiUser": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Blizzfull-API-User",
        "description": "Your API user name."
      },
      "ApiToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Blizzfull-API-Token",
        "description": "Your API token. Keep it secret; it is shown once."
      }
    },
    "schemas": {
      "StandardOrder": {
        "$ref": "standard-order.schema.json"
      },
      "MenuFeed": {
        "$ref": "menu-feed.schema.json"
      },
      "MenuImport": {
        "$ref": "blizzfullMenuSchema.json"
      },
      "OrderResponse": {
        "type": "object",
        "required": [
          "response"
        ],
        "properties": {
          "response": {
            "type": "object",
            "required": [
              "order"
            ],
            "properties": {
              "order": {
                "$ref": "#/components/schemas/StandardOrder"
              }
            }
          }
        }
      },
      "CreateOrderRequest": {
        "type": "object",
        "description": "A pickup order, in StandardOrder's shape. Only these fields are read.",
        "required": [
          "type",
          "customer",
          "items"
        ],
        "properties": {
          "tempID": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,8}$",
            "description": "Your reference for this order, 1 to 8 letters, digits, `-` or `_`, unique per order. Makes retries safe."
          },
          "type": {
            "type": "string",
            "enum": [
              "PICKUP"
            ]
          },
          "isFuture": {
            "type": "boolean",
            "description": "True for an order scheduled for `fulfillmentTime`. Leave out for as soon as possible."
          },
          "fulfillmentTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the customer will pick up, with an offset. Read only when `isFuture` is true."
          },
          "delayedTime": {
            "type": "integer",
            "description": "Alternative to `isFuture` + `fulfillmentTime`: the pickup time in Unix seconds. 0 means as soon as possible."
          },
          "customer": {
            "type": "object",
            "required": [
              "phone"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "Required (or firstName): staff call it out at pickup and it prints on the ticket. Split into first and last name at the first space."
              },
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "description": "The callback number; at least 10 digits, any formatting.",
                "examples": [
                  "(661) 555-0177"
                ]
              },
              "phoneExt": {
                "type": "string"
              }
            },
            "description": "The caller. The order is a guest order: this name and phone stay on it and are never matched to, created as or saved on a Blizzfull customer account.",
            "anyOf": [
              {
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              },
              {
                "required": [
                  "firstName"
                ],
                "properties": {
                  "firstName": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            ]
          },
          "note": {
            "type": "string",
            "description": "Instructions for the whole order."
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "totalPrice"
              ],
              "properties": {
                "id": {
                  "type": "object",
                  "required": [
                    "internal"
                  ],
                  "properties": {
                    "internal": {
                      "type": "integer",
                      "description": "The item's `id` from the menu feed."
                    }
                  }
                },
                "name": {
                  "type": "string",
                  "description": "The item's name, as the caller ordered it."
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1,
                  "default": 1
                },
                "totalPrice": {
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "description": "The line total in cents: item, size and options, times quantity."
                },
                "size": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "object",
                      "properties": {
                        "internal": {
                          "type": [
                            "string",
                            "integer"
                          ],
                          "description": "The size's `id` from the menu feed."
                        }
                      }
                    }
                  }
                },
                "optionSelections": {
                  "type": "array",
                  "description": "The caller's choices, as StandardOrder's OptionGroupSelection: each group's optionGroupId with its selections (optionItemId, selected), and sub-options one level deep. List what differs from the menu's defaults. Anything else in this list is refused (412).",
                  "items": {
                    "$ref": "standard-order.schema.json#/$defs/OptionGroupSelection"
                  }
                },
                "note": {
                  "type": "string",
                  "description": "Instructions for this line."
                }
              }
            }
          },
          "discounts": {
            "type": "array",
            "description": "Order-level discounts. Their amounts are added up and taken off the order (never below zero); names are not kept.",
            "items": {
              "type": "object",
              "required": [
                "amount"
              ],
              "properties": {
                "amount": {
                  "type": "integer",
                  "description": "Cents."
                }
              }
            }
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "response"
        ],
        "properties": {
          "response": {
            "type": "string",
            "description": "What happened, or what to fix."
          }
        }
      },
      "EngineErrorBody": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "exception"
            ]
          },
          "message": {
            "type": "string"
          },
          "code": {
            "type": "integer"
          },
          "trace": {
            "type": "null"
          },
          "request": {
            "type": "string"
          }
        }
      },
      "ImportError": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "message": {
            "type": "string"
          },
          "code": {
            "type": "integer"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "property": {
                  "type": "string",
                  "description": "Where, as a path (`option_groups[0].name`)."
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Storefront": {
        "type": "object",
        "description": "What a caller needs to know about the restaurant, at the location you order for. An ordering key gets exactly these fields.",
        "required": [
          "id",
          "name",
          "phone",
          "timeZone",
          "taxRate",
          "takingOrders",
          "closedUntil",
          "hoursDataPickup",
          "pickupTime"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The Blizzfull restaurant ID."
          },
          "name": {
            "type": "string",
            "description": "The restaurant's name."
          },
          "description": {
            "type": "string",
            "description": "The restaurant's description, as its website shows it. May be empty."
          },
          "phone": {
            "type": "string",
            "description": "The restaurant's phone number, digits only."
          },
          "streetAddress": {
            "type": "string",
            "description": "Street address. May be empty."
          },
          "city": {
            "type": "string",
            "description": "City."
          },
          "state": {
            "type": "string",
            "description": "Two-letter state code."
          },
          "zip": {
            "type": "string",
            "description": "ZIP code."
          },
          "geoLocation": {
            "type": "object",
            "description": "Where the restaurant is.",
            "properties": {
              "latitude": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "longitude": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "timeZone": {
            "type": "string",
            "description": "IANA time zone. Hours and times below are in it.",
            "examples": [
              "America/Los_Angeles"
            ]
          },
          "taxRate": {
            "type": "number",
            "description": "Sales tax, as a percentage (8.25 is 8.25%). Blizzfull adds it to every order.",
            "examples": [
              8.25
            ]
          },
          "takingOrders": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ],
            "description": "`\"1\"` while the restaurant takes online orders; `\"0\"` when it has turned them off."
          },
          "closedUntil": {
            "type": "integer",
            "description": "Unix seconds: the restaurant has paused online orders until then. 0 or a time already past means it has not."
          },
          "hoursDataPickup": {
            "type": "array",
            "description": "Pickup hours: `[weekly, dates]`. `weekly` has seven entries, Sunday first; each is a list of `[open, close]` pairs in minutes after local midnight (`[[660, 1260]]` is 11 AM to 9 PM), and an empty list is closed. `dates` is `[]`, or an object keyed `MM/DD` whose hours replace the weekly ones on that date.",
            "minItems": 2,
            "maxItems": 2,
            "prefixItems": [
              {
                "type": "array",
                "items": {
                  "type": "array",
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "minItems": 2,
                    "maxItems": 2
                  }
                }
              },
              {
                "type": [
                  "array",
                  "object"
                ]
              }
            ]
          },
          "pickupTime": {
            "type": "string",
            "description": "The usual minutes until a pickup order is ready, as a decimal string.",
            "examples": [
              "15"
            ]
          },
          "busyMode": {
            "type": "boolean",
            "description": "True while the restaurant is busy and quoting longer times."
          },
          "busyModePickupTime": {
            "type": "integer",
            "description": "While `busyMode` is on, the minutes it quotes for pickup (0 when not set: use `pickupTime`)."
          },
          "pickupInstructions": {
            "type": "string",
            "description": "What to tell the customer about picking up. May be empty."
          }
        }
      },
      "OrderFeedToken": {
        "type": "object",
        "description": "Ably TokenDetails: what Ably's client libraries expect from an auth URL.",
        "required": [
          "token",
          "keyName",
          "issued",
          "expires",
          "capability"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The token."
          },
          "keyName": {
            "type": "string",
            "description": "The Ably key that issued it."
          },
          "issued": {
            "type": "integer",
            "description": "When it was issued, in Unix milliseconds."
          },
          "expires": {
            "type": "integer",
            "description": "When it expires, in Unix milliseconds: 24 hours after `issued`."
          },
          "capability": {
            "type": "string",
            "description": "What it allows, as JSON: subscribing to your restaurant's channel."
          },
          "clientId": {
            "type": "string",
            "description": "`apiuser:` and your API user's ID."
          }
        }
      },
      "TicketOrder": {
        "type": "object",
        "description": "An order in your ticket format: the same format `/orders/poll` returns with `posToMap`, so it has more fields than these.",
        "required": [
          "id",
          "status",
          "items"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^(ex-)?[1-9][0-9]*$",
            "description": "The order number: the message's `orderId`, or `ex-<n>` for a marketplace order."
          },
          "status": {
            "type": "string",
            "description": "`submitted` when it is new, `closed` once it is ready (a table check: once it is paid), `canceled`, or `open` while a table check is still ordering. A marketplace order is `submitted`, `accepted`, `fulfilled` or `canceled`.",
            "examples": [
              "submitted"
            ]
          },
          "accepted": {
            "type": "boolean",
            "description": "Whether the restaurant has accepted it."
          },
          "type": {
            "type": "string",
            "description": "How it is served, such as `take-out`, `delivery` or `for-here`.",
            "examples": [
              "take-out"
            ]
          },
          "tableID": {
            "type": "string",
            "description": "The table, for a table check; empty otherwise."
          },
          "items": {
            "type": "array",
            "description": "The order's lines (for a table check, every line the table has sent).",
            "items": {
              "type": "object",
              "required": [
                "orderItemID",
                "itemID",
                "name",
                "quantity"
              ],
              "properties": {
                "orderItemID": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "The line's number in the order. Numbers only grow and are never reused."
                },
                "itemID": {
                  "type": [
                    "integer",
                    "string"
                  ],
                  "description": "Blizzfull's item ID."
                },
                "externalID": {
                  "type": [
                    "string",
                    "integer",
                    "null"
                  ],
                  "description": "Your item ID, from the restaurant's item mapping; null when the item isn't mapped."
                },
                "externalSizeID": {
                  "type": [
                    "string",
                    "integer",
                    "null"
                  ],
                  "description": "Your size ID, when the size is mapped."
                },
                "name": {
                  "type": "string",
                  "description": "The item's name."
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "How many."
                },
                "specialInstructions": {
                  "type": "string",
                  "description": "The guest's note for the kitchen."
                },
                "externalOptionMapMask": {
                  "type": "array",
                  "description": "The guest's choices with your option IDs: one list for each option group.",
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "externalID": {
                          "type": [
                            "string",
                            "integer",
                            "null"
                          ],
                          "description": "Your option ID."
                        },
                        "sel": {
                          "type": [
                            "integer",
                            "string"
                          ],
                          "description": "The selection, as in your poll's orders: a number, or a string for an option sold by amount."
                        },
                        "subOptions": {
                          "type": "array",
                          "description": "Choices made inside this choice.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "externalID": {
                                "type": [
                                  "string",
                                  "integer",
                                  "null"
                                ],
                                "description": "Your option ID."
                              },
                              "sel": {
                                "type": [
                                  "integer",
                                  "string"
                                ],
                                "description": "The selection, as in your poll's orders: a number, or a string for an option sold by amount."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "OrderList": {
        "type": "object",
        "description": "One page of orders.",
        "required": [
          "result",
          "totalFound"
        ],
        "properties": {
          "result": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "status"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "pattern": "^(ex-)?[1-9][0-9]*$"
                },
                "status": {
                  "type": "string"
                }
              }
            }
          },
          "totalFound": {
            "type": [
              "integer",
              "string"
            ],
            "description": "How many orders match, on every page."
          }
        }
      },
      "OrderStatusResult": {
        "type": "object",
        "required": [
          "status",
          "lastModified"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ACCEPTED",
              "FULFILLED",
              "CANCELED"
            ],
            "description": "The order's status now, as StandardOrder names it."
          },
          "lastModified": {
            "type": "integer",
            "description": "Unix seconds."
          }
        }
      },
      "RefundResult": {
        "type": "object",
        "required": [
          "refunded",
          "cash",
          "pending",
          "willRetry",
          "lastModified"
        ],
        "properties": {
          "refunded": {
            "type": "integer",
            "description": "Cents given back now."
          },
          "cash": {
            "type": "integer",
            "description": "Of those, the cents paid in cash: hand them back at the counter."
          },
          "pending": {
            "type": "integer",
            "description": "Cents still to go back."
          },
          "willRetry": {
            "type": "boolean",
            "description": "Blizzfull retries `pending` later (a card that hasn't settled, within 12 hours). When false and `pending` is above 0, the restaurant's staff settle it from the order in their dashboard."
          },
          "lastModified": {
            "type": "integer",
            "description": "Unix seconds."
          }
        }
      },
      "OrderingState": {
        "type": "object",
        "required": [
          "takingOrders",
          "closedUntil",
          "orderingEnabled",
          "busyMode",
          "pickupMinutes",
          "deliveryMinutes"
        ],
        "properties": {
          "takingOrders": {
            "type": "boolean",
            "description": "False while ordering is paused until someone turns it back on."
          },
          "closedUntil": {
            "type": [
              "integer",
              "null"
            ],
            "description": "While ordering is paused until a time: that time, in Unix seconds."
          },
          "orderingEnabled": {
            "type": "boolean",
            "description": "Whether customers can order now, hours aside: `takingOrders`, and no pause running."
          },
          "busyMode": {
            "type": "object",
            "required": [
              "on",
              "pickupMinutes",
              "deliveryMinutes"
            ],
            "properties": {
              "on": {
                "type": "boolean"
              },
              "pickupMinutes": {
                "type": "integer",
                "description": "Quoted for pickup while busy mode is on."
              },
              "deliveryMinutes": {
                "type": "integer",
                "description": "Quoted for delivery while busy mode is on."
              }
            }
          },
          "pickupMinutes": {
            "type": "integer",
            "description": "The usual pickup quote."
          },
          "deliveryMinutes": {
            "type": "object",
            "description": "The usual delivery quote.",
            "required": [
              "min",
              "max"
            ],
            "properties": {
              "min": {
                "type": "integer"
              },
              "max": {
                "type": "integer"
              }
            }
          }
        }
      },
      "MarketplaceState": {
        "type": "object",
        "required": [
          "locations"
        ],
        "properties": {
          "locations": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "A location the marketplaces know: the restaurant's own, or a virtual brand's.",
              "required": [
                "id",
                "name",
                "connected",
                "marketplaces"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "What `location` takes."
                },
                "name": {
                  "type": "string"
                },
                "connected": {
                  "type": "boolean",
                  "description": "Whether the marketplaces connection is live at this location."
                },
                "marketplaces": {
                  "type": "array",
                  "description": "The marketplaces this location took an order from in the last 90 days, or that were paused or reported on.",
                  "items": {
                    "type": "object",
                    "required": [
                      "marketplace",
                      "name",
                      "paused",
                      "pausedUntil",
                      "changedAt"
                    ],
                    "properties": {
                      "marketplace": {
                        "type": "string",
                        "description": "What `marketplace` takes, and what an order calls `sourcePlatform`.",
                        "examples": [
                          "doordash"
                        ]
                      },
                      "name": {
                        "type": "string",
                        "examples": [
                          "DoorDash"
                        ]
                      },
                      "paused": {
                        "type": "boolean"
                      },
                      "pausedUntil": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "When it comes back on its own, in Unix seconds. Null while paused means until you resume it."
                      },
                      "changedAt": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "When it was last paused or resumed, in Unix seconds."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "SoldOut": {
        "type": "object",
        "required": [
          "soldOut"
        ],
        "properties": {
          "soldOut": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "externalID",
                "id",
                "name",
                "until"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "item",
                    "option"
                  ]
                },
                "externalID": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Your ID for it. Null when your menu import has no such item or choice, such as one added in Blizzfull."
                },
                "group": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "For a choice: your ID for its option group."
                },
                "id": {
                  "type": "integer",
                  "description": "Blizzfull's ID for it."
                },
                "name": {
                  "type": "string"
                },
                "until": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "When it comes back on its own, in Unix seconds. Null means until you mark it available."
                }
              }
            }
          }
        }
      },
      "AvailabilityChange": {
        "type": "object",
        "required": [
          "type",
          "externalID",
          "available"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "item",
              "option"
            ],
            "description": "An item, or an option choice. Blizzfull has no sold-out sizes: mark the item."
          },
          "externalID": {
            "type": "string",
            "description": "Your ID for it, from your menu import. A choice is marked in every option group that has it."
          },
          "available": {
            "type": "boolean"
          },
          "until": {
            "type": "integer",
            "description": "For `available: false`: when it comes back on its own, in Unix seconds. Left out: until you mark it available."
          }
        }
      },
      "AvailabilityResult": {
        "type": "object",
        "required": [
          "updated",
          "notFound",
          "marketplaceMenuRefreshed"
        ],
        "properties": {
          "updated": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The changes made, by `type` and `externalID`."
          },
          "notFound": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The ones your menu import doesn't have."
          },
          "marketplaceMenuRefreshed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the marketplaces were asked to fetch the menu again. Null when the restaurant doesn't take marketplace orders. False means the request failed: the change is saved here and reaches them with the next one."
          }
        }
      }
    },
    "responses": {
      "Refused": {
        "description": "Refused: the message names the field to fix.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Message"
            },
            "example": {
              "response": "items[0].totalPrice must be the line total in cents"
            }
          }
        }
      },
      "EngineError": {
        "description": "The order engine couldn't take the order, for example a choice that isn't on the item.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/EngineErrorBody"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API user or token is missing or wrong.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Message"
            },
            "example": {
              "response": "Unauthorized"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key may not make this call, or not for this restaurant or order.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Over the rate limit. Wait `Retry-After` seconds.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Message"
            }
          }
        }
      }
    }
  }
}
