{
  "openapi": "3.0.3",
  "info": {
    "title": "RestroLab Third-Party Integration API",
    "version": "1.0.0",
    "description": "A generic, provider-agnostic contract: any external platform (food delivery, booking/OTA, POS, marketplace, ...) that implements it can integrate with a RestroLab tenant's Restaurant and/or Hotel operations. Nothing here is specific to any one partner.\n\nOne credential, independent modules: the same API key/secret authenticates both the Restro and Hotel endpoints below -- which ones actually work depends only on whether *this tenant* has that feature enabled (a hotel-only tenant's credential works against every `/hotels/*` endpoint and gets a 403 from every Restro one, and vice versa; a tenant with both enabled can use either through the one credential).\n\n### End-to-end flow\n1. The restaurant/hotel owner generates credentials inside RestroLab (`POST /v1/third-party/credentials/`, their own JWT login, owner-only) and copies the `api_key`/`api_secret` shown once in the response.\n2. The owner pastes those into this platform's own settings page. RestroLab is never called by the owner directly again.\n3. This platform's backend authenticates every call below with `X-API-Key` / `X-API-Secret` headers. The tenant is always resolved from the credential -- never send a `tenant_id`, `restaurant_id`, or `hotel_id`.\n4. **Restro**: `GET /menu/` (each item's `item_id` is the dish's own SKU, echo it straight back) then `POST /orders/` to place an order. **Hotel**: `GET /hotels/rooms/` (each type's `room_type_id` is its own id, echo it straight back), optionally `GET /hotels/availability/`, then `POST /hotels/reservations/` to create a reservation. Both creates are idempotent on their external id: a retry with the same `external_order_id`/`external_reservation_id` returns the existing one (200), never a duplicate (only the first call is 201).\n5. Call `POST /webhook/` once with `{\"webhook_url\": \"https://...\"}` (HTTPS only) -- shared by both modules. The response's `webhook_secret` is shown once -- store it to verify future deliveries.\n6. When an order's status changes, or a reservation is created/modified/cancelled, in RestroLab, a signed event (`order.status_changed`, `reservation.created`, `reservation.modified`, or `reservation.cancelled`) is POSTed to that URL asynchronously. Verify `X-RestroLab-Signature: sha256=...` (HMAC-SHA256 of `{timestamp}.{raw body}` with your `webhook_secret`) and process `X-RestroLab-Event-Id` idempotently -- it stays the same across retries. Respond 2xx quickly; non-2xx/timeouts are retried with exponential backoff up to 5 attempts."
  },
  "paths": {
    "/v1/third-party/credentials/": {
      "get": {
        "operationId": "credentials_list",
        "description": "The owner-facing side of the contract -- the owner generates\ncredentials here, copies them, and pastes them into the external\nplatform's own settings page. RestroLab never calls its own API on\nthe owner's behalf.",
        "tags": [
          "Third-Party Integration Credentials"
        ],
        "security": [
          {
            "jwtAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IntegrationCredential"
                  }
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "credentials_create",
        "description": "The owner-facing side of the contract -- the owner generates\ncredentials here, copies them, and pastes them into the external\nplatform's own settings page. RestroLab never calls its own API on\nthe owner's behalf.",
        "tags": [
          "Third-Party Integration Credentials"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IntegrationCredentialCreate"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/IntegrationCredentialCreate"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/IntegrationCredentialCreate"
              }
            }
          }
        },
        "security": [
          {
            "jwtAuth": []
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationCredentialSecretResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/credentials/{id}/revoke/": {
      "post": {
        "operationId": "credentials_revoke_create",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Third-Party Integration Credentials"
        ],
        "security": [
          {
            "jwtAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationCredential"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/credentials/{id}/rotate/": {
      "post": {
        "operationId": "credentials_rotate_create",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true
          }
        ],
        "tags": [
          "Third-Party Integration Credentials"
        ],
        "security": [
          {
            "jwtAuth": []
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntegrationCredentialSecretResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/hotels/availability/": {
      "get": {
        "operationId": "hotels_availability_retrieve",
        "description": "GET /v1/third-party/hotels/availability/?room_type_id=&check_in=&check_out=",
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AvailabilityResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/hotels/reservations/": {
      "get": {
        "operationId": "hotels_reservations_list",
        "description": "GET lists every reservation this credential has placed (spec §6's\n\"GET reservations\"); POST creates a new one (idempotent, spec §7).",
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ReservationResponse"
                  }
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "hotels_reservations_create",
        "description": "GET lists every reservation this credential has placed (spec §6's\n\"GET reservations\"); POST creates a new one (idempotent, spec §7).",
        "tags": [
          "Third-Party Integration"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReservationCreate"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/ReservationCreate"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ReservationCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/hotels/reservations/{external_reservation_id}/": {
      "get": {
        "operationId": "hotels_reservations_retrieve",
        "description": "Every hotel-facing view -- adds the hotel feature gate on top of\nthe shared HasActiveCredential check. A tenant with\nhotel_enabled=False gets a 403 here regardless of whether restaurant\nis enabled -- the two are independent (spec case A).",
        "parameters": [
          {
            "in": "path",
            "name": "external_reservation_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/hotels/reservations/{external_reservation_id}/cancel/": {
      "post": {
        "operationId": "hotels_reservations_cancel_create",
        "description": "Every hotel-facing view -- adds the hotel feature gate on top of\nthe shared HasActiveCredential check. A tenant with\nhotel_enabled=False gets a 403 here regardless of whether restaurant\nis enabled -- the two are independent (spec case A).",
        "parameters": [
          {
            "in": "path",
            "name": "external_reservation_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReservationResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/hotels/rooms/": {
      "get": {
        "operationId": "hotels_rooms_list",
        "description": "GET /v1/third-party/hotels/rooms/ -- tenant is resolved entirely\nfrom the calling credential, no ?tenant_id=/?hotel_id= query param.",
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/RoomType"
                  }
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/menu/": {
      "get": {
        "operationId": "menu_list",
        "description": "GET /v1/third-party/menu/ -- tenant is resolved entirely from the\ncalling credential, no ?tenant_id=/?restaurant_id= query param.\n\nReturns the same shape as the internal, staff-facing\napps.restro.api.v1.menu.dish.DishViewSet (DishSerializer) -- by\nrequest, so a partner sees everything staff see (category, kitchen,\nsub_menu, menu_set, tax settings, dish_type, ...), not a hand-picked\nsubset -- except `ingredients`, which is deliberately dropped; see\nThirdPartyDishSerializer's docstring for why. sku is still what a\nplatform echoes back as item_id on POST /orders/ -- that request-side\ncontract is unchanged.",
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ThirdPartyDish"
                  }
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/orders/": {
      "get": {
        "operationId": "orders_list",
        "description": "GET lists every order this credential has ever placed (spec §11's\n\"GET orders\"); POST creates a new one (spec §4).",
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrderResponse"
                  }
                }
              }
            },
            "description": ""
          }
        }
      },
      "post": {
        "operationId": "orders_create",
        "description": "GET lists every order this credential has ever placed (spec §11's\n\"GET orders\"); POST creates a new one (spec §4).",
        "tags": [
          "Third-Party Integration"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderCreate"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/OrderCreate"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/OrderCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/orders/{external_order_id}/": {
      "get": {
        "operationId": "orders_retrieve",
        "description": "Every restro-facing view -- adds the restaurant feature gate on\ntop of the shared HasActiveCredential check. A tenant with\nrestaurant_enabled=False gets a 403 here regardless of whether hotel\nis enabled -- the two are independent (spec case B).",
        "parameters": [
          {
            "in": "path",
            "name": "external_order_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/orders/{external_order_id}/cancel/": {
      "post": {
        "operationId": "orders_cancel_create",
        "description": "Every restro-facing view -- adds the restaurant feature gate on\ntop of the shared HasActiveCredential check. A tenant with\nrestaurant_enabled=False gets a 403 here regardless of whether hotel\nis enabled -- the two are independent (spec case B).",
        "parameters": [
          {
            "in": "path",
            "name": "external_order_id",
            "schema": {
              "type": "string"
            },
            "required": true
          }
        ],
        "tags": [
          "Third-Party Integration"
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    },
    "/v1/third-party/webhook/": {
      "post": {
        "operationId": "webhook_create",
        "tags": [
          "Third-Party Integration"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRegister"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRegister"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRegister"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookRegisterResponse"
                }
              }
            },
            "description": ""
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AddOnDish": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "maxLength": 512
          },
          "price": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$"
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "dish_type": {
            "nullable": true,
            "oneOf": [
              {
                "$ref": "#/components/schemas/DishTypeEnum"
              },
              {
                "$ref": "#/components/schemas/BlankEnum"
              },
              {
                "$ref": "#/components/schemas/NullEnum"
              }
            ]
          },
          "is_active": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name",
          "price"
        ]
      },
      "AvailabilityDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "sellable": {
            "type": "integer"
          }
        },
        "required": [
          "date",
          "sellable"
        ]
      },
      "AvailabilityResponse": {
        "type": "object",
        "properties": {
          "room_type_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "base_price": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$",
            "nullable": true
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AvailabilityDay"
            }
          }
        },
        "required": [
          "base_price",
          "days",
          "name",
          "room_type_id"
        ]
      },
      "BlankEnum": {
        "enum": [
          ""
        ]
      },
      "Category": {
        "type": "object",
        "description": "Base serializer for tenant-aware models.\n\nResponsibilities:\n- Inject tenant on create\n- Prevent tenant override",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "tenant": {
            "type": "integer",
            "readOnly": true
          },
          "dish_count": {
            "type": "string",
            "readOnly": true
          },
          "sub_menu": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SubMenu"
              }
            ],
            "readOnly": true
          },
          "menu_set": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MenuSet"
              }
            ],
            "readOnly": true
          },
          "sub_menu_id": {
            "type": "string",
            "format": "uuid",
            "writeOnly": true,
            "nullable": true
          },
          "menu_set_id": {
            "type": "string",
            "format": "uuid",
            "writeOnly": true,
            "nullable": true
          },
          "deleted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "restored_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "transaction_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "ticket_type": {
            "$ref": "#/components/schemas/TicketTypeEnum"
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true
          }
        },
        "required": [
          "created_at",
          "dish_count",
          "id",
          "menu_set",
          "name",
          "sub_menu",
          "tenant",
          "updated_at"
        ]
      },
      "CustomerInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "phone": {
            "type": "string",
            "maxLength": 20
          }
        }
      },
      "DishTypeEnum": {
        "enum": [
          "veg",
          "non_veg",
          "halal",
          "vegan",
          "gluten_free",
          "sugar_free"
        ],
        "type": "string",
        "description": "* `veg` - Veg\n* `non_veg` - Non-Veg\n* `halal` - Halal\n* `vegan` - Vegan\n* `gluten_free` - Gluten Free\n* `sugar_free` - Sugar Free"
      },
      "IntegrationCredential": {
        "type": "object",
        "description": "Read/list shape -- never includes api_secret_hash or\nwebhook_secret. The raw secret is only ever visible in the response\nof the create/rotate actions, one time each.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "label": {
            "type": "string",
            "readOnly": true,
            "description": "Owner-given name to tell multiple connections apart, e.g. 'Firri'. Never interpreted by RestroLab."
          },
          "api_key": {
            "type": "string",
            "readOnly": true
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true
          },
          "is_active": {
            "type": "boolean",
            "readOnly": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "last_rotated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          }
        },
        "required": [
          "api_key",
          "created_at",
          "id",
          "is_active",
          "label",
          "last_rotated_at",
          "last_used_at",
          "webhook_url"
        ]
      },
      "IntegrationCredentialCreate": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "maxLength": 255
          }
        }
      },
      "IntegrationCredentialSecretResponse": {
        "type": "object",
        "description": "Returned once, from create/rotate only.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "api_key": {
            "type": "string"
          },
          "api_secret": {
            "type": "string"
          }
        },
        "required": [
          "api_key",
          "api_secret",
          "id",
          "label"
        ]
      },
      "Kitchen": {
        "type": "object",
        "description": "Base serializer for tenant-aware models.\n\nResponsibilities:\n- Inject tenant on create\n- Prevent tenant override",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "hotel_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "stock_location_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          }
        },
        "required": [
          "created_at",
          "id",
          "name",
          "updated_at"
        ]
      },
      "MenuSet": {
        "type": "object",
        "description": "Base serializer for tenant-aware models.\n\nResponsibilities:\n- Inject tenant on create\n- Prevent tenant override",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "tenant": {
            "type": "integer",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "service": {
            "$ref": "#/components/schemas/ServiceEnum"
          },
          "status": {
            "$ref": "#/components/schemas/StatusEnum"
          },
          "sub_menus": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubMenu"
            },
            "readOnly": true
          },
          "sub_menu_id": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "writeOnly": true
            },
            "writeOnly": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          }
        },
        "required": [
          "created_at",
          "id",
          "name",
          "sub_menus",
          "tenant",
          "updated_at"
        ]
      },
      "NullEnum": {
        "enum": [
          null
        ]
      },
      "OrderCreate": {
        "type": "object",
        "properties": {
          "external_order_id": {
            "type": "string",
            "maxLength": 150
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderItemInput"
            }
          },
          "order_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OrderTypeEnum"
              }
            ],
            "default": "delivery"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerInput"
          },
          "delivery_address": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "external_order_id",
          "items"
        ]
      },
      "OrderItemInput": {
        "type": "object",
        "properties": {
          "item_id": {
            "type": "string",
            "maxLength": 100
          },
          "quantity": {
            "type": "integer",
            "minimum": 1
          },
          "variant_id": {
            "type": "string",
            "maxLength": 100
          },
          "addons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ThirdPartyOrderItemAddonInput"
            }
          }
        },
        "required": [
          "item_id",
          "quantity"
        ]
      },
      "OrderResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "order_id": {
            "type": "string"
          },
          "external_order_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "external_order_id",
          "order_id",
          "status",
          "success"
        ]
      },
      "OrderTypeEnum": {
        "enum": [
          "delivery",
          "take_away",
          "pick_up"
        ],
        "type": "string",
        "description": "* `delivery` - delivery\n* `take_away` - take_away\n* `pick_up` - pick_up"
      },
      "ReservationCreate": {
        "type": "object",
        "properties": {
          "external_reservation_id": {
            "type": "string",
            "maxLength": 150
          },
          "room_type_id": {
            "type": "string",
            "format": "uuid"
          },
          "check_in": {
            "type": "string",
            "format": "date"
          },
          "check_out": {
            "type": "string",
            "format": "date"
          },
          "guests": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "guest": {
            "$ref": "#/components/schemas/ReservationGuestInput"
          }
        },
        "required": [
          "check_in",
          "check_out",
          "external_reservation_id",
          "guest",
          "room_type_id"
        ]
      },
      "ReservationGuestInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 500
          },
          "email": {
            "type": "string",
            "maxLength": 254
          },
          "phone": {
            "type": "string",
            "maxLength": 20
          }
        },
        "required": [
          "name",
          "phone"
        ]
      },
      "ReservationResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "reservation_id": {
            "type": "string"
          },
          "external_reservation_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "external_reservation_id",
          "reservation_id",
          "status",
          "success"
        ]
      },
      "RoomType": {
        "type": "object",
        "description": "room_type_id is the room type's own UUID id -- HotelRoomType.code\nis optional and not guaranteed unique per tenant, so it isn't usable\nas a stable external id (see the restro menu serializer's item_id for\nthe same \"no separate mapping needed\" idea, there via Dish.sku).",
        "properties": {
          "room_type_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "nullable": true
          },
          "max_occupancy": {
            "type": "integer"
          },
          "base_price": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$",
            "nullable": true
          }
        },
        "required": [
          "base_price",
          "code",
          "max_occupancy",
          "name",
          "room_type_id"
        ]
      },
      "ServiceEnum": {
        "enum": [
          "dine_in",
          "delivery",
          "reservation"
        ],
        "type": "string",
        "description": "* `dine_in` - Dine In\n* `delivery` - Delivery\n* `reservation` - Reservation"
      },
      "StatusEnum": {
        "enum": [
          "used",
          "unused"
        ],
        "type": "string",
        "description": "* `used` - Used\n* `unused` - Unused"
      },
      "SubMenu": {
        "type": "object",
        "description": "Base serializer for tenant-aware models.\n\nResponsibilities:\n- Inject tenant on create\n- Prevent tenant override",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "tenant": {
            "type": "integer",
            "readOnly": true
          },
          "active_dishes": {
            "type": "string",
            "readOnly": true
          },
          "used_in": {
            "type": "string",
            "readOnly": true
          },
          "deleted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "restored_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "transaction_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "nullable": true
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "is_active": {
            "type": "boolean"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "menu_sets": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "active_dishes",
          "created_at",
          "id",
          "name",
          "tenant",
          "updated_at",
          "used_in"
        ]
      },
      "ThirdPartyAddOn": {
        "type": "object",
        "description": "Same reasoning as ThirdPartyDishSerializer below -- AddOn has its\nown, separate ingredients -> inventory item -> default_supplier ->\ncreated_by chain (apps.inventory.serializers.addon_ingredient /\napps.inventory.serializers.item / apps.inventory.serializers.supplier)\nthat leaks the same staff PII. Dropped for the same reason.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "maxLength": 512
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "quantity": {
            "type": "integer",
            "maximum": 2147483647,
            "minimum": 0
          },
          "price": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$"
          },
          "currency": {
            "type": "string",
            "readOnly": true
          },
          "available": {
            "type": "boolean"
          },
          "used_in": {
            "type": "string",
            "readOnly": true
          },
          "dishes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddOnDish"
            },
            "readOnly": true
          }
        },
        "required": [
          "currency",
          "dishes",
          "id",
          "name",
          "price",
          "used_in"
        ]
      },
      "ThirdPartyDish": {
        "type": "object",
        "description": "Identical to the internal, staff-facing DishSerializer -- same\ncategory/kitchen/sub_menu/menu_set/tax fields/dish_type/image/\nprice_variants/addons -- with exactly one exception: `ingredients` is\ndropped, on both the dish itself and its nested addons.\n\nThat nested chain (ingredients -> inventory item -> supplier ->\ncreated_by) pulls a staff user's email/phone/date_of_birth/gender and\ntheir full cross-tenant membership list (UserSerializer.tenants,\nsystem-wide, not scoped to this tenant) into what is otherwise a\npublic API response and public Swagger docs -- a real PII/cross-tenant\nleak, not a style choice. Everything else stays byte-for-byte the\nsame as what staff see in the admin app.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "dish_type": {
            "nullable": true,
            "oneOf": [
              {
                "$ref": "#/components/schemas/DishTypeEnum"
              },
              {
                "$ref": "#/components/schemas/BlankEnum"
              },
              {
                "$ref": "#/components/schemas/NullEnum"
              }
            ]
          },
          "name": {
            "type": "string",
            "maxLength": 512
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "category": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Category"
              }
            ],
            "readOnly": true
          },
          "category_id": {
            "type": "string",
            "format": "uuid",
            "writeOnly": true,
            "nullable": true
          },
          "sub_menu": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SubMenu"
              }
            ],
            "readOnly": true
          },
          "sub_menu_id": {
            "type": "string",
            "format": "uuid",
            "writeOnly": true,
            "nullable": true
          },
          "kitchen": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Kitchen"
              }
            ],
            "readOnly": true
          },
          "kitchen_id": {
            "type": "string",
            "format": "uuid",
            "writeOnly": true,
            "nullable": true
          },
          "addons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ThirdPartyAddOn"
            },
            "readOnly": true
          },
          "addon_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid",
              "writeOnly": true
            },
            "writeOnly": true
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "sku": {
            "type": "string",
            "maxLength": 50
          },
          "is_active": {
            "type": "boolean"
          },
          "is_taxable": {
            "type": "boolean"
          },
          "extra_tax_percentage": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,3}(?:\\.\\d{0,2})?$",
            "description": "Extra tax percentage applied on top of the tenant's base tax when is_taxable is set (e.g. beverages)."
          },
          "price": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$"
          },
          "currency": {
            "type": "string",
            "readOnly": true
          },
          "menu_set": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MenuSet"
              }
            ],
            "readOnly": true
          },
          "price_variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Variant"
            },
            "readOnly": true
          },
          "price_variant": {
            "type": "string",
            "writeOnly": true
          }
        },
        "required": [
          "addons",
          "category",
          "currency",
          "id",
          "kitchen",
          "menu_set",
          "name",
          "price",
          "price_variants",
          "sub_menu"
        ]
      },
      "ThirdPartyOrderItemAddonInput": {
        "type": "object",
        "properties": {
          "addon_id": {
            "type": "string",
            "maxLength": 100
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          }
        },
        "required": [
          "addon_id"
        ]
      },
      "TicketTypeEnum": {
        "enum": [
          "KOT",
          "BOT",
          "OTHER"
        ],
        "type": "string",
        "description": "* `KOT` - Kitchen\n* `BOT` - Bar\n* `OTHER` - Other"
      },
      "Variant": {
        "type": "object",
        "description": "Base serializer for tenant-aware models.\n\nResponsibilities:\n- Inject tenant on create\n- Prevent tenant override",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true
          },
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "price": {
            "type": "string",
            "format": "decimal",
            "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$"
          },
          "currency": {
            "type": "string",
            "readOnly": true
          },
          "dish": {
            "type": "string",
            "readOnly": true
          },
          "dish_type": {
            "type": "string",
            "readOnly": true
          }
        },
        "required": [
          "currency",
          "dish",
          "dish_type",
          "id",
          "name",
          "price"
        ]
      },
      "WebhookRegister": {
        "type": "object",
        "properties": {
          "webhook_url": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "webhook_url"
        ]
      },
      "WebhookRegisterResponse": {
        "type": "object",
        "properties": {
          "webhook_url": {
            "type": "string",
            "format": "uri"
          },
          "webhook_secret": {
            "type": "string"
          }
        },
        "required": [
          "webhook_secret",
          "webhook_url"
        ]
      }
    },
    "securitySchemes": {
      "jwtAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  }
}