{
  "components": {
    "schemas": {
      "V1CancelOrder": {
        "properties": {
          "reason": {
            "maxLength": 140,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "V1CreateCustomer": {
        "properties": {
          "email": {
            "format": "email",
            "maxLength": 320,
            "type": "string"
          },
          "name": {
            "maxLength": 160,
            "minLength": 1,
            "type": "string"
          },
          "phone": {
            "maxLength": 40,
            "minLength": 3,
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "V1CreateOrder": {
        "properties": {
          "customer": {
            "properties": {
              "addressLines": {
                "items": {
                  "maxLength": 200,
                  "minLength": 1,
                  "type": "string"
                },
                "maxItems": 6,
                "type": "array"
              },
              "name": {
                "maxLength": 100,
                "minLength": 1,
                "type": "string"
              },
              "phone": {
                "maxLength": 30,
                "minLength": 1,
                "type": "string"
              }
            },
            "type": "object"
          },
          "externalId": {
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "lines": {
            "items": {
              "properties": {
                "kitchenNote": {
                  "maxLength": 140,
                  "minLength": 1,
                  "type": "string"
                },
                "quantity": {
                  "pattern": "^-?\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                },
                "variantId": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "required": [
                "variantId",
                "quantity"
              ],
              "type": "object"
            },
            "maxItems": 200,
            "minItems": 1,
            "type": "array"
          },
          "locationId": {
            "format": "uuid",
            "type": "string"
          },
          "orderType": {
            "enum": [
              "delivery",
              "takeaway"
            ],
            "type": "string"
          },
          "platform": {
            "enum": [
              "talabat",
              "elmenus",
              "mrsool",
              "other"
            ],
            "type": "string"
          },
          "totals": {
            "properties": {
              "currency": {
                "pattern": "^[A-Z]{3}$",
                "type": "string"
              },
              "deliveryFeeMinor": {
                "exclusiveMinimum": false,
                "minimum": 0,
                "type": "integer"
              },
              "discountMinor": {
                "exclusiveMinimum": false,
                "minimum": 0,
                "type": "integer"
              },
              "subtotalMinor": {
                "exclusiveMinimum": false,
                "minimum": 0,
                "type": "integer"
              },
              "totalMinor": {
                "exclusiveMinimum": false,
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "subtotalMinor",
              "totalMinor",
              "currency"
            ],
            "type": "object"
          }
        },
        "required": [
          "locationId",
          "externalId",
          "orderType",
          "lines"
        ],
        "type": "object"
      },
      "V1CreateSale": {
        "properties": {
          "customerId": {
            "format": "uuid",
            "type": "string"
          },
          "discount": {
            "properties": {
              "kind": {
                "enum": [
                  "percent",
                  "amount"
                ],
                "type": "string"
              },
              "value": {
                "exclusiveMinimum": false,
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "kind",
              "value"
            ],
            "type": "object"
          },
          "lines": {
            "items": {
              "properties": {
                "discount": {
                  "properties": {
                    "kind": {
                      "enum": [
                        "percent",
                        "amount"
                      ],
                      "type": "string"
                    },
                    "value": {
                      "exclusiveMinimum": false,
                      "minimum": 0,
                      "type": "integer"
                    }
                  },
                  "required": [
                    "kind",
                    "value"
                  ],
                  "type": "object"
                },
                "quantity": {
                  "pattern": "^\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                },
                "selectedComponents": {
                  "items": {
                    "format": "uuid",
                    "type": "string"
                  },
                  "maxItems": 50,
                  "type": "array"
                },
                "unit": {
                  "maxLength": 16,
                  "minLength": 1,
                  "pattern": "^[a-z0-9][a-z0-9_-]{0,15}$",
                  "type": "string"
                },
                "variantId": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "required": [
                "variantId",
                "quantity"
              ],
              "type": "object"
            },
            "maxItems": 200,
            "minItems": 1,
            "type": "array"
          },
          "locationId": {
            "format": "uuid",
            "type": "string"
          },
          "note": {
            "maxLength": 500,
            "type": "string"
          },
          "payment": {
            "properties": {
              "method": {
                "enum": [
                  "cash",
                  "manual_card",
                  "loyalty",
                  "on_account",
                  "bank_transfer",
                  "cheque",
                  "online"
                ],
                "type": "string"
              },
              "reference": {
                "maxLength": 128,
                "minLength": 1,
                "type": "string"
              },
              "tenderedMinor": {
                "exclusiveMinimum": false,
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "method"
            ],
            "type": "object"
          },
          "payments": {
            "items": {
              "properties": {
                "amountMinor": {
                  "exclusiveMinimum": false,
                  "minimum": 0,
                  "type": "integer"
                },
                "method": {
                  "enum": [
                    "cash",
                    "manual_card",
                    "loyalty",
                    "on_account",
                    "bank_transfer",
                    "cheque",
                    "online"
                  ],
                  "type": "string"
                },
                "reference": {
                  "maxLength": 128,
                  "minLength": 1,
                  "type": "string"
                },
                "tenderedMinor": {
                  "exclusiveMinimum": false,
                  "minimum": 0,
                  "type": "integer"
                }
              },
              "required": [
                "method",
                "amountMinor"
              ],
              "type": "object"
            },
            "maxItems": 4,
            "minItems": 1,
            "type": "array"
          },
          "sendToEta": {
            "type": "boolean"
          }
        },
        "required": [
          "locationId",
          "lines"
        ],
        "type": "object"
      },
      "V1Customer": {
        "properties": {
          "createdAt": {
            "type": "string"
          },
          "email": {
            "nullable": true,
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "phone",
          "email",
          "createdAt"
        ],
        "type": "object"
      },
      "V1CustomersPage": {
        "properties": {
          "data": {
            "items": {
              "properties": {
                "createdAt": {
                  "type": "string"
                },
                "email": {
                  "nullable": true,
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "phone": {
                  "nullable": true,
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name",
                "phone",
                "email",
                "createdAt"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "nextCursor": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "data",
          "nextCursor"
        ],
        "type": "object"
      },
      "V1Error": {
        "properties": {
          "code": {
            "type": "string"
          },
          "details": {
            "properties": {
              "code": {
                "enum": [
                  "api_token_origin_not_allowed",
                  "api_token_ip_not_allowed",
                  "api_token_ip_gate_unavailable",
                  "v1_invalid_query",
                  "v1_invalid_cursor",
                  "v1_invalid_path_parameter",
                  "v1_location_not_accessible",
                  "v1_resource_not_found",
                  "v1_invalid_body",
                  "v1_write_rejected",
                  "v1_write_conflict",
                  "v1_insufficient_stock",
                  "v1_duplicate_customer",
                  "v1_line_not_sellable",
                  "unit_not_for_sale",
                  "quotation_required",
                  "v1_idempotency_key_required",
                  "v1_idempotency_key_invalid",
                  "v1_idempotency_key_reuse",
                  "v1_rate_limited_address",
                  "v1_rate_limited_token",
                  "plan_locked",
                  "v1_quota_exceeded",
                  "feature_disabled",
                  "incoming_order_duplicate",
                  "incoming_order_not_cancellable"
                ],
                "type": "string"
              }
            },
            "required": [
              "code"
            ],
            "type": "object"
          },
          "error": {
            "type": "string"
          },
          "requestId": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "error",
          "code"
        ],
        "type": "object"
      },
      "V1Order": {
        "properties": {
          "acceptedAt": {
            "nullable": true,
            "type": "string"
          },
          "appTotalMinor": {
            "type": "integer"
          },
          "cancelledAt": {
            "nullable": true,
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "externalId": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lines": {
            "items": {
              "properties": {
                "name": {
                  "type": "string"
                },
                "quantity": {
                  "pattern": "^-?\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                },
                "variantId": {
                  "format": "uuid",
                  "nullable": true,
                  "type": "string"
                }
              },
              "required": [
                "variantId",
                "name",
                "quantity"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "locationId": {
            "format": "uuid",
            "type": "string"
          },
          "orderNo": {
            "nullable": true,
            "type": "integer"
          },
          "orderType": {
            "enum": [
              "delivery",
              "takeaway"
            ],
            "type": "string"
          },
          "pickedUpAt": {
            "nullable": true,
            "type": "string"
          },
          "platform": {
            "enum": [
              "talabat",
              "elmenus",
              "mrsool",
              "other"
            ],
            "type": "string"
          },
          "prepMinutes": {
            "nullable": true,
            "type": "integer"
          },
          "readyAt": {
            "nullable": true,
            "type": "string"
          },
          "receivedAt": {
            "type": "string"
          },
          "rejectReason": {
            "enum": [
              "too_busy",
              "item_unavailable",
              "closed",
              "menu_account_settings",
              "technical_problem",
              "other"
            ],
            "nullable": true,
            "type": "string"
          },
          "rejectedAt": {
            "nullable": true,
            "type": "string"
          },
          "status": {
            "enum": [
              "received",
              "accepted",
              "preparing",
              "ready",
              "picked_up",
              "cancelled",
              "rejected"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "locationId",
          "platform",
          "externalId",
          "status",
          "orderType",
          "orderNo",
          "prepMinutes",
          "rejectReason",
          "lines",
          "appTotalMinor",
          "currency",
          "receivedAt",
          "acceptedAt",
          "readyAt",
          "pickedUpAt",
          "cancelledAt",
          "rejectedAt"
        ],
        "type": "object"
      },
      "V1Product": {
        "properties": {
          "active": {
            "type": "boolean"
          },
          "attributes": {
            "additionalProperties": {
              "type": "string"
            },
            "nullable": true,
            "type": "object"
          },
          "barcode": {
            "type": "string"
          },
          "baseUnit": {
            "type": "string"
          },
          "collectionCode": {
            "type": "string"
          },
          "collectionId": {
            "format": "uuid",
            "type": "string"
          },
          "currency": {
            "pattern": "^[A-Z]{3}$",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "modelNumber": {
            "nullable": true,
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "priceMinor": {
            "type": "integer"
          },
          "priceUnit": {
            "nullable": true,
            "type": "string"
          },
          "productAttributes": {
            "additionalProperties": {
              "oneOf": [
                {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                {
                  "properties": {
                    "unit": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "value",
                    "unit"
                  ],
                  "type": "object"
                },
                {
                  "type": "string"
                }
              ]
            },
            "description": "Facts about the whole product, by attribute name (Material, Country of origin, Length …): a list attribute as an array of its values, a measurement as { value, unit } in the unit it was entered in (this item’s own value when it has one, else the product’s), a text as a string; {} when none. `attributes` is what makes this item different from its siblings.",
            "type": "object"
          },
          "productId": {
            "format": "uuid",
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "sellable": {
            "type": "boolean"
          },
          "sku": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "id",
          "productId",
          "productName",
          "name",
          "sku",
          "barcode",
          "priceMinor",
          "currency",
          "baseUnit",
          "priceUnit",
          "attributes",
          "productAttributes",
          "active",
          "collectionId",
          "collectionCode",
          "modelNumber",
          "sellable"
        ],
        "type": "object"
      },
      "V1ProductsPage": {
        "properties": {
          "data": {
            "items": {
              "properties": {
                "active": {
                  "type": "boolean"
                },
                "attributes": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "nullable": true,
                  "type": "object"
                },
                "barcode": {
                  "type": "string"
                },
                "baseUnit": {
                  "type": "string"
                },
                "collectionCode": {
                  "type": "string"
                },
                "collectionId": {
                  "format": "uuid",
                  "type": "string"
                },
                "currency": {
                  "pattern": "^[A-Z]{3}$",
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                },
                "modelNumber": {
                  "nullable": true,
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "priceMinor": {
                  "type": "integer"
                },
                "priceUnit": {
                  "nullable": true,
                  "type": "string"
                },
                "productAttributes": {
                  "additionalProperties": {
                    "oneOf": [
                      {
                        "items": {
                          "type": "string"
                        },
                        "type": "array"
                      },
                      {
                        "properties": {
                          "unit": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "value",
                          "unit"
                        ],
                        "type": "object"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "description": "Facts about the whole product, by attribute name (Material, Country of origin, Length …): a list attribute as an array of its values, a measurement as { value, unit } in the unit it was entered in (this item’s own value when it has one, else the product’s), a text as a string; {} when none. `attributes` is what makes this item different from its siblings.",
                  "type": "object"
                },
                "productId": {
                  "format": "uuid",
                  "type": "string"
                },
                "productName": {
                  "type": "string"
                },
                "sellable": {
                  "type": "boolean"
                },
                "sku": {
                  "nullable": true,
                  "type": "string"
                }
              },
              "required": [
                "id",
                "productId",
                "productName",
                "name",
                "sku",
                "barcode",
                "priceMinor",
                "currency",
                "baseUnit",
                "priceUnit",
                "attributes",
                "productAttributes",
                "active",
                "collectionId",
                "collectionCode",
                "modelNumber",
                "sellable"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "nextCursor": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "data",
          "nextCursor"
        ],
        "type": "object"
      },
      "V1RefundRequest": {
        "properties": {
          "lines": {
            "items": {
              "properties": {
                "billLineId": {
                  "format": "uuid",
                  "type": "string"
                },
                "quantity": {
                  "pattern": "^\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                }
              },
              "required": [
                "billLineId",
                "quantity"
              ],
              "type": "object"
            },
            "maxItems": 200,
            "minItems": 1,
            "type": "array"
          },
          "method": {
            "enum": [
              "manual_card"
            ],
            "type": "string"
          },
          "reason": {
            "maxLength": 500,
            "minLength": 1,
            "type": "string"
          },
          "restock": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "V1RefundResult": {
        "properties": {
          "billId": {
            "format": "uuid",
            "type": "string"
          },
          "billStatus": {
            "enum": [
              "open",
              "paid",
              "partially_refunded",
              "refunded",
              "voided"
            ],
            "type": "string"
          },
          "refund": {
            "properties": {
              "createdAt": {
                "type": "string"
              },
              "currency": {
                "pattern": "^[A-Z]{3}$",
                "type": "string"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "kind": {
                "enum": [
                  "refund",
                  "void"
                ],
                "type": "string"
              },
              "reason": {
                "nullable": true,
                "type": "string"
              },
              "restock": {
                "type": "boolean"
              },
              "seq": {
                "exclusiveMinimum": true,
                "minimum": 0,
                "type": "integer"
              },
              "subtotalMinor": {
                "type": "integer"
              },
              "taxMinor": {
                "type": "integer"
              },
              "totalMinor": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "kind",
              "seq",
              "reason",
              "subtotalMinor",
              "taxMinor",
              "totalMinor",
              "currency",
              "restock",
              "createdAt"
            ],
            "type": "object"
          }
        },
        "required": [
          "refund",
          "billId",
          "billStatus"
        ],
        "type": "object"
      },
      "V1SaleDetail": {
        "properties": {
          "lines": {
            "items": {
              "properties": {
                "currency": {
                  "pattern": "^[A-Z]{3}$",
                  "type": "string"
                },
                "discountMinor": {
                  "type": "integer"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                },
                "lineNo": {
                  "exclusiveMinimum": false,
                  "minimum": 0,
                  "type": "integer"
                },
                "lineTotalMinor": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "quantity": {
                  "pattern": "^-?\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                },
                "refundedQuantity": {
                  "pattern": "^-?\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                },
                "taxMinor": {
                  "type": "integer"
                },
                "unitPriceMinor": {
                  "type": "integer"
                },
                "variantId": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "required": [
                "id",
                "lineNo",
                "variantId",
                "name",
                "quantity",
                "unitPriceMinor",
                "lineTotalMinor",
                "taxMinor",
                "discountMinor",
                "currency",
                "refundedQuantity"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "payments": {
            "items": {
              "properties": {
                "amountMinor": {
                  "type": "integer"
                },
                "currency": {
                  "pattern": "^[A-Z]{3}$",
                  "type": "string"
                },
                "method": {
                  "enum": [
                    "cash",
                    "manual_card",
                    "loyalty",
                    "on_account",
                    "bank_transfer",
                    "cheque",
                    "online"
                  ],
                  "type": "string"
                },
                "status": {
                  "enum": [
                    "captured",
                    "recorded"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "method",
                "amountMinor",
                "currency",
                "status"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "refunds": {
            "items": {
              "properties": {
                "createdAt": {
                  "type": "string"
                },
                "currency": {
                  "pattern": "^[A-Z]{3}$",
                  "type": "string"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                },
                "kind": {
                  "enum": [
                    "refund",
                    "void"
                  ],
                  "type": "string"
                },
                "reason": {
                  "nullable": true,
                  "type": "string"
                },
                "restock": {
                  "type": "boolean"
                },
                "seq": {
                  "exclusiveMinimum": true,
                  "minimum": 0,
                  "type": "integer"
                },
                "subtotalMinor": {
                  "type": "integer"
                },
                "taxMinor": {
                  "type": "integer"
                },
                "totalMinor": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "kind",
                "seq",
                "reason",
                "subtotalMinor",
                "taxMinor",
                "totalMinor",
                "currency",
                "restock",
                "createdAt"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "sale": {
            "properties": {
              "billNumber": {
                "exclusiveMinimum": true,
                "minimum": 0,
                "type": "integer"
              },
              "createdAt": {
                "type": "string"
              },
              "currency": {
                "pattern": "^[A-Z]{3}$",
                "type": "string"
              },
              "customerId": {
                "format": "uuid",
                "nullable": true,
                "type": "string"
              },
              "discountMinor": {
                "type": "integer"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "locationId": {
                "format": "uuid",
                "type": "string"
              },
              "refundedMinor": {
                "type": "integer"
              },
              "status": {
                "enum": [
                  "open",
                  "paid",
                  "partially_refunded",
                  "refunded",
                  "voided"
                ],
                "type": "string"
              },
              "subtotalMinor": {
                "type": "integer"
              },
              "taxMinor": {
                "type": "integer"
              },
              "totalMinor": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "billNumber",
              "locationId",
              "status",
              "subtotalMinor",
              "taxMinor",
              "discountMinor",
              "totalMinor",
              "refundedMinor",
              "currency",
              "customerId",
              "createdAt"
            ],
            "type": "object"
          }
        },
        "required": [
          "sale",
          "lines",
          "payments",
          "refunds"
        ],
        "type": "object"
      },
      "V1SalesPage": {
        "properties": {
          "data": {
            "items": {
              "properties": {
                "billNumber": {
                  "exclusiveMinimum": true,
                  "minimum": 0,
                  "type": "integer"
                },
                "createdAt": {
                  "type": "string"
                },
                "currency": {
                  "pattern": "^[A-Z]{3}$",
                  "type": "string"
                },
                "customerId": {
                  "format": "uuid",
                  "nullable": true,
                  "type": "string"
                },
                "discountMinor": {
                  "type": "integer"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                },
                "locationId": {
                  "format": "uuid",
                  "type": "string"
                },
                "refundedMinor": {
                  "type": "integer"
                },
                "status": {
                  "enum": [
                    "open",
                    "paid",
                    "partially_refunded",
                    "refunded",
                    "voided"
                  ],
                  "type": "string"
                },
                "subtotalMinor": {
                  "type": "integer"
                },
                "taxMinor": {
                  "type": "integer"
                },
                "totalMinor": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "billNumber",
                "locationId",
                "status",
                "subtotalMinor",
                "taxMinor",
                "discountMinor",
                "totalMinor",
                "refundedMinor",
                "currency",
                "customerId",
                "createdAt"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "nextCursor": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "data",
          "nextCursor"
        ],
        "type": "object"
      },
      "V1StockPage": {
        "properties": {
          "data": {
            "items": {
              "properties": {
                "locationId": {
                  "format": "uuid",
                  "type": "string"
                },
                "onHandQty": {
                  "pattern": "^-?\\d+(?:\\.\\d{1,6})?$",
                  "type": "string"
                },
                "variantId": {
                  "format": "uuid",
                  "type": "string"
                }
              },
              "required": [
                "variantId",
                "locationId",
                "onHandQty"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "nextCursor": {
            "nullable": true,
            "type": "string"
          }
        },
        "required": [
          "data",
          "nextCursor"
        ],
        "type": "object"
      },
      "V1VoidRequest": {
        "properties": {
          "reason": {
            "maxLength": 500,
            "minLength": 1,
            "type": "string"
          },
          "restock": {
            "type": "boolean"
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "bearerFormat": "JWT",
        "description": "An API token in the form `<tenantId>.<tokenId>.<secret>`. Shown exactly once, when it is created or rotated. Send it as `Authorization: Bearer <token>`.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {},
    "description": "The PointLit partner API: read the catalogue, read live stock, place an order, read it back, and take goods back.\n\nAuthenticate with `Authorization: Bearer <token>`, using an API token an owner minted in the PointLit settings screen. A token can do no more than the person who created it could do, and that is re-checked on every request — so a token narrows when its creator is demoted and dies when they are disabled.\n\nA machine credential in a browser is a published credential. Call this API from your server.\n\nCompatibility: once a /v1 response shape is published, a field may be ADDED but is never renamed, removed or changed in kind. Ignore fields you do not recognise, and never assume the set of fields is closed.\n\nEvery write requires an `Idempotency-Key` header. Money is integer minor units paired with an ISO-4217 `currency`; quantities are decimal STRINGS with up to 6 decimal places. Identifiers are opaque — pass back exactly what a response returned.\n\nThe narrative guide, which this document is the reference half of, covers authentication, pagination, idempotency, rate limits, the full error table and a worked end-to-end example.",
    "title": "PointLit Partner API",
    "version": "1.0.0"
  },
  "openapi": "3.0.0",
  "paths": {
    "/v1/customers": {
      "get": {
        "description": "Customers of this Business, keyset-paged. `q` is a case-insensitive contains on NAME or PHONE — the same lookup the till uses, and deliberately not a second search; it does NOT match on email. Pass the exact phone number to find the record behind a `v1_duplicate_customer` refusal. Private commercial fields — the staff note and the credit limit — are never exposed here.",
        "operationId": "V1CustomersController_list",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "exclusiveMaximum": false,
              "exclusiveMinimum": false,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "maxLength": 160,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1CustomersPage"
                }
              }
            },
            "description": "One page. Follow `nextCursor` until it is `null`."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_query` — A query parameter was missing, malformed, or is not accepted by this endpoint. The message names the parameter. Unknown parameters are rejected rather than ignored, so a typo in a filter name is visible immediately.\n- `v1_invalid_cursor` — The pagination cursor could not be decoded. Pass back the exact nextCursor value from the previous page; do not construct, truncate or URL-mangle one."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "List or search customers",
        "tags": [
          "V1Customers"
        ]
      },
      "post": {
        "description": "Create a customer record to attach to orders. A phone number is UNIQUE within a Business: a second create with the same phone is refused with `v1_duplicate_customer`, and that refusal IS this endpoint’s replay safety — it is the one /v1 write with no writer-level idempotency key, so a retry whose first response you never saw is told the record already exists rather than handed a second one. `details.field` and `details.value` name the colliding input; look the existing record up with `GET /v1/customers?q=<phone>`.",
        "operationId": "V1CustomersController_create",
        "parameters": [
          {
            "description": "REQUIRED on every /v1 write (D91). 1 to 255 printable-ASCII characters; a UUID is ideal. One value per logical operation: resend the SAME value to retry that operation (you get the original response back, flagged with Idempotent-Replay), and generate a NEW value for a new operation. The same key with a different body is refused, never silently replayed.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateCustomer"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Customer"
                }
              }
            },
            "description": "The created customer.",
            "headers": {
              "Idempotent-Replay": {
                "description": "Present and set to \"true\" only when this response is a REPLAY of an earlier identical request. Absent on the first, real execution. Nothing was written a second time.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_body` — The JSON body was missing a required field, malformed, or carried a field this endpoint does not accept. Bodies are strict: an unknown key is refused rather than ignored, so a misspelled field is visible immediately.\n- `v1_idempotency_key_required` — This endpoint requires an Idempotency-Key header. Generate one value per logical operation (a UUID is ideal), send it with the first attempt, and send the SAME value on every retry of that operation — a retry then returns the original response instead of creating a second order.\n- `v1_idempotency_key_invalid` — The Idempotency-Key header is not a usable key: it must be 1 to 255 printable ASCII characters, with no control characters or newlines. A UUID is the recommended value."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_idempotency_key_reuse` — This Idempotency-Key was already used for a DIFFERENT request on this endpoint. Nothing was written. A key identifies one operation for ever — reuse the same key only to retry the identical request, and generate a new key for a new operation.\n- `v1_duplicate_customer` — A customer with that phone number already exists in this Business; a phone is unique within a Business. Look the existing record up with GET /v1/customers?q=<phone> rather than retrying — this endpoint never creates a second row for the same number."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Create a customer",
        "tags": [
          "V1Customers"
        ]
      }
    },
    "/v1/orders": {
      "post": {
        "description": "Send an order from your channel (or a delivery app you aggregate) to one Location of a restaurant that has \"Orders from delivery apps\" switched on. Name PointLit variant ids (GET /v1/products) and whole quantities; never a price. The order arrives `received` on the till, where staff accept it with a prep time (it then goes to the kitchen) or reject it with a reason — read where it is with GET /v1/orders/{id}. `externalId` is your own order number: the same number twice for one `platform` is refused with `incoming_order_duplicate` (to retry a request whose answer you never saw, resend the same Idempotency-Key instead). Your `totals` are kept beside the restaurant’s own figures and never charged. A Business on a plan without the partner API is refused with `plan_locked`.",
        "operationId": "V1OrdersController_create",
        "parameters": [
          {
            "description": "REQUIRED on every /v1 write (D91). 1 to 255 printable-ASCII characters; a UUID is ideal. One value per logical operation: resend the SAME value to retry that operation (you get the original response back, flagged with Idempotent-Replay), and generate a NEW value for a new operation. The same key with a different body is refused, never silently replayed.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateOrder"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Order"
                }
              }
            },
            "description": "The order as GET /v1/orders/{id} returns it, `received` (or `accepted` when the restaurant accepts this app’s orders on its own).",
            "headers": {
              "Idempotent-Replay": {
                "description": "Present and set to \"true\" only when this response is a REPLAY of an earlier identical request. Absent on the first, real execution. Nothing was written a second time.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_body` — The JSON body was missing a required field, malformed, or carried a field this endpoint does not accept. Bodies are strict: an unknown key is refused rather than ignored, so a misspelled field is visible immediately.\n- `v1_idempotency_key_required` — This endpoint requires an Idempotency-Key header. Generate one value per logical operation (a UUID is ideal), send it with the first attempt, and send the SAME value on every retry of that operation — a retry then returns the original response instead of creating a second order.\n- `v1_idempotency_key_invalid` — The Idempotency-Key header is not a usable key: it must be 1 to 255 printable ASCII characters, with no control characters or newlines. A UUID is the recommended value.\n- `v1_write_rejected` — The write was refused on its merits by the same rule the till obeys — an unconvertible unit, a tender that does not settle the total, or a refund tender through which no money can be handed back. The message names the specific reason."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once.\n- `feature_disabled` — This Business has not switched on the feature this route belongs to (for /v1/orders: \"Orders from delivery apps\", in Restaurant settings). Nothing was written. The Business owner switches it on; the same key then works.\n- `v1_location_not_accessible` — The locationId does not belong to this Business, or names a Location that has been retired. Read GET /v1/stock only for Locations this Business currently operates."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_idempotency_key_reuse` — This Idempotency-Key was already used for a DIFFERENT request on this endpoint. Nothing was written. A key identifies one operation for ever — reuse the same key only to retry the identical request, and generate a new key for a new operation.\n- `v1_write_conflict` — The sale is not in a state that allows this write — it is already fully refunded, already voided, or the amount would exceed what the bill took. Read GET /v1/sales/{id} for the current status and refundedMinor.\n- `incoming_order_duplicate` — An order with this externalId from this platform is already in this Business; nothing was written. To retry a request whose answer you never saw, send the SAME Idempotency-Key instead. The existing order’s id is in details.orderId."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Send an order to the till",
        "tags": [
          "V1Orders"
        ]
      }
    },
    "/v1/orders/{id}": {
      "get": {
        "description": "Where an order you sent stands: `received` until the restaurant decides; `accepted` (with its prep time and daily order number), `preparing`, `ready` and `picked_up`; or `rejected` (with the reason) or `cancelled`.",
        "operationId": "V1OrdersController_detail",
        "parameters": [
          {
            "description": "An opaque identifier returned by a previous /v1 response. Pass it back exactly; do not construct, truncate or re-case one. A malformed value is refused with `v1_invalid_path_parameter`, and an id belonging to another Business is reported as missing.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Order"
                }
              }
            },
            "description": "The order, its lines and its timeline."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_path_parameter` — A path parameter is not a well-formed UUID. Identifiers are opaque — pass back exactly what a /v1 response returned."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once.\n- `feature_disabled` — This Business has not switched on the feature this route belongs to (for /v1/orders: \"Orders from delivery apps\", in Restaurant settings). Nothing was written. The Business owner switches it on; the same key then works."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Read one order",
        "tags": [
          "V1Orders"
        ]
      }
    },
    "/v1/orders/{id}/cancel": {
      "post": {
        "description": "Cancel an order while it is still `received`. Once the restaurant has accepted (or rejected) it, the cancel is refused with `incoming_order_not_cancellable` — the kitchen may have started, so call the restaurant. Cancelling a cancelled order answers it as it is.",
        "operationId": "V1OrdersController_cancel",
        "parameters": [
          {
            "description": "An opaque identifier returned by a previous /v1 response. Pass it back exactly; do not construct, truncate or re-case one. A malformed value is refused with `v1_invalid_path_parameter`, and an id belonging to another Business is reported as missing.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "REQUIRED on every /v1 write (D91). 1 to 255 printable-ASCII characters; a UUID is ideal. One value per logical operation: resend the SAME value to retry that operation (you get the original response back, flagged with Idempotent-Replay), and generate a NEW value for a new operation. The same key with a different body is refused, never silently replayed.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CancelOrder"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Order"
                }
              }
            },
            "description": "The order, now `cancelled`.",
            "headers": {
              "Idempotent-Replay": {
                "description": "Present and set to \"true\" only when this response is a REPLAY of an earlier identical request. Absent on the first, real execution. Nothing was written a second time.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_body` — The JSON body was missing a required field, malformed, or carried a field this endpoint does not accept. Bodies are strict: an unknown key is refused rather than ignored, so a misspelled field is visible immediately.\n- `v1_idempotency_key_required` — This endpoint requires an Idempotency-Key header. Generate one value per logical operation (a UUID is ideal), send it with the first attempt, and send the SAME value on every retry of that operation — a retry then returns the original response instead of creating a second order.\n- `v1_idempotency_key_invalid` — The Idempotency-Key header is not a usable key: it must be 1 to 255 printable ASCII characters, with no control characters or newlines. A UUID is the recommended value.\n- `v1_invalid_path_parameter` — A path parameter is not a well-formed UUID. Identifiers are opaque — pass back exactly what a /v1 response returned."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once.\n- `feature_disabled` — This Business has not switched on the feature this route belongs to (for /v1/orders: \"Orders from delivery apps\", in Restaurant settings). Nothing was written. The Business owner switches it on; the same key then works."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_idempotency_key_reuse` — This Idempotency-Key was already used for a DIFFERENT request on this endpoint. Nothing was written. A key identifies one operation for ever — reuse the same key only to retry the identical request, and generate a new key for a new operation.\n- `incoming_order_not_cancellable` — The restaurant has already decided this order (accepted, rejected or cancelled), so it can no longer be cancelled through the API; details.status says where it is. Call the restaurant to stop an accepted order."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Cancel an order before the restaurant decides",
        "tags": [
          "V1Orders"
        ]
      }
    },
    "/v1/products": {
      "get": {
        "description": "Every sellable Variant in this Business, keyset-paged, newest first. A Variant is the thing an order line names and the thing stock is counted for; `productId`/`productName` let a storefront group combinations of one Product back together. Discontinued Variants are RETURNED with `active: false` rather than omitted — to a mirror, a row that simply vanishes is indistinguishable from one its own sync dropped. `priceMinor` is the current sell price in minor units of `currency`; cost, margin and supplier pricing are never exposed on this surface. Every item belongs to one COLLECTION (a season or range): by default the list holds the items of OPEN collections; pass `collectionId` to list one collection at any status (planned and closed included). `sellable` says whether a sale may include the item now.",
        "operationId": "V1ProductsController_list",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "exclusiveMaximum": false,
              "exclusiveMinimum": false,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "collectionId",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProductsPage"
                }
              }
            },
            "description": "One page. Follow `nextCursor` until it is `null`; an empty `data` array is NOT an end signal."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_query` — A query parameter was missing, malformed, or is not accepted by this endpoint. The message names the parameter. Unknown parameters are rejected rather than ignored, so a typo in a filter name is visible immediately.\n- `v1_invalid_cursor` — The pagination cursor could not be decoded. Pass back the exact nextCursor value from the previous page; do not construct, truncate or URL-mangle one."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "List catalogue items",
        "tags": [
          "V1Products"
        ]
      }
    },
    "/v1/products/{id}": {
      "get": {
        "description": "One Variant by the `id` a list page returned. Ids are tenant-scoped and opaque: an id belonging to another Business is reported as MISSING rather than forbidden, because \"forbidden\" would confirm it exists.",
        "operationId": "V1ProductsController_byId",
        "parameters": [
          {
            "description": "An opaque identifier returned by a previous /v1 response. Pass it back exactly; do not construct, truncate or re-case one. A malformed value is refused with `v1_invalid_path_parameter`, and an id belonging to another Business is reported as missing.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Product"
                }
              }
            },
            "description": "The Variant."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_path_parameter` — A path parameter is not a well-formed UUID. Identifiers are opaque — pass back exactly what a /v1 response returned."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Read one catalogue item",
        "tags": [
          "V1Products"
        ]
      }
    },
    "/v1/sales": {
      "get": {
        "description": "Sales of this Business, keyset-paged, newest first. Filter by date range (`from`/`to`, ISO-8601 instants), Location, status or customer. The list returns sale HEADERS; read one sale for its lines, payments and refunds. Both till sales and API orders are returned — this is the Business’s book, not just yours.",
        "operationId": "V1SalesController_list",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "exclusiveMaximum": false,
              "exclusiveMinimum": false,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "locationId",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "open",
                "paid",
                "partially_refunded",
                "refunded",
                "voided"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "customerId",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1SalesPage"
                }
              }
            },
            "description": "One page of sale headers. Follow `nextCursor` until it is `null`."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_query` — A query parameter was missing, malformed, or is not accepted by this endpoint. The message names the parameter. Unknown parameters are rejected rather than ignored, so a typo in a filter name is visible immediately.\n- `v1_invalid_cursor` — The pagination cursor could not be decoded. Pass back the exact nextCursor value from the previous page; do not construct, truncate or URL-mangle one."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "List sales",
        "tags": [
          "V1Sales"
        ]
      },
      "post": {
        "description": "Create a paid sale at one Location. Prices, tax, discounts and totals are computed SERVER-side from the stored catalogue — send quantities and a tender, never a price. Settle with `payment` (one leg) or `payments` (split tender), never both; `loyalty` and `on_account` are stored-value ledgers and are not tenders this API can settle with. A line `discount` requires the `sales:discount` permission on the token AND on its creator. Stock is taken through the same ledger the till uses: if any line would go negative the WHOLE order is rolled back with `v1_insufficient_stock` and nothing is written — no bill, no payment, no movement. A line for an item whose collection is not open (planned, or closed) is refused with `v1_line_not_sellable`, also before anything is written. At a Location whose every sale must start as a quotation the order is refused with `quotation_required` before anything is written. The order is tagged as an API sale and carries no shift, so it can never land in a cashier drawer, an expected-cash figure or a Z-report.",
        "operationId": "V1SalesController_create",
        "parameters": [
          {
            "description": "REQUIRED on every /v1 write (D91). 1 to 255 printable-ASCII characters; a UUID is ideal. One value per logical operation: resend the SAME value to retry that operation (you get the original response back, flagged with Idempotent-Replay), and generate a NEW value for a new operation. The same key with a different body is refused, never silently replayed.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1CreateSale"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1SaleDetail"
                }
              }
            },
            "description": "The sale as `GET /v1/sales/{id}` would return it: header, lines, payments and (empty) refunds.",
            "headers": {
              "Idempotent-Replay": {
                "description": "Present and set to \"true\" only when this response is a REPLAY of an earlier identical request. Absent on the first, real execution. Nothing was written a second time.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_body` — The JSON body was missing a required field, malformed, or carried a field this endpoint does not accept. Bodies are strict: an unknown key is refused rather than ignored, so a misspelled field is visible immediately.\n- `v1_idempotency_key_required` — This endpoint requires an Idempotency-Key header. Generate one value per logical operation (a UUID is ideal), send it with the first attempt, and send the SAME value on every retry of that operation — a retry then returns the original response instead of creating a second order.\n- `v1_idempotency_key_invalid` — The Idempotency-Key header is not a usable key: it must be 1 to 255 printable ASCII characters, with no control characters or newlines. A UUID is the recommended value.\n- `v1_write_rejected` — The write was refused on its merits by the same rule the till obeys — an unconvertible unit, a tender that does not settle the total, or a refund tender through which no money can be handed back. The message names the specific reason.\n- `unit_not_for_sale` — A line's unit is not one the business sells this item in (the unit is set for buying only, or not used). Nothing was written. details.variantId, details.unit and details.index name the line. Send the line in a unit the item is sold in."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once.\n- `v1_location_not_accessible` — The locationId does not belong to this Business, or names a Location that has been retired. Read GET /v1/stock only for Locations this Business currently operates."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_idempotency_key_reuse` — This Idempotency-Key was already used for a DIFFERENT request on this endpoint. Nothing was written. A key identifies one operation for ever — reuse the same key only to retry the identical request, and generate a new key for a new operation.\n- `v1_write_conflict` — The sale is not in a state that allows this write — it is already fully refunded, already voided, or the amount would exceed what the bill took. Read GET /v1/sales/{id} for the current status and refundedMinor.\n- `v1_insufficient_stock` — The order would take stock below zero at that Location. The whole order was rolled back — no bill, no payment and no stock movement was written. details.variantId and details.locationId name what was short.\n- `v1_line_not_sellable` — The item's collection is not open (it is planned, or closed), so a line naming it cannot be sold. Nothing was written. details.variantId names the item, and GET /v1/products shows sellable: false for it. Remove the line, or ask the business to open the collection.\n- `quotation_required` — The Location sells only through quotations: every sale there starts as a quotation that becomes an order, so an order cannot be placed there directly. Nothing was written. details.locationId names the Location. Place the order at another Location, or ask the business to turn the setting off."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Place an order",
        "tags": [
          "V1Sales"
        ]
      }
    },
    "/v1/sales/{id}": {
      "get": {
        "description": "One sale with its lines, its payment legs and every refund written against it. `refundedMinor` on the header and `refundedQuantity` on each line are the running totals a further return is checked against — read them before refunding rather than tracking your own. Internal attribution (which cashier, which shift) and drawer figures are deliberately not exposed.",
        "operationId": "V1SalesController_detail",
        "parameters": [
          {
            "description": "An opaque identifier returned by a previous /v1 response. Pass it back exactly; do not construct, truncate or re-case one. A malformed value is refused with `v1_invalid_path_parameter`, and an id belonging to another Business is reported as missing.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1SaleDetail"
                }
              }
            },
            "description": "The sale, its lines, its payments and its refunds."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_path_parameter` — A path parameter is not a well-formed UUID. Identifiers are opaque — pass back exactly what a /v1 response returned."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Read one sale",
        "tags": [
          "V1Sales"
        ]
      }
    },
    "/v1/sales/{id}/refunds": {
      "post": {
        "description": "Refund a sale, in part or in full. Send `lines` to return specific quantities, or omit `lines` to return everything still refundable. **`restock: true` is what makes this a customer RETURN rather than a money-only refund**: it puts the goods back on the shelf through the one stock ledger. `method` names how the money goes back and may not be `cash` — a machine cannot open a drawer, and paying cash out of a till that never took it in for this order is money that will not reconcile; omit `method` to return it on the bill’s own tender. A refund never edits the sale: it writes a compensating document, and the sale’s `status` and `refundedMinor` move to match.",
        "operationId": "V1SalesController_refund",
        "parameters": [
          {
            "description": "An opaque identifier returned by a previous /v1 response. Pass it back exactly; do not construct, truncate or re-case one. A malformed value is refused with `v1_invalid_path_parameter`, and an id belonging to another Business is reported as missing.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "REQUIRED on every /v1 write (D91). 1 to 255 printable-ASCII characters; a UUID is ideal. One value per logical operation: resend the SAME value to retry that operation (you get the original response back, flagged with Idempotent-Replay), and generate a NEW value for a new operation. The same key with a different body is refused, never silently replayed.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1RefundRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1RefundResult"
                }
              }
            },
            "description": "The refund document, plus the sale id and its new status.",
            "headers": {
              "Idempotent-Replay": {
                "description": "Present and set to \"true\" only when this response is a REPLAY of an earlier identical request. Absent on the first, real execution. Nothing was written a second time.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_body` — The JSON body was missing a required field, malformed, or carried a field this endpoint does not accept. Bodies are strict: an unknown key is refused rather than ignored, so a misspelled field is visible immediately.\n- `v1_idempotency_key_required` — This endpoint requires an Idempotency-Key header. Generate one value per logical operation (a UUID is ideal), send it with the first attempt, and send the SAME value on every retry of that operation — a retry then returns the original response instead of creating a second order.\n- `v1_idempotency_key_invalid` — The Idempotency-Key header is not a usable key: it must be 1 to 255 printable ASCII characters, with no control characters or newlines. A UUID is the recommended value.\n- `v1_invalid_path_parameter` — A path parameter is not a well-formed UUID. Identifiers are opaque — pass back exactly what a /v1 response returned.\n- `v1_write_rejected` — The write was refused on its merits by the same rule the till obeys — an unconvertible unit, a tender that does not settle the total, or a refund tender through which no money can be handed back. The message names the specific reason."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_idempotency_key_reuse` — This Idempotency-Key was already used for a DIFFERENT request on this endpoint. Nothing was written. A key identifies one operation for ever — reuse the same key only to retry the identical request, and generate a new key for a new operation.\n- `v1_write_conflict` — The sale is not in a state that allows this write — it is already fully refunded, already voided, or the amount would exceed what the bill took. Read GET /v1/sales/{id} for the current status and refundedMinor."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Take goods back (customer return)",
        "tags": [
          "V1Sales"
        ]
      }
    },
    "/v1/sales/{id}/void": {
      "post": {
        "description": "Cancel a sale in one act: everything still refundable comes back. A void is a refund of the whole remainder, written the same way and through the same writer — it does not delete the sale or edit any figure on it. `restock: true` puts the goods back. A sale already voided or already fully refunded is refused with `v1_write_conflict`; read `GET /v1/sales/{id}` for the current status.",
        "operationId": "V1SalesController_void",
        "parameters": [
          {
            "description": "An opaque identifier returned by a previous /v1 response. Pass it back exactly; do not construct, truncate or re-case one. A malformed value is refused with `v1_invalid_path_parameter`, and an id belonging to another Business is reported as missing.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "REQUIRED on every /v1 write (D91). 1 to 255 printable-ASCII characters; a UUID is ideal. One value per logical operation: resend the SAME value to retry that operation (you get the original response back, flagged with Idempotent-Replay), and generate a NEW value for a new operation. The same key with a different body is refused, never silently replayed.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1VoidRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1RefundResult"
                }
              }
            },
            "description": "The compensating document, plus the sale id and its new status.",
            "headers": {
              "Idempotent-Replay": {
                "description": "Present and set to \"true\" only when this response is a REPLAY of an earlier identical request. Absent on the first, real execution. Nothing was written a second time.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_body` — The JSON body was missing a required field, malformed, or carried a field this endpoint does not accept. Bodies are strict: an unknown key is refused rather than ignored, so a misspelled field is visible immediately.\n- `v1_idempotency_key_required` — This endpoint requires an Idempotency-Key header. Generate one value per logical operation (a UUID is ideal), send it with the first attempt, and send the SAME value on every retry of that operation — a retry then returns the original response instead of creating a second order.\n- `v1_idempotency_key_invalid` — The Idempotency-Key header is not a usable key: it must be 1 to 255 printable ASCII characters, with no control characters or newlines. A UUID is the recommended value.\n- `v1_invalid_path_parameter` — A path parameter is not a well-formed UUID. Identifiers are opaque — pass back exactly what a /v1 response returned.\n- `v1_write_rejected` — The write was refused on its merits by the same rule the till obeys — an unconvertible unit, a tender that does not settle the total, or a refund tender through which no money can be handed back. The message names the specific reason."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_resource_not_found` — No such resource in this Business. Ids are tenant-scoped: an id from another Business is reported as missing, never as forbidden."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_idempotency_key_reuse` — This Idempotency-Key was already used for a DIFFERENT request on this endpoint. Nothing was written. A key identifies one operation for ever — reuse the same key only to retry the identical request, and generate a new key for a new operation.\n- `v1_write_conflict` — The sale is not in a state that allows this write — it is already fully refunded, already voided, or the amount would exceed what the bill took. Read GET /v1/sales/{id} for the current status and refundedMinor."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Void a sale",
        "tags": [
          "V1Sales"
        ]
      }
    },
    "/v1/stock": {
      "get": {
        "description": "Live on-hand quantity for every Variant at ONE Location. `locationId` is REQUIRED and is validated: a token carries no Location, because a machine has no session and a Location-scoped token would be a claim this system cannot keep. Quantities are exact decimal STRINGS in the Variant `baseUnit`, with up to 6 decimal places — parse them as decimals, never as floats. This answer is a snapshot: it is not a reservation, and stock can move between reading it and ordering. Order anyway and handle `v1_insufficient_stock`, which rolls the whole order back. This list is not paged; `nextCursor` is always `null`.",
        "operationId": "V1StockController_list",
        "parameters": [
          {
            "in": "query",
            "name": "locationId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1StockPage"
                }
              }
            },
            "description": "Every Variant with a stock position at that Location."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_invalid_query` — A query parameter was missing, malformed, or is not accepted by this endpoint. The message names the parameter. Unknown parameters are rejected rather than ignored, so a typo in a filter name is visible immediately."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "No credential, or one this API will not accept. Deliberately generic: malformed, unknown, wrong, revoked and expired are indistinguishable, so there is no machine code to branch on. Check the Authorization header first, then whether the token was revoked or has expired."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `api_token_origin_not_allowed` — The token has a web-origin allow-list and the request did not come from an allow-listed origin. A server-to-server call sends no Origin header and is refused by such a token; clear the origin list for a backend integration.\n- `api_token_ip_not_allowed` — The token has an IP allow-list and the request came from an address outside it. Check the egress address of the calling server — NAT and cloud egress pools change it.\n- `api_token_ip_gate_unavailable` — The token has an IP allow-list but the API is not configured to determine the client address behind its proxy. The operator must set TRUST_PROXY_HOPS to the number of reverse-proxy hops in front of the API.\n- `plan_locked` — This Business's plan does not include the partner API, so every call is refused, sales, refunds and voids included. The key itself is valid; the Business owner can upgrade the plan under Billing & plan, and the same keys work again at once.\n- `v1_location_not_accessible` — The locationId does not belong to this Business, or names a Location that has been retired. Read GET /v1/stock only for Locations this Business currently operates."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Error"
                }
              }
            },
            "description": "The machine-readable reason is in `details.code`. Branch on that, never on the sentence in `error`.\n\n- `v1_rate_limited_address` — Too many /v1 requests from this network address in the current window. This limit is per calling address and is applied before the credential is checked. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_rate_limited_token` — Too many /v1 requests for this API token in the current window. This limit is per token, so a second token in the same Business has its own budget. Wait the number of seconds in the Retry-After header and retry; a retry of a write is safe as long as it carries the same Idempotency-Key.\n- `v1_quota_exceeded` — This Business has used the partner API calls its plan allows this calendar month (UTC). Sales, refunds and voids are still accepted and counted; every other call is refused until the first day of next month — the Retry-After header gives the seconds until then. The quota is per Business, shared by all its keys; a plan with more calls lifts it at once.",
            "headers": {
              "Retry-After": {
                "description": "How many seconds to wait before retrying. Authoritative — prefer it over any published default.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "summary": "Read on-hand stock at one Location",
        "tags": [
          "V1Stock"
        ]
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "servers": [
    {
      "description": "Your Business’s own PointLit address, with /api.",
      "url": "{baseUrl}",
      "variables": {
        "baseUrl": {
          "default": "https://your-business.pointlit.app/api",
          "description": "Your Business’s address followed by /api: https://<workspace>.pointlit.app/api, or https://<your connected domain>/api. The API token names the Business."
        }
      }
    }
  ],
  "tags": []
}
