{
  "openapi": "3.1.0",
  "info": {
    "title": "Covered Public API",
    "version": "1.0.0",
    "description": "Public API for Covered — the hospitality OS for restaurants, bars, and venues. Use these endpoints to integrate bookings, customers, menus, payments, and webhooks into partner products.\n\n## Authentication\n\nAll endpoints require an API key in the `Authorization: Bearer covt_<prefix>_<secret>` header. Operators mint keys from `/dashboard/settings/api` and tick the scopes their integration needs. The secret is shown ONCE at creation — store it securely.\n\n## Rate limiting\n\n60 requests/minute/API key/route by default (some routes raise this). Every response — success or error — carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers.\n\n## Pagination\n\nList endpoints use cursor pagination. Pass `limit` (default 50, max 200 or 500 depending on route) and `cursor` (the `nextCursor` from the previous response).\n\n## Errors\n\nAll error responses share the shape `{ \"error\": \"human-readable string\" }` with the correct HTTP status. See the `Error` schema below.\n\n## Versioning\n\nNo breaking changes inside v1. Breaking changes ship as `/api/v2/*` with a `Sunset` header on the deprecated v1 equivalent (90-day grace period minimum). See https://covered.technology/docs/api-versioning.",
    "contact": {
      "name": "Covered Support",
      "email": "api@covered.technology",
      "url": "https://covered.technology/api-docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://covered.technology/terms"
    }
  },
  "servers": [
    {
      "url": "https://covered.technology",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "bookings", "description": "Create, read, update, and cancel reservations." },
    { "name": "customers", "description": "Read and update customer profiles (PII decrypted server-side)." },
    { "name": "tables", "description": "Read the venue floor-plan layout." },
    { "name": "availability", "description": "Query available time slots for a date + party + booking type." },
    { "name": "menus", "description": "Read booking-type menus and pricing (currency-aware)." },
    { "name": "payments", "description": "Read charges (deposits, no-show fees, card-of-security)." },
    { "name": "webhooks", "description": "Register and remove outbound webhook endpoints." },
    { "name": "audit", "description": "Read recent audit log entries for compliance / SIEM." }
  ],
  "security": [
    { "ApiKeyAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "covt_<prefix>_<secret>",
        "description": "API key minted at /dashboard/settings/api. Carries the scopes ticked at creation time."
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 50 },
        "description": "Maximum number of items to return."
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": { "type": "string" },
        "description": "Pagination cursor. Pass the `nextCursor` value from the previous response."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "Missing API key" }
        }
      },
      "Pagination": {
        "type": "object",
        "required": ["limit"],
        "properties": {
          "nextCursor": { "type": ["string", "null"] },
          "limit": { "type": "integer" }
        }
      },
      "Money": {
        "type": "object",
        "required": ["amountSmallestUnit", "currency"],
        "description": "Monetary amount in the smallest currency unit (pence for GBP, cents for USD), accompanied by the ISO-4217 currency code.",
        "properties": {
          "amountSmallestUnit": { "type": "integer", "example": 1500 },
          "currency": { "type": "string", "example": "GBP", "description": "ISO 4217 three-letter code." }
        }
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "bookingRef": { "type": "string", "example": "ABCD2345" },
          "bookingDate": { "type": "string", "format": "date-time" },
          "timeSlot": { "type": "string", "example": "19:30" },
          "partySize": { "type": "integer" },
          "bookingType": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["PENDING", "PENDING_DEPOSIT", "CONFIRMED", "SEATED", "COMPLETED", "CANCELLED", "NO_SHOW"]
          },
          "source": { "type": "string" },
          "specialRequests": { "type": ["string", "null"] },
          "version": { "type": "integer", "description": "Optimistic concurrency token. Pass in If-Match on PATCH." },
          "customer": { "$ref": "#/components/schemas/Customer" },
          "table": {
            "type": ["object", "null"],
            "properties": {
              "id": { "type": "string" },
              "tableName": { "type": "string" }
            }
          }
        }
      },
      "CreateBookingInput": {
        "type": "object",
        "required": ["bookingDate", "timeSlot", "partySize", "bookingType", "firstName", "lastName", "email", "phone"],
        "properties": {
          "bookingDate": { "type": "string", "example": "2026-06-01" },
          "timeSlot": { "type": "string", "example": "19:30" },
          "partySize": { "type": "integer", "minimum": 1, "maximum": 50 },
          "bookingType": { "type": "string" },
          "firstName": { "type": "string" },
          "lastName": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": "string" },
          "tableTypePreference": { "type": ["string", "null"] },
          "specialRequests": { "type": ["string", "null"] },
          "whatsappOptIn": { "type": "boolean" },
          "smsOptIn": { "type": "boolean" },
          "emailOptIn": { "type": "boolean" },
          "idempotencyKey": { "type": "string", "description": "Optional. Pass a stable key (e.g. partner-side order id) to make POST safely retryable." }
        }
      },
      "PatchBookingInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "status": { "type": "string", "enum": ["PENDING", "CONFIRMED", "SEATED", "COMPLETED", "CANCELLED", "NO_SHOW"] },
          "bookingDate": { "type": "string", "example": "2026-06-01" },
          "timeSlot": { "type": "string", "example": "20:00" },
          "partySize": { "type": "integer", "minimum": 1, "maximum": 50 },
          "specialRequests": { "type": ["string", "null"] }
        }
      },
      "Customer": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "firstName": { "type": "string" },
          "lastName": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "phone": { "type": ["string", "null"] },
          "whatsappOptIn": { "type": "boolean" },
          "smsOptIn": { "type": "boolean" },
          "emailOptIn": { "type": "boolean" },
          "isVip": { "type": "boolean" },
          "visitCount": { "type": "integer" },
          "noShowCount": { "type": "integer" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "PatchCustomerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "firstName": { "type": "string" },
          "lastName": { "type": "string" },
          "phone": { "type": ["string", "null"] },
          "whatsappOptIn": { "type": "boolean" },
          "smsOptIn": { "type": "boolean" },
          "emailOptIn": { "type": "boolean" },
          "notes": { "type": ["string", "null"] },
          "isVip": { "type": "boolean" }
        }
      },
      "Table": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "capacity": { "type": "integer" },
          "minCovers": { "type": "integer" },
          "section": { "type": "string" },
          "tableType": { "type": "string" },
          "shape": { "type": "string" },
          "isActive": { "type": "boolean" },
          "status": { "type": "string", "example": "AVAILABLE" },
          "venueId": { "type": ["string", "null"] },
          "revenue": {
            "type": "object",
            "description": "Only present when ?includeRevenue=true. Sum of succeeded charges against bookings assigned to this table.",
            "properties": {
              "amountSmallestUnit": { "type": "integer" },
              "currency": { "type": "string" }
            }
          }
        }
      },
      "AvailabilitySlot": {
        "type": "object",
        "properties": {
          "time": { "type": "string", "example": "19:30" },
          "fittingTables": { "type": "integer", "description": "Number of tables with capacity >= partySize." },
          "bookable": { "type": "boolean" }
        }
      },
      "MenuItem": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "category": { "type": "string" },
          "bookingTypeIds": { "type": "array", "items": { "type": "string" } },
          "venueIds": { "type": ["array", "null"], "items": { "type": "string" } },
          "isAvailable": { "type": "boolean" },
          "imageUrl": { "type": ["string", "null"] },
          "allergens": { "type": "array", "items": { "type": "string" } },
          "sortOrder": { "type": "integer" },
          "price": {
            "type": "object",
            "properties": {
              "amountSmallestUnit": { "type": "integer" },
              "amountMajor": { "type": "number" },
              "amountMaxSmallestUnit": { "type": ["integer", "null"] },
              "currency": { "type": "string" }
            }
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "bookingId": { "type": "string" },
          "amount": { "$ref": "#/components/schemas/Money" },
          "chargeType": { "type": "string", "enum": ["DEPOSIT", "CARD_OF_SECURITY"] },
          "reason": { "type": "string", "enum": ["NO_SHOW", "LATE_CANCELLATION", "DAMAGE", "OTHER"] },
          "description": { "type": ["string", "null"] },
          "stripePaymentIntentId": { "type": "string" },
          "stripePaymentStatus": { "type": "string", "example": "succeeded" },
          "chargedByEmail": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "description": "P2 Rec #19 (2026-05-19) — expanded event catalogue. Grouped: Bookings (4), Customers (4), Payments (4), POS (3), Wallet passes (3), Marketing delivery (3). Pick the subset relevant to your integration; subscribing to a family you don't consume only costs partner-side bandwidth (delivery still runs through the same HMAC-signed + exponential-backoff pipeline as the original 4 booking events).",
        "enum": [
          "booking.created",
          "booking.updated",
          "booking.cancelled",
          "booking.no_show_charged",
          "customer.created",
          "customer.updated",
          "customer.merged",
          "customer.erased",
          "payment.charged",
          "payment.refunded",
          "payment.dispute_opened",
          "payment.dispute_closed",
          "pos.check.opened",
          "pos.check.closed",
          "pos.check.voided",
          "wallet.pass.installed",
          "wallet.pass.uninstalled",
          "wallet.pass.updated",
          "marketing.delivery.sent",
          "marketing.delivery.opened",
          "marketing.delivery.clicked"
        ]
      },
      "WebhookEndpoint": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "events": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/WebhookEventType" }
          },
          "lastSuccessAt": { "type": ["string", "null"], "format": "date-time" },
          "lastFailureAt": { "type": ["string", "null"], "format": "date-time" },
          "consecutiveFailures": { "type": "integer" },
          "disabledAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "CreateWebhookInput": {
        "type": "object",
        "required": ["url", "events"],
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "HTTPS only." },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "#/components/schemas/WebhookEventType" }
          }
        }
      },
      "WebhookEventEnvelope": {
        "type": "object",
        "description": "Wire format of every outbound webhook POST body. The HMAC signature on `X-Covered-Signature` covers the exact JSON bytes of this envelope.",
        "properties": {
          "event": { "$ref": "#/components/schemas/WebhookEventType" },
          "deliveryId": { "type": "string", "description": "Unique id for this delivery attempt — useful for idempotent partner-side processing." },
          "createdAt": { "type": "string", "format": "date-time" },
          "data": {
            "type": "object",
            "description": "The event-specific payload. Shape depends on `event`. All payloads carry `organisationId` so multi-tenant consumers can route safely. Money fields carry `{ amountSmallestUnit, currency }` after the multi-currency rollout."
          }
        }
      },
      "WebhookEndpointWithSecret": {
        "allOf": [
          { "$ref": "#/components/schemas/WebhookEndpoint" },
          {
            "type": "object",
            "properties": {
              "secret": { "type": "string", "description": "HMAC signing secret. Returned ONCE on create — store it securely." }
            }
          }
        ]
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "action": { "type": "string" },
          "actorId": { "type": ["string", "null"] },
          "actorEmail": { "type": "string" },
          "actorIp": { "type": ["string", "null"] },
          "resourceType": { "type": ["string", "null"] },
          "resourceId": { "type": ["string", "null"] },
          "metadata": { "type": ["object", "null"] },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, malformed, or revoked API key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "API key is valid but is missing the required scope.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "NotFound": {
        "description": "Resource not found (or not owned by the authed tenant — cross-tenant probes return 404).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "BadRequest": {
        "description": "Validation failure on query / body.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimitExceeded": {
        "description": "Too many requests for this API key in the current window.",
        "headers": {
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "paths": {
    "/api/v1/bookings": {
      "get": {
        "tags": ["bookings"],
        "operationId": "listBookings",
        "summary": "List bookings",
        "description": "Cursor-paginated list of bookings for the authed tenant. Filter by date or status.",
        "security": [{ "ApiKeyAuth": ["bookings:read"] }],
        "parameters": [
          { "name": "date", "in": "query", "schema": { "type": "string", "example": "2026-06-01" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Booking" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      },
      "post": {
        "tags": ["bookings"],
        "operationId": "createBooking",
        "summary": "Create a booking",
        "security": [{ "ApiKeyAuth": ["bookings:write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateBookingInput" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/Booking" } }
                }
              }
            }
          },
          "200": { "description": "Idempotent replay — body identical to 201." },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/bookings/{id}": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
      ],
      "get": {
        "tags": ["bookings"],
        "operationId": "getBooking",
        "summary": "Read a booking",
        "security": [{ "ApiKeyAuth": ["bookings:read"] }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Booking" } } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      },
      "patch": {
        "tags": ["bookings"],
        "operationId": "patchBooking",
        "summary": "Update a booking",
        "description": "Honours `If-Match: <version>` for optimistic concurrency. A mismatch returns 412 with the current version.",
        "security": [{ "ApiKeyAuth": ["bookings:write"] }],
        "parameters": [
          { "name": "If-Match", "in": "header", "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatchBookingInput" } } }
        },
        "responses": {
          "200": { "description": "OK" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "412": { "description": "Precondition failed — If-Match version did not match the current version." },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      },
      "delete": {
        "tags": ["bookings"],
        "operationId": "cancelBooking",
        "summary": "Cancel a booking",
        "description": "Idempotent — calling on an already-cancelled booking returns 200.",
        "security": [{ "ApiKeyAuth": ["bookings:write"] }],
        "responses": {
          "200": { "description": "OK" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/customers": {
      "get": {
        "tags": ["customers"],
        "operationId": "listCustomers",
        "summary": "List customers",
        "security": [{ "ApiKeyAuth": ["customers:read"] }],
        "parameters": [
          { "name": "email", "in": "query", "schema": { "type": "string", "format": "email" } },
          { "name": "phone", "in": "query", "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Customer" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/customers/{id}": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
      ],
      "get": {
        "tags": ["customers"],
        "operationId": "getCustomer",
        "summary": "Read a customer",
        "security": [{ "ApiKeyAuth": ["customers:read"] }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Customer" } } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      },
      "patch": {
        "tags": ["customers"],
        "operationId": "patchCustomer",
        "summary": "Update a customer profile",
        "description": "Strict allowlist of fields. Derived state (visitCount, totalSpend) is owned by the dashboard.",
        "security": [{ "ApiKeyAuth": ["customers:write"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatchCustomerInput" } } }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Customer" } } }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "Customer has been erased (GDPR Article 17) and cannot be modified." },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/tables": {
      "get": {
        "tags": ["tables"],
        "operationId": "listTables",
        "summary": "List tables",
        "description": "Floor-plan layout. Pass `includeRevenue=true` to attach a currency-aware revenue summary per table.",
        "security": [{ "ApiKeyAuth": ["tables:read"] }],
        "parameters": [
          { "name": "venueId", "in": "query", "schema": { "type": "string" } },
          { "name": "section", "in": "query", "schema": { "type": "string" } },
          { "name": "includeRevenue", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Table" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/availability": {
      "get": {
        "tags": ["availability"],
        "operationId": "queryAvailability",
        "summary": "Query available slots",
        "description": "Returns 30-minute slots for the given date + party size, filtered to the booking-type's hours and the tenant's table capacity.",
        "security": [{ "ApiKeyAuth": ["availability:read"] }],
        "parameters": [
          { "name": "date", "in": "query", "required": true, "schema": { "type": "string", "example": "2026-06-01" } },
          { "name": "partySize", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 1, "maximum": 200 } },
          { "name": "bookingTypeId", "in": "query", "schema": { "type": "string" } },
          { "name": "venueId", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/AvailabilitySlot" } },
                    "date": { "type": "string" },
                    "partySize": { "type": "integer" },
                    "bookingTypeId": { "type": ["string", "null"] },
                    "reason": { "type": "string", "description": "Present when data is empty. e.g. blocked_date, venue_closed, day_not_available, party_above_max." }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/menus": {
      "get": {
        "tags": ["menus"],
        "operationId": "listMenuItems",
        "summary": "List menu items",
        "description": "Read-only menu surface. Authoring lives in the dashboard. Prices are currency-aware.",
        "security": [{ "ApiKeyAuth": ["menus:read"] }],
        "parameters": [
          { "name": "bookingTypeId", "in": "query", "schema": { "type": "string" } },
          { "name": "category", "in": "query", "schema": { "type": "string" } },
          { "name": "includeUnavailable", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/MenuItem" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/payments": {
      "get": {
        "tags": ["payments"],
        "operationId": "listPayments",
        "summary": "List charges",
        "description": "Deposits, no-show fees, and card-of-security charges. Every entry includes the ISO-4217 currency code.",
        "security": [{ "ApiKeyAuth": ["payments:read"] }],
        "parameters": [
          { "name": "bookingId", "in": "query", "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "example": "2026-01-01" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "example": "2026-12-31" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Payment" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "tags": ["webhooks"],
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "description": "Signing secret is never returned on read — only at creation.",
        "security": [{ "ApiKeyAuth": ["webhooks:manage"] }],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/WebhookEndpoint" } }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      },
      "post": {
        "tags": ["webhooks"],
        "operationId": "createWebhook",
        "summary": "Register an endpoint",
        "description": "Returns the plaintext signing secret ONCE — store it.",
        "security": [{ "ApiKeyAuth": ["webhooks:manage"] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateWebhookInput" } } }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "$ref": "#/components/schemas/WebhookEndpointWithSecret" } }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
      ],
      "delete": {
        "tags": ["webhooks"],
        "operationId": "deleteWebhook",
        "summary": "Delete an endpoint",
        "security": [{ "ApiKeyAuth": ["webhooks:manage"] }],
        "responses": {
          "200": { "description": "OK" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/api/v1/audit": {
      "get": {
        "tags": ["audit"],
        "operationId": "listAuditEntries",
        "summary": "List audit entries",
        "description": "Recent audit log entries for compliance / SIEM integration.",
        "security": [{ "ApiKeyAuth": ["audit:read"] }],
        "parameters": [
          { "name": "action", "in": "query", "schema": { "type": "string" } },
          { "name": "actorEmail", "in": "query", "schema": { "type": "string" } },
          { "name": "resourceType", "in": "query", "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "example": "2026-01-01" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "example": "2026-12-31" } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/AuditEntry" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    }
  }
}
