{
  "openapi": "3.1.0",
  "info": {
    "title": "KitchenFlow Restaurant API",
    "version": "1.0.0",
    "description": "Tenant-scoped API. Send a scoped API key as Authorization: Bearer kf_live_... . API key provisioning is administrator-only. Restaurant provisioning requires a platform key (api_keys.restaurant_id null) with the restaurants:write scope. Cash-register payments (payments) and cash turns (shifts) are recorded through this API; automatically charging customers still requires a payment provider."
  },
  "servers": [{ "url": "/api/v1" }],
  "security": [{ "KitchenFlowKey": [] }],
  "tags": [
    { "name": "Orders" }, { "name": "Menu" }, { "name": "Payments" }, { "name": "Shifts" }, { "name": "Restaurant" },
    { "name": "Tables" }, { "name": "Analytics" }, { "name": "API key administration" }
  ],
  "components": {
    "securitySchemes": {
      "KitchenFlowKey": { "type": "http", "scheme": "bearer", "bearerFormat": "kf_live API key" },
      "SupabaseSession": { "type": "http", "scheme": "bearer", "bearerFormat": "Supabase access token" }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" } }, "required": ["error"] },
      "OrderInput": {
        "type": "object", "required": ["items"],
        "properties": {
          "table": { "type": ["string", "null"], "maxLength": 120 },
          "type": { "type": "string", "enum": ["dine-in", "takeout"], "default": "dine-in" },
          "items": { "type": "array", "minItems": 1, "maxItems": 100, "items": {
            "type": "object", "required": ["name", "price", "quantity"],
            "properties": {
              "name": { "type": "string", "minLength": 1, "maxLength": 160 },
              "price": { "type": "number", "minimum": 0 }, "quantity": { "type": "integer", "minimum": 1 },
              "course": { "type": "string" }, "station": { "type": ["string", "null"] },
              "customization": { "type": "object", "additionalProperties": true }
            }
          } }
        }
      },
      "MenuItemInput": { "type": "object", "required": ["name", "price"], "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "price": { "type": "number", "minimum": 0 },
        "course": { "type": "string" }, "target_prep_time": { "type": "integer", "minimum": 1, "maximum": 240 },
        "is_available": { "type": "boolean" }, "category_id": { "type": ["string", "null"], "format": "uuid" },
        "station_id": { "type": ["string", "null"], "format": "uuid" }
      } },
      "MenuItemPatch": { "type": "object", "minProperties": 1, "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 160 }, "price": { "type": "number", "minimum": 0 },
        "course": { "type": "string" }, "is_available": { "type": "boolean" },
        "category_id": { "type": ["string", "null"], "format": "uuid" },
        "station_id": { "type": ["string", "null"], "format": "uuid" }
      } },
      "TableInput": { "type": "object", "required": ["name"], "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 80 },
        "capacity": { "type": "integer", "minimum": 1, "maximum": 100, "default": 2 }
      } },
      "ApiKeyInput": { "type": "object", "required": ["name", "scopes"], "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 80 },
        "scopes": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "type": "string", "enum": ["orders:read", "orders:write", "orders:update", "menu:read", "menu:write", "payments:read", "payments:write", "shifts:read", "shifts:write", "restaurants:read", "restaurants:write", "tables:read", "tables:write", "analytics:read"] } },
        "rate_limit": { "type": "integer", "minimum": 1, "maximum": 1200, "default": 60 }
      } },
      "RestaurantInput": { "type": "object", "required": ["name"], "properties": {
        "name": { "type": "string", "minLength": 1, "maxLength": 160 },
        "currency": { "type": "string", "minLength": 1, "maxLength": 8, "default": "$" },
        "tax_rate": { "type": "number", "minimum": 0, "maximum": 100, "default": 0 },
        "tier": { "type": "string", "enum": ["starter", "pro", "agency"], "default": "starter" }
      } },
      "PaymentInput": { "type": "object", "required": ["amount", "method"], "properties": {
        "amount": { "type": "number", "minimum": 0.01, "maximum": 9999999.99 },
        "method": { "type": "string", "enum": ["cash", "card", "transfer", "other"] },
        "status": { "type": "string", "enum": ["pending", "completed", "refunded"], "default": "completed" },
        "shift_id": { "type": ["string", "null"], "format": "uuid" },
        "order_id": { "type": ["string", "null"], "format": "uuid" },
        "external_reference": { "type": ["string", "null"], "maxLength": 512 }
      } },
      "TerminalChargeInput": { "type": "object", "required": ["amount"], "properties": {
        "amount": { "type": "number", "minimum": 0.01, "maximum": 9999999.99 },
        "currency": { "type": "string", "description": "ISO 4217. Defaults to the restaurant currency normalized (símbolos locales como '$' o 'S/' caen a USD)." },
        "description": { "type": "string", "maxLength": 512 }
      } },
      "TerminalCharge": { "type": "object", "required": ["provider", "payment_id", "status", "requires_confirmation", "metadata"], "properties": {
        "provider": { "type": "string", "enum": ["stripe-terminal", "square", "mercado-pago"] },
        "payment_id": { "type": "string", "description": "Id remoto: payment_intent (Stripe Terminal), checkout (Square) u order (Mercado Pago Point)." },
        "status": { "type": "string", "enum": ["pending"] },
        "requires_confirmation": { "type": "boolean", "description": "true solo para Stripe Terminal: la confirmación ocurre en el lector con client_secret." },
        "metadata": { "type": "object", "additionalProperties": true }
      } },
      "ShiftOpenInput": { "type": "object", "properties": {
        "opening_cash": { "type": "number", "minimum": 0, "default": 0 },
        "notes": { "type": ["string", "null"], "maxLength": 500 }
      } },
      "ShiftCloseInput": { "type": "object", "required": ["status"], "properties": {
        "status": { "type": "string", "enum": ["closed"] },
        "counted_cash": { "type": "number", "minimum": 0 },
        "notes": { "type": ["string", "null"], "maxLength": 500 }
      } }
    },
    "parameters": {
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": true, "description": "Reuse this UUID only for retries of the same order.", "schema": { "type": "string", "format": "uuid" } }
    },
    "responses": {
      "BadRequest": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid credential", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "Credential lacks the required scope", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "API key rate limit exceeded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  },
  "paths": {
    "/orders": {
      "get": { "tags": ["Orders"], "summary": "List tenant orders", "x-required-scope": "orders:read", "parameters": [
        { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
        { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["new", "preparing", "ready", "completed"] } }
      ], "responses": { "200": { "description": "Orders with their order items" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Orders"], "summary": "Create an order", "x-required-scope": "orders:write", "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OrderInput" } } } }, "responses": { "201": { "description": "Order created; retries with the same key do not duplicate it" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/orders/{id}": { "patch": { "tags": ["Orders"], "summary": "Update order status", "x-required-scope": "orders:update", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["status"], "properties": { "status": { "type": "string", "enum": ["new", "preparing", "ready", "completed"] } } } } } }, "responses": { "200": { "description": "Updated order" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "description": "Order not found in this tenant" }, "429": { "$ref": "#/components/responses/RateLimited" } } } },
    "/menu": {
      "get": { "tags": ["Menu"], "summary": "List tenant menu items", "x-required-scope": "menu:read", "responses": { "200": { "description": "Menu items" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Menu"], "summary": "Create menu item", "x-required-scope": "menu:write", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MenuItemInput" } } } }, "responses": { "201": { "description": "Created menu item" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/menu/{id}": { "patch": { "tags": ["Menu"], "summary": "Update menu item", "x-required-scope": "menu:write", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MenuItemPatch" } } } }, "responses": { "200": { "description": "Updated menu item" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "description": "Menu item not found in this tenant" }, "429": { "$ref": "#/components/responses/RateLimited" } } } },
    "/restaurants": {
      "get": { "tags": ["Restaurant"], "summary": "Read the restaurant for this key", "x-required-scope": "restaurants:read", "responses": { "200": { "description": "Restaurant record" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Restaurant"], "summary": "Provision a new restaurant tenant (platform key only)", "x-required-scope": "restaurants:write", "description": "Creates a restaurant plus an initial API key returned once. Rejected with 403 for tenant-scoped keys.", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RestaurantInput" } } } }, "responses": { "201": { "description": "Created restaurant with a one-time initial API key" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/tables": {
      "get": { "tags": ["Tables"], "summary": "List tenant tables", "x-required-scope": "tables:read", "responses": { "200": { "description": "Restaurant tables" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Tables"], "summary": "Create a restaurant table", "x-required-scope": "tables:write", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TableInput" } } } }, "responses": { "201": { "description": "Created table" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/analytics/kpis": { "get": { "tags": ["Analytics"], "summary": "Read today's KPIs (UTC)", "x-required-scope": "analytics:read", "responses": { "200": { "description": "Order count, gross total, active count, and completed count" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } } },
    "/payments": {
      "get": { "tags": ["Payments"], "summary": "List tenant payments (filter by shift/method/status)", "x-required-scope": "payments:read", "parameters": [
        { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
        { "name": "shift_id", "in": "query", "schema": { "type": "string", "format": "uuid" } },
        { "name": "method", "in": "query", "schema": { "type": "string", "enum": ["cash", "card", "transfer", "other"] } },
        { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["pending", "completed", "refunded"] } }
      ], "responses": { "200": { "description": "Payments, newest first" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Payments"], "summary": "Record a payment by method", "x-required-scope": "payments:write", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaymentInput" } } } }, "responses": { "201": { "description": "Recorded payment" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/payments/terminal": {
      "get": { "tags": ["Payments"], "summary": "Check whether a terminal payments provider is configured", "x-required-scope": "payments:read", "responses": { "200": { "description": "Provider status: { configured, provider }" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Payments"], "summary": "Create a card-present charge on the physical terminal (Stripe Terminal / Square / Mercado Pago Point)", "description": "Requiere PAYMENTS_PROVIDER y las keys del proveedor en el servidor. Sin config responde 503 payments_not_configured. El monto se convierte a unidades mínimas según la divisa.", "x-required-scope": "payments:write", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TerminalChargeInput" } } } }, "responses": { "201": { "description": "Terminal charge created (pending)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TerminalCharge" } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "description": "No Point device available (Mercado Pago)" }, "502": { "description": "Provider did not return a usable id" }, "503": { "description": "Terminal payments not configured on this server (payments_not_configured)" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/shifts": {
      "get": { "tags": ["Shifts"], "summary": "List tenant cash turns", "x-required-scope": "shifts:read", "parameters": [
        { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
        { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["open", "closed"] } }
      ], "responses": { "200": { "description": "Shifts, newest first" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" } } },
      "post": { "tags": ["Shifts"], "summary": "Open a cash turn", "x-required-scope": "shifts:write", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ShiftOpenInput" } } } }, "responses": { "201": { "description": "Opened shift" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "description": "A shift is already open" }, "429": { "$ref": "#/components/responses/RateLimited" } } }
    },
    "/shifts/{id}": { "patch": { "tags": ["Shifts"], "summary": "Close an open cash turn (arqueo)", "x-required-scope": "shifts:write", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ShiftCloseInput" } } } }, "responses": { "200": { "description": "Closed shift with counted cash" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "description": "Shift not found in this tenant" }, "409": { "description": "Shift is not open" }, "429": { "$ref": "#/components/responses/RateLimited" } } } },
    "/keys": {
      "get": { "tags": ["API key administration"], "summary": "List key metadata for the signed-in admin's restaurant", "security": [{ "SupabaseSession": [] }], "responses": { "200": { "description": "Metadata only; API secrets are not returned" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "description": "An active restaurant administrator session is required" } } },
      "post": { "tags": ["API key administration"], "summary": "Create a scoped API key", "security": [{ "SupabaseSession": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiKeyInput" } } } }, "responses": { "201": { "description": "Raw key is returned once; store securely" }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "description": "An active restaurant administrator session is required" } } }
    },
    "/keys/{id}": { "delete": { "tags": ["API key administration"], "summary": "Revoke an API key", "security": [{ "SupabaseSession": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }], "responses": { "200": { "description": "Key revoked" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "description": "An active restaurant administrator session is required" }, "404": { "description": "Active key not found in this tenant" } } } }
  }
}
