{
  "openapi": "3.2.0",
  "info": {
    "title": "Redocly Cafe",
    "description": "Demo API for cafe operators (not customers) to manage menus, orders, and revenue.\nCreate API credentials and try it yourself in a realistic OpenAPI workflow.\n",
    "version": "1.1.0",
    "contact": {
      "email": "team@redocly.com",
      "url": "https://redocly.com/contact-us/"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    },
    "termsOfService": "https://redocly.com/subscription-agreement"
  },
  "servers": [
    {
      "url": "https://api.cafe.redocly.com",
      "description": "Live server."
    }
  ],
  "tags": [
    {
      "name": "Authorization",
      "description": "Create a client to demo the API."
    },
    {
      "name": "Products",
      "description": "Operations related to products."
    },
    {
      "name": "Orders",
      "description": "Order management operations."
    },
    {
      "name": "Statistics",
      "description": "Statistics operations."
    }
  ],
  "paths": {
    "/menu": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "List all menu items",
        "description": "Retrieve a collection of menu items with optional filtering and pagination.",
        "operationId": "listMenuItems",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/Sort",
            "description": "Sorts the collection by a single field. Prefix with `-` for descending order\n(for example, `-price`); omit the prefix for ascending order (`price`).\n\n**Sortable fields:** `name`, `price`, `createdAt`, `updatedAt`, `id`"
          },
          {
            "$ref": "#/components/parameters/Filter",
            "description": "Filters the collection items using space-separated `field:value` pairs.\n\n**Filterable fields:** `name`, `category`, `price`, `createdAt`, `id`\n\n**Format:** `field1:value1 field2:value2`\n\n**Supported operators:**\n- `field:value` - Exact match\n- `field:value1,value2` - Match any of the comma-separated values (OR)\n- Time ranges (on `createdAt`): Use `30d` (30 days), `7d` (7 days), `1h` (1 hour), etc.\n\n**Examples:**\n- `category:beverage` - Filter by a single category.\n- `category:beverage,dessert` - Filter by multiple categories.\n- `createdAt:7d` - Menu items created in the last 7 days.\n- `name:Latte` - Filter by name.\n- `category:beverage price:400` - Combine multiple filters."
          },
          {
            "$ref": "#/components/parameters/Search",
            "description": "Performs a case-insensitive text search across the searchable fields, returning\nitems where any of them contain the search term as a substring.\n\n**Searchable fields:** `name`, `photoTextDescription`"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuItemList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Create menu item",
        "description": "Create a new menu item.",
        "operationId": "createMenuItem",
        "security": [
          {
            "OAuth2": [
              "menu:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/MenuItem"
              },
              "examples": {
                "Beverage": {
                  "summary": "Create a beverage",
                  "value": {
                    "name": "Cappuccino",
                    "price": 4500,
                    "category": "beverage",
                    "volume": 250,
                    "containsCaffeine": true,
                    "photoTextDescription": "A hot cappuccino in a white ceramic cup."
                  }
                },
                "Dessert": {
                  "summary": "Create a dessert",
                  "value": {
                    "name": "Chocolate-Brownie",
                    "price": 3500,
                    "category": "dessert",
                    "calories": 420,
                    "photoTextDescription": "A rich chocolate brownie square dusted with cocoa."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Menu item created successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuItem"
                },
                "examples": {
                  "MenuItemResponse": {
                    "value": {
                      "id": "prd_01khr487f7qm7p44xn427m43vb",
                      "object": "menuItem",
                      "name": "coffee",
                      "price": 4000,
                      "category": "beverage",
                      "createdAt": "2026-02-18T10:20:38.228Z",
                      "updatedAt": "2026-02-18T10:20:38.228Z",
                      "volume": 600,
                      "containsCaffeine": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/menu/{menuItemId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MenuItemId"
        }
      ],
      "delete": {
        "tags": [
          "Products"
        ],
        "summary": "Delete a menu item",
        "description": "Delete an existing menu item.",
        "operationId": "deleteMenuItem",
        "security": [
          {
            "OAuth2": [
              "menu:write"
            ]
          }
        ],
        "responses": {
          "204": {
            "description": "Menu item deleted successfully."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/menu-item-images/{menuItemId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MenuItemId"
        }
      ],
      "get": {
        "operationId": "getMenuItemPhoto",
        "summary": "Retrieve a menu item photo",
        "description": "Retrieve the product photo image for a specific menu item.",
        "security": [],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PhotoSize"
          }
        ],
        "responses": {
          "200": {
            "description": "Menu item photo retrieved successfully.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/plain": {
                "schema": {
                  "description": "Alternative image text.",
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/orders": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "List all orders",
        "description": "Retrieve a collection of orders with optional filtering and pagination.",
        "operationId": "listOrders",
        "security": [
          {
            "OAuth2": [
              "orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Filter",
            "description": "Filters the collection items using space-separated `field:value` pairs.\n\n**Filterable fields:** `customerName`, `status`, `totalPrice`, `createdAt`, `id`\n\n**Format:** `field1:value1 field2:value2`\n\n**Supported operators:**\n- `field:value` - Exact match\n- `field:value1,value2` - Match any of the comma-separated values (OR)\n- Time ranges (on `createdAt`): Use `30d` (30 days), `7d` (7 days), `1h` (1 hour), etc.\n\n**Examples:**\n- `status:placed` - Filter by a single status.\n- `status:placed,completed` - Filter by multiple statuses.\n- `createdAt:30d` - Orders created in the last 30 days.\n- `id:ord_01h1s5z6vf2mm1mz3hevnn9va7` - Filter by a specific order ID.\n- `status:placed createdAt:7d` - Combine multiple filters."
          },
          {
            "$ref": "#/components/parameters/Sort",
            "description": "Sorts the collection by a single field. Prefix with `-` for descending order\n(for example, `-createdAt`); omit the prefix for ascending order (`createdAt`).\n\n**Sortable fields:** `customerName`, `status`, `totalPrice`, `createdAt`, `updatedAt`, `id`"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/After"
          },
          {
            "$ref": "#/components/parameters/Before"
          },
          {
            "$ref": "#/components/parameters/Search",
            "description": "Performs a case-insensitive text search across the searchable fields, returning\nitems where any of them contain the search term as a substring.\n\n**Searchable fields:** `customerName`, `id`"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "tags": [
          "Orders"
        ],
        "summary": "Create order",
        "description": "Create a new order.\nOrder items cannot be changed - if they need to be updated, cancel the order and place a new one.\n",
        "operationId": "createOrder",
        "security": [
          {
            "OAuth2": [
              "orders:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Order"
              },
              "examples": {
                "OrderRequest": {
                  "dataValue": {
                    "customerName": "Mary Ann",
                    "orderItems": [
                      {
                        "menuItemId": "prd_01h1s5z6vf2mm1mz3hevnn9va7",
                        "quantity": 2,
                        "comment": "No sugar!",
                        "discount": 0
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order placed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "examples": {
                  "OrderResponse": {
                    "dataValue": {
                      "id": "ord_01h1s5z6vf2mm1mz3hevnn9va7",
                      "customerName": "Mary Ann",
                      "orderItems": [
                        {
                          "menuItemId": "prd_01h1s5z6vf2mm1mz3hevnn9va7",
                          "quantity": 2,
                          "comment": "No sugar!",
                          "discount": 0
                        }
                      ],
                      "object": "order",
                      "status": "placed",
                      "totalPrice": 200,
                      "createdAt": "2026-08-24T14:15:22Z",
                      "updatedAt": "2026-08-24T14:15:22Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/orders/{orderId}": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "Retrieve an order",
        "description": "Retrieve a single order by its ID.",
        "operationId": "getOrderById",
        "security": [
          {
            "OAuth2": [
              "orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "examples": {
                  "OrderResponse": {
                    "dataValue": {
                      "id": "ord_01h1s5z6vf2mm1mz3hevnn9va7",
                      "customerName": "Mary Ann",
                      "orderItems": [
                        {
                          "menuItemId": "prd_01h1s5z6vf2mm1mz3hevnn9va7",
                          "quantity": 2,
                          "comment": "No sugar!",
                          "discount": 0
                        }
                      ],
                      "object": "order",
                      "status": "placed",
                      "totalPrice": 200,
                      "createdAt": "2026-08-24T14:15:22Z",
                      "updatedAt": "2026-08-24T14:15:22Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "patch": {
        "tags": [
          "Orders"
        ],
        "summary": "Partially update an order",
        "description": "Update an existing order status.\nOrder items cannot be changed - if they need to be updated, cancel the order and place a new one.\n",
        "operationId": "updateOrder",
        "security": [
          {
            "OAuth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Partial order update using JSON Merge Patch - only include fields to update.\n",
                "properties": {
                  "status": {
                    "$ref": "#/components/schemas/OrderStatus"
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order updated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "examples": {
                  "OrderResponse": {
                    "dataValue": {
                      "id": "ord_01h1s5z6vf2mm1mz3hevnn9va7",
                      "customerName": "Mary Ann",
                      "orderItems": [
                        {
                          "menuItemId": "prd_01h1s5z6vf2mm1mz3hevnn9va7",
                          "quantity": 2,
                          "comment": "No sugar!",
                          "discount": 0
                        }
                      ],
                      "object": "order",
                      "status": "completed",
                      "totalPrice": 200,
                      "createdAt": "2026-08-24T14:15:22Z",
                      "updatedAt": "2026-08-24T14:15:22Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "delete": {
        "tags": [
          "Orders"
        ],
        "summary": "Delete an order",
        "description": "Delete the order.\nTo keep the order history, cancel the order instead of deleting it.\n",
        "operationId": "deleteOrder",
        "security": [
          {
            "OAuth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "204": {
            "description": "Order deleted successfully."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/order-items": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "List all order items with menu item details",
        "description": "Returns an array of order items for a specific order.\nUse the `filter` parameter to filter by order ID.\n",
        "operationId": "listOrderItems",
        "security": [
          {
            "OAuth2": [
              "orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderItemsFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful operation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "description": "List of menu items that are part of the order.",
                  "items": {
                    "$ref": "#/components/schemas/OrderItem"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/revenue": {
      "get": {
        "tags": [
          "Statistics"
        ],
        "summary": "Get revenue statistics",
        "description": "Retrieve revenue statistics for a configurable date range.\nReturns revenue, order counts, average order amount, and other useful statistics.\n",
        "operationId": "getRevenue",
        "security": [
          {
            "ApiKey": []
          },
          {
            "OAuth2": [
              "revenue:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "description": "Start date for the revenue calculation period (ISO 8601 datetime format).\nDefaults to 30 days ago if not provided.\n",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "examples": {
              "StartOfJanuary": {
                "summary": "Start of January 2026",
                "dataValue": "2026-01-01T00:00:00Z"
              }
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "description": "End date for the revenue calculation period (ISO 8601 datetime format).\nDefaults to current time if not provided.\n",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "examples": {
              "EndOfJanuary": {
                "summary": "End of January 2026",
                "dataValue": "2026-01-31T23:59:59Z"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revenue statistics retrieved successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevenueStatistics"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/oauth2/register": {
      "post": {
        "tags": [
          "Authorization"
        ],
        "summary": "Create OAuth2 client",
        "description": "Register a new OAuth2 client, implementing the Dynamic Client Registration Protocol (RFC 7591) with the standard snake_case metadata names, so off-the-shelf DCR clients work without modification.\nResponses use only the standard snake_case names (RFC 7591 Section 3.2.1).\n\nAll fields are optional.\nIf not provided:\n\n- `scope` defaults to all available scopes (`menu:read menu:write orders:read orders:write revenue:read`)\n- `grant_types` defaults to `authorization_code`, `client_credentials`, and `refresh_token`\n- `token_endpoint_auth_method` defaults to `client_secret_basic` (RFC 7591 Section 2); both supported methods are accepted at the token endpoint regardless of the registered value\n\nThis server registers confidential clients only: every client is issued a `client_secret`, and `token_endpoint_auth_method: none` (public clients such as SPAs or mobile apps, RFC 7591 Section 2) is not supported by design.\n\n`redirect_uris` is required whenever the effective grant types include `authorization_code` (RFC 7591 Section 2), including when `grant_types` is omitted, since the default includes it.\nRegister with `grant_types: [\"client_credentials\"]` for a client that needs no redirect URI.\n\nRefresh token behavior follows the registered grant types: the token endpoint returns a refresh token with `authorization_code` grant responses only when the client registered the `refresh_token` grant type.\nThe `client_credentials` grant never returns refresh tokens (RFC 6749 Section 4.4.3).\nThose clients request a new access token with their own credentials instead.\n\nReturns the registered client information per RFC 7591 Section 3.2.1, including:\n\n- `client_id` and `client_secret` (must be stored securely)\n- `client_id_issued_at` and `client_secret_expires_at` timestamps\n- `registration_client_uri` and `registration_access_token` for managing the registration (RFC 7592)\n- All registered client metadata\n\nErrors use the RFC 7591 Section 3.2.2 format: `400` with `error` set to `invalid_redirect_uri` or `invalid_client_metadata` and a human-readable `error_description`.\n",
        "operationId": "registerOAuth2Client",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterClientObject"
              },
              "examples": {
                "RegisterClientObject": {
                  "summary": "Client credentials only",
                  "dataValue": {
                    "client_name": "auth",
                    "scope": "menu:read menu:write orders:read orders:write revenue:read",
                    "grant_types": [
                      "client_credentials"
                    ]
                  }
                },
                "RegisterClientForAuthorizationCode": {
                  "summary": "Authorization code with refresh tokens (RFC 7591 names)",
                  "dataValue": {
                    "client_name": "pos-terminal",
                    "redirect_uris": [
                      "https://api.cafe.redocly.com/callback"
                    ],
                    "scope": "menu:read orders:read orders:write",
                    "grant_types": [
                      "authorization_code",
                      "refresh_token"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OAuth2 client registered successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Client"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuth2BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      }
    },
    "/oauth2/register/{clientId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OAuth2ClientId"
        }
      ],
      "get": {
        "tags": [
          "Authorization"
        ],
        "summary": "Retrieve OAuth2 client",
        "description": "Read the current registration of an OAuth2 client (RFC 7592 Section 2.1).\nAuthenticate with the `registration_access_token` returned at registration, as a Bearer token in the `Authorization` header.\nAn invalid or missing token returns `401` without revealing whether the client exists.\n",
        "operationId": "getOAuth2Client",
        "security": [
          {
            "RegistrationAccessToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current client registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Client"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/OAuth2Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      },
      "put": {
        "tags": [
          "Authorization"
        ],
        "summary": "Update OAuth2 client",
        "description": "Replace the registration metadata of an OAuth2 client (RFC 7592 Section 2.2).\nAuthenticate with the `registration_access_token` as a Bearer token.\n\nThis is a full replacement, not a merge: omitted metadata fields are reset to their registration defaults.\nThe request must include `client_id` matching the client being updated (RFC 7592 Section 2.2).\nIf `client_secret` is included it must match the current secret.\nCredentials cannot be rotated through this endpoint.\n",
        "operationId": "updateOAuth2Client",
        "security": [
          {
            "RegistrationAccessToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateClientObject"
              },
              "examples": {
                "UpdateClient": {
                  "dataValue": {
                    "client_id": "client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
                    "client_name": "pos-terminal-v2",
                    "redirect_uris": [
                      "https://api.cafe.redocly.com/callback"
                    ],
                    "scope": "menu:read orders:read",
                    "grant_types": [
                      "authorization_code",
                      "refresh_token"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated client registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Client"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuth2BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/OAuth2Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      },
      "delete": {
        "tags": [
          "Authorization"
        ],
        "summary": "Delete OAuth2 client",
        "description": "Deprovision an OAuth2 client (RFC 7592 Section 2.3).\nAuthenticate with the `registration_access_token` as a Bearer token.\nDeleting a client also invalidates all of its tokens and pending authorization codes.\n",
        "operationId": "deleteOAuth2Client",
        "security": [
          {
            "RegistrationAccessToken": []
          }
        ],
        "responses": {
          "204": {
            "description": "Client registration deleted."
          },
          "401": {
            "$ref": "#/components/responses/OAuth2Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      }
    },
    "/oauth2/token": {
      "post": {
        "tags": [
          "Authorization"
        ],
        "summary": "Create access token",
        "description": "OAuth2 token endpoint (RFC 6749 Section 3.2).\nSupports the `authorization_code`, `client_credentials`, and `refresh_token` grant types.\n\n**Client authentication.**\nAuthenticate with HTTP Basic (`Authorization: Basic base64(client_id:client_secret)`) or with `client_id` and `client_secret` in the request body — not both at once (RFC 6749 Section 2.3.1).\n\n**PKCE (RFC 7636).** \nWhen the authorization request included a `code_challenge`, the matching `code_verifier` is required here.\nA mismatch or a `code_verifier` for a code issued without a challenge is rejected with `invalid_grant`.\n`S256` and `plain` challenge methods are supported.\n\n**Refresh tokens.**\nRotate on every use: a successful `refresh_token` grant retires the presented token and returns a replacement.\nRefresh tokens expire 30 days after issuance; rotation restarts that window.\nThe `authorization_code` grant returns a refresh token only when the client is registered for the `refresh_token` grant type.\nThe `client_credentials` grant never returns one (RFC 6749 Section 4.4.3).\n\n**Scopes.**\n`scope` is space-separated per RFC 6749 (comma-separated values are additionally tolerated).\nMust be a subset of the scopes previously granted (authorization code or refresh token) or registered (client credentials).\n\nErrors follow RFC 6749 Section 5.2: `400` (or `401` for `invalid_client`) with the standard `error` codes such as `invalid_grant`, `invalid_client`, `unauthorized_client`, `unsupported_grant_type`, and `invalid_scope`.\n",
        "operationId": "createAccessToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "authorization_code",
                      "client_credentials",
                      "refresh_token"
                    ],
                    "description": "The OAuth2 grant type."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Client identifier. Omit when authenticating with HTTP Basic."
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Client secret. Omit when authenticating with HTTP Basic."
                  },
                  "code": {
                    "type": "string",
                    "description": "The authorization code (required for `authorization_code`)."
                  },
                  "redirect_uri": {
                    "type": "string",
                    "format": "uri",
                    "description": "Must match the redirect URI used in the authorization request. Required for `authorization_code`)."
                  },
                  "code_verifier": {
                    "type": "string",
                    "minLength": 43,
                    "maxLength": 128,
                    "description": "PKCE code verifier (RFC 7636) Required when the authorization request included a `code_challenge`."
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "The refresh token. Required for `refresh_token`."
                  },
                  "scope": {
                    "type": "string",
                    "description": "Space-separated scopes to request. Defaults to the previously granted or registered scopes."
                  }
                },
                "required": [
                  "grant_type"
                ]
              },
              "examples": {
                "ClientCredentials": {
                  "summary": "Client credentials grant",
                  "dataValue": {
                    "grant_type": "client_credentials",
                    "client_id": "client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
                    "client_secret": "YOUR_CLIENT_SECRET",
                    "scope": "menu:read orders:read"
                  }
                },
                "AuthorizationCode": {
                  "summary": "Authorization code exchange with PKCE",
                  "dataValue": {
                    "grant_type": "authorization_code",
                    "client_id": "client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
                    "client_secret": "YOUR_CLIENT_SECRET",
                    "code": "AUTHORIZATION_CODE",
                    "redirect_uri": "https://api.cafe.redocly.com/callback",
                    "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
                  }
                },
                "RefreshToken": {
                  "summary": "Refresh token grant",
                  "dataValue": {
                    "grant_type": "refresh_token",
                    "client_id": "client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
                    "client_secret": "YOUR_CLIENT_SECRET",
                    "refresh_token": "YOUR_REFRESH_TOKEN"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued successfully. The response is not cacheable (it is served with `Cache-Control` set to `no-store`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/OAuth2BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/OAuth2Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      }
    },
    "/oauth2/revoke": {
      "post": {
        "tags": [
          "Authorization"
        ],
        "summary": "Revoke token",
        "description": "OAuth2 token revocation endpoint (RFC 7009). Revokes an access or refresh\ntoken issued to the authenticated client. Access and refresh tokens issued\ntogether are revoked together.\n\nAuthenticate with HTTP Basic or with `client_id` and `client_secret` in the\nrequest body, the same as the token endpoint.\n\n`token_type_hint` is an optional lookup optimization; both token types are searched regardless.\nPer RFC 7009 Section 2.2 the endpoint returns `200` even when the token is unknown or already revoked.\nIt cannot be used to probe for valid tokens.\n",
        "operationId": "revokeToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The access or refresh token to revoke."
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "access_token",
                      "refresh_token"
                    ],
                    "description": "Optional hint about the token type."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Client identifier. Omit when authenticating with HTTP Basic."
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Client secret. Omit when authenticating with HTTP Basic."
                  }
                },
                "required": [
                  "token"
                ]
              },
              "examples": {
                "RevokeAccessToken": {
                  "dataValue": {
                    "token": "YOUR_ACCESS_TOKEN",
                    "token_type_hint": "access_token",
                    "client_id": "client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
                    "client_secret": "YOUR_CLIENT_SECRET"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The token has been revoked or was already invalid."
          },
          "400": {
            "$ref": "#/components/responses/OAuth2BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/OAuth2Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "Authorization"
        ],
        "summary": "Retrieve authorization server metadata",
        "description": "OAuth2 authorization server metadata (RFC 8414).\nLists the authorization, token, registration, and revocation endpoints along with the supported scopes, grant types, client authentication methods, and PKCE code challenge methods.\nClients can discover the server's capabilities instead of hard-coding them.\n",
        "operationId": "getAuthorizationServerMetadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Authorization server metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationServerMetadata"
                },
                "examples": {
                  "Metadata": {
                    "dataValue": {
                      "issuer": "https://api.cafe.redocly.com",
                      "authorization_endpoint": "https://api.cafe.redocly.com/oauth2/authorize",
                      "token_endpoint": "https://api.cafe.redocly.com/oauth2/token",
                      "registration_endpoint": "https://api.cafe.redocly.com/oauth2/register",
                      "revocation_endpoint": "https://api.cafe.redocly.com/oauth2/revoke",
                      "scopes_supported": [
                        "menu:read",
                        "menu:write",
                        "orders:read",
                        "orders:write",
                        "revenue:read"
                      ],
                      "response_types_supported": [
                        "code"
                      ],
                      "response_modes_supported": [
                        "query"
                      ],
                      "grant_types_supported": [
                        "authorization_code",
                        "client_credentials",
                        "refresh_token"
                      ],
                      "token_endpoint_auth_methods_supported": [
                        "client_secret_basic",
                        "client_secret_post"
                      ],
                      "revocation_endpoint_auth_methods_supported": [
                        "client_secret_basic",
                        "client_secret_post"
                      ],
                      "code_challenge_methods_supported": [
                        "S256",
                        "plain"
                      ],
                      "authorization_response_iss_parameter_supported": true,
                      "service_documentation": "https://cafe.redocly.com/openapi/cafe"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound",
            "description": "Authorization server metadata is not available for this issuer (RFC 8414 Section 3)."
          },
          "500": {
            "$ref": "#/components/responses/OAuth2ServerError"
          }
        }
      }
    }
  },
  "webhooks": {
    "order-notification": {
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "orderNotificationWebhook",
        "security": [],
        "summary": "Order notification webhook",
        "description": "Webhook triggered when a new order is placed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderNotification"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook received successfully."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "OAuth2": {
        "type": "oauth2",
        "description": "OAuth2 authorization for API access. The token endpoint accepts `grant_type=authorization_code`, `grant_type=client_credentials`, and `grant_type=refresh_token`.\nStandard OAuth2 client libraries can drive these flows unmodified.\nServer capabilities are discoverable from the [RFC 8414 metadata endpoint](https://api.cafe.redocly.com/.well-known/oauth-authorization-server).\n\n### Protocol behavior\n\n- **Errors follow RFC 6749 Section 5.2 and RFC 7591 Section 3.2.2.** The OAuth2 endpoints return `{\"error\": ..., \"error_description\": ...}` with the standard codes (`invalid_grant`, `invalid_client`, `unsupported_grant_type`, `invalid_scope`, `invalid_client_metadata`, ...). The rest of the API uses RFC 9457 problem+json.\n- **Client authentication.** Both `client_secret_basic` (HTTP Basic per RFC 6749 Section 2.3.1) and `client_secret_post` (credentials in the form body) are accepted at the token and revocation endpoints, but not both in one request.\n- **PKCE (RFC 7636) is supported** for the authorization code flow with the `S256` and `plain` challenge methods (`S256` recommended). When an authorization request carries a `code_challenge`, the token exchange requires the matching `code_verifier`.\n- **Refresh tokens rotate on every use.** A successful refresh retires the token presented and returns a replacement in `refresh_token`, as RFC 6749 Section 6 permits and RFC 9700 recommends. Store the new value; the old one stops working. Refresh tokens expire 30 days after they are issued, and rotation restarts that window. The authorization code flow returns a refresh token only when the client is registered for the `refresh_token` grant type; the client credentials flow returns none (RFC 6749 Section 4.4.3).\n- **Authorization responses carry `iss`** (RFC 9207) alongside `code` and `state`.\n- **Tokens can be revoked** at the [revocation endpoint](https://api.cafe.redocly.com/oauth2/revoke) (RFC 7009), and client registrations managed via RFC 7592 using the `registration_access_token`.\n- **`scope` accepts commas.** The space-delimited form required by RFC 6749 is always accepted and recommended; comma-separated values are additionally tolerated.\n",
        "oauth2MetadataUrl": "https://api.cafe.redocly.com/.well-known/oauth-authorization-server",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://api.cafe.redocly.com/oauth2/authorize",
            "tokenUrl": "https://api.cafe.redocly.com/oauth2/token",
            "refreshUrl": "https://api.cafe.redocly.com/oauth2/token",
            "scopes": {
              "menu:read": "Read access to menu items and images",
              "menu:write": "Write access to menu items (create, delete)",
              "orders:read": "Read access to orders",
              "orders:write": "Write access to orders (create, update, delete)",
              "revenue:read": "Read access to revenue statistics"
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://api.cafe.redocly.com/oauth2/token",
            "scopes": {
              "menu:read": "Read access to menu items and images",
              "menu:write": "Write access to menu items (create, delete)",
              "orders:read": "Read access to orders",
              "orders:write": "Write access to orders (create, update, delete)",
              "revenue:read": "Read access to revenue statistics"
            }
          }
        }
      },
      "ApiKey": {
        "type": "apiKey",
        "name": "X-API-Key",
        "in": "header",
        "description": "API key for internal operations."
      },
      "RegistrationAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Registration access token from the client registration response (RFC 7592), sent as a Bearer token to authenticate requests to the client configuration endpoint (`/oauth2/register/{clientId}`)."
      }
    },
    "parameters": {
      "After": {
        "name": "after",
        "in": "query",
        "required": false,
        "description": "Cursor of the next page.\n`page.nextPage` contains it as the `after` query parameter, so you can also request `page.nextPage` directly.\nThe cursor is opaque, and its internal structure is subject to change.",
        "schema": {
          "type": "string"
        }
      },
      "Before": {
        "name": "before",
        "in": "query",
        "required": false,
        "description": "Cursor of the previous page.\n`page.previousPage` contains it as the `before` query parameter, so you can also request `page.previousPage` directly.\nThe cursor is opaque, and its internal structure is subject to change.",
        "schema": {
          "type": "string"
        }
      },
      "Sort": {
        "name": "sort",
        "description": "To sort by id in descending order use `-id`.\nTo sort by id in ascending order use `id`.",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "examples": {
          "NewestFirst": {
            "summary": "Newest first",
            "dataValue": "-createdAt"
          }
        }
      },
      "Filter": {
        "name": "filter",
        "description": "Filters the collection items using space-separated `field:value` pairs.\nSee each list endpoint for the specific fields it supports.\n\n**Format:** `field1:value1 field2:value2`\n\n**Supported operators:**\n- `field:value` - exact match\n- `field:value1,value2` - match any of the comma-separated values (OR)\n- Time ranges (on date fields): use `30d` (30 days), `7d` (7 days), `1h` (1 hour), etc.",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "examples": {
          "CreatedLastWeek": {
            "summary": "Created in the last 7 days",
            "dataValue": "createdAt:7d"
          }
        }
      },
      "Search": {
        "name": "search",
        "in": "query",
        "description": "Performs a case-insensitive text search across the endpoint's searchable fields,\nreturning items where any of those fields contain the search term as a substring.\nSee each list endpoint for the specific fields it searches.",
        "required": false,
        "schema": {
          "type": "string"
        },
        "examples": {
          "SearchTerm": {
            "summary": "Search term",
            "dataValue": "Latte"
          }
        }
      },
      "Limit": {
        "name": "limit",
        "description": "Specify the number of results per page.\nIf there is more data, use in combination with `after` to page through all results.\n",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 10
        },
        "examples": {
          "DefaultPageSize": {
            "summary": "Default page size",
            "dataValue": 10
          }
        }
      },
      "MenuItemId": {
        "name": "menuItemId",
        "in": "path",
        "description": "ID of the menu item to retrieve.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^prd_[0-9abcdefghjkmnpqrstvwxyz]{26}$"
        },
        "examples": {
          "MenuItemId": {
            "summary": "Menu item ID",
            "dataValue": "prd_01khr487f7qm7p44xn427m43vb"
          }
        }
      },
      "PhotoSize": {
        "name": "photoSize",
        "in": "query",
        "description": "Photo size to retrieve.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "thumbnail",
            "medium",
            "large"
          ],
          "default": "medium"
        }
      },
      "OrderId": {
        "name": "orderId",
        "in": "path",
        "description": "ID of the order to retrieve.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^ord_[0-9abcdefghjkmnpqrstvwxyz]{26}$"
        },
        "examples": {
          "OrderId": {
            "summary": "Order ID",
            "dataValue": "ord_01h1s5z6vf2mm1mz3hevnn9va7"
          }
        }
      },
      "OrderItemsFilter": {
        "name": "filter",
        "in": "query",
        "required": true,
        "description": "Selects the order whose items to return, using a single `orderId:<value>` pair.\nOnly the `orderId` field is supported.",
        "schema": {
          "type": "string"
        },
        "examples": {
          "ByOrderId": {
            "summary": "Items of a single order",
            "dataValue": "orderId:ord_01h1s5z6vf2mm1mz3hevnn9va7"
          }
        }
      },
      "OAuth2ClientId": {
        "name": "clientId",
        "in": "path",
        "required": true,
        "description": "The client identifier issued at registration.",
        "schema": {
          "type": "string"
        },
        "examples": {
          "ClientId": {
            "summary": "Client ID",
            "dataValue": "client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d"
          }
        }
      }
    },
    "schemas": {
      "Page": {
        "type": "object",
        "properties": {
          "nextPage": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri-reference",
            "description": "Path of the next page, relative to the API server URL.\nAppend the value to the server URL to get the next page.\nThe path keeps the query parameters of the request and sets `after` to the cursor of the next page.\nWhen `null`, there is no next page.",
            "examples": [
              "/menu?limit=10&after=G0EAAIyUqm8V"
            ]
          },
          "previousPage": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri-reference",
            "description": "Path of the previous page, relative to the API server URL.\nAppend the value to the server URL to get the previous page.\nThe path keeps the query parameters of the request and sets `before` to the cursor of the previous page.\nWhen `null`, there is no previous page.",
            "examples": [
              "/menu?limit=10&before=G0EAAIyUqm8W"
            ]
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 10,
            "description": "Value showing how many items are in the page limit."
          }
        },
        "required": [
          "nextPage",
          "previousPage",
          "limit"
        ]
      },
      "MenuBaseItem": {
        "type": "object",
        "properties": {
          "createdAt": {
            "description": "Created date.",
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updatedAt": {
            "description": "Updated date.",
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "id": {
            "description": "Menu item ID. Unique identifier prefixed with `prd_`.",
            "type": "string",
            "readOnly": true,
            "pattern": "^prd_[0-9abcdefghjkmnpqrstvwxyz]{26}$",
            "example": "prd_01h1s5z6vf2mm1mz3hevnn9va7"
          },
          "object": {
            "description": "Entity name.",
            "type": "string",
            "const": "menuItem",
            "readOnly": true
          },
          "name": {
            "description": "Menu item name.",
            "type": "string",
            "minLength": 1,
            "maxLength": 50
          },
          "price": {
            "description": "Price in cents.",
            "type": "integer",
            "minimum": 0
          },
          "photo": {
            "writeOnly": true,
            "type": [
              "string",
              "null"
            ],
            "format": "binary",
            "description": "Photo of the menu item. Must be a PNG image less than 1MB in size."
          },
          "photoUrl": {
            "readOnly": true,
            "type": "string",
            "format": "uri",
            "description": "Photo URL of the menu item."
          },
          "photoTextDescription": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "price",
          "createdAt",
          "updatedAt",
          "object"
        ]
      },
      "Beverage": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "category": {
                "description": "Menu item category.",
                "type": "string",
                "const": "beverage"
              },
              "volume": {
                "type": "number",
                "description": "Size of the beverage in milliliters.",
                "exclusiveMinimum": 0
              },
              "containsCaffeine": {
                "type": "boolean",
                "description": "Indicates if the beverage contains caffeine."
              }
            },
            "required": [
              "category",
              "volume",
              "containsCaffeine"
            ]
          },
          {
            "$ref": "#/components/schemas/MenuBaseItem"
          }
        ]
      },
      "Dessert": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "category": {
                "description": "Menu item category.",
                "type": "string",
                "const": "dessert"
              },
              "calories": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "Amount of calories."
              }
            },
            "required": [
              "category",
              "calories"
            ]
          },
          {
            "$ref": "#/components/schemas/MenuBaseItem"
          }
        ]
      },
      "MenuItem": {
        "discriminator": {
          "propertyName": "category",
          "mapping": {
            "beverage": "#/components/schemas/Beverage",
            "dessert": "#/components/schemas/Dessert"
          }
        },
        "oneOf": [
          {
            "$ref": "#/components/schemas/Beverage"
          },
          {
            "$ref": "#/components/schemas/Dessert"
          }
        ],
        "required": [
          "category"
        ]
      },
      "MenuItemList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "list",
            "description": "Entity name."
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MenuItem"
            }
          }
        },
        "required": [
          "object",
          "page",
          "items"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri-reference",
            "description": "URI reference that identifies the problem type.",
            "default": "about:blank"
          },
          "title": {
            "type": "string",
            "description": "Short summary of the problem type."
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "description": "HTTP status code generated by the origin server for this occurrence of the problem.\n",
            "minimum": 100,
            "exclusiveMaximum": 600
          },
          "instance": {
            "type": "string",
            "format": "uri-reference",
            "description": "URI reference that identifies the specific occurrence of the problem, e.g. by adding a fragment identifier or sub-path to the problem type.\nCan be used to locate the root of this problem in the source code.\n",
            "example": "/some/uri-reference#specific-occurrence-context"
          },
          "details": {
            "description": "Additional error details.",
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "type",
          "title",
          "status"
        ]
      },
      "OrderStatus": {
        "type": "string",
        "description": "Order status.",
        "enum": [
          "placed",
          "preparing",
          "completed",
          "canceled"
        ]
      },
      "Order": {
        "type": "object",
        "title": "Order",
        "properties": {
          "id": {
            "description": "Order ID. Unique identifier prefixed with `ord_`.",
            "type": "string",
            "format": "ulid",
            "readOnly": true,
            "pattern": "^ord_[0-9abcdefghjkmnpqrstvwxyz]{26}$",
            "example": "ord_01h1s5z6vf2mm1mz3hevnn9va7"
          },
          "object": {
            "description": "Entity name.",
            "type": "string",
            "const": "order",
            "readOnly": true
          },
          "customerName": {
            "description": "Name of the customer who placed the order.\nMust start and end with a letter, and can contain letters, spaces, hyphens, and apostrophes (e.g., \"John Doe\", \"Mary-Jane\", \"O'Brien\").\n",
            "type": "string",
            "pattern": "^[A-Za-z]+(?:[\\s'-][A-Za-z]+)*$",
            "minLength": 1,
            "maxLength": 100
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus",
            "readOnly": true
          },
          "totalPrice": {
            "description": "Total order price in cents.",
            "type": "integer",
            "minimum": 0,
            "readOnly": true
          },
          "createdAt": {
            "description": "Created date.",
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "updatedAt": {
            "description": "Updated date.",
            "type": "string",
            "format": "date-time",
            "readOnly": true
          },
          "orderItems": {
            "type": "array",
            "description": "List of items to include in the order.",
            "minItems": 1,
            "items": {
              "type": "object",
              "properties": {
                "menuItemId": {
                  "type": "string",
                  "format": "ulid",
                  "description": "ID of the menu item to add to the order."
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "Quantity of the menu item."
                },
                "discount": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Discount amount in cents (absolute value).",
                  "default": 0
                },
                "comment": {
                  "type": "string",
                  "maxLength": 500,
                  "description": "Optional comment for the order item (e.g., \"No sugar\")."
                }
              },
              "required": [
                "menuItemId",
                "quantity"
              ]
            }
          }
        },
        "required": [
          "customerName",
          "orderItems"
        ]
      },
      "OrderList": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "list",
            "description": "Entity name."
          },
          "page": {
            "$ref": "#/components/schemas/Page"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            }
          }
        },
        "required": [
          "object",
          "page",
          "items"
        ]
      },
      "OrderItem": {
        "type": "object",
        "properties": {
          "menuItemId": {
            "type": "string",
            "description": "ID of the menu item to add to the order.",
            "writeOnly": true
          },
          "menuItem": {
            "$ref": "#/components/schemas/MenuItem",
            "readOnly": true,
            "description": "Menu item that is part of the order."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "description": "Quantity of the menu item."
          },
          "discount": {
            "type": "integer",
            "minimum": 0,
            "description": "Discount amount in cents (absolute value).",
            "default": 0
          },
          "comment": {
            "type": "string",
            "maxLength": 500,
            "description": "Optional comment for the order item (e.g., \"No sugar\")."
          }
        },
        "required": [
          "menuItemId",
          "quantity"
        ]
      },
      "RevenueStatistics": {
        "type": "object",
        "description": "Revenue statistics for a given date range.",
        "properties": {
          "revenue": {
            "type": "number",
            "format": "float",
            "description": "Total revenue in cents from completed orders.",
            "minimum": 0
          },
          "averageOrderAmount": {
            "type": "number",
            "format": "float",
            "description": "Average order amount in cents (calculated from completed orders only).",
            "minimum": 0
          },
          "totalOrders": {
            "type": "integer",
            "description": "Total number of orders (all statuses) in the date range.",
            "minimum": 0
          },
          "placedOrders": {
            "type": "integer",
            "description": "Number of placed orders.",
            "minimum": 0
          },
          "preparingOrders": {
            "type": "integer",
            "description": "Number of preparing orders.",
            "minimum": 0
          },
          "completedOrders": {
            "type": "integer",
            "description": "Number of completed orders.",
            "minimum": 0
          },
          "canceledOrders": {
            "type": "integer",
            "description": "Number of canceled orders.",
            "minimum": 0
          },
          "startDate": {
            "type": "string",
            "format": "date-time",
            "description": "Start date of the revenue calculation period."
          },
          "endDate": {
            "type": "string",
            "format": "date-time",
            "description": "End date of the revenue calculation period."
          }
        },
        "required": [
          "revenue",
          "averageOrderAmount",
          "totalOrders",
          "placedOrders",
          "preparingOrders",
          "completedOrders",
          "canceledOrders",
          "startDate",
          "endDate"
        ]
      },
      "RegisterClientMetadata": {
        "type": "object",
        "description": "Client registration metadata fields per RFC 7591 Section 2, shared by both\nregistration variants.\n",
        "properties": {
          "client_name": {
            "type": "string",
            "description": "Human-readable client name, displayed on the authorization consent screen."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated list of scopes. Defaults to all available scopes.",
            "default": "menu:read menu:write orders:read orders:write revenue:read",
            "pattern": "^(menu:read|menu:write|orders:read|orders:write|revenue:read)( (menu:read|menu:write|orders:read|orders:write|revenue:read))*$",
            "example": "menu:read orders:read orders:write"
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "client_credentials",
                "refresh_token"
              ]
            },
            "default": [
              "authorization_code",
              "client_credentials",
              "refresh_token"
            ],
            "description": "Grant types the client may use.\nA refresh token is issued with `authorization_code` grant responses only when `refresh_token` is included here.\nThe `client_credentials` grant never returns one (RFC 6749 Section 4.4.3).\n"
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "client_secret_basic",
              "client_secret_post"
            ],
            "default": "client_secret_basic",
            "description": "Requested client authentication method for the token endpoint, registered as client metadata and echoed in registration responses.\nDefaults to `client_secret_basic` (RFC 7591 Section 2).\nThe token and revocation endpoints accept both methods for every client regardless.\n"
          }
        }
      },
      "RegisterClientAuthorizationCode": {
        "title": "authorization_code",
        "description": "Effective grant types include `authorization_code` — explicitly, or by default when `grant_types` is omitted. A non-empty `redirect_uris` is required. Other grant types (`client_credentials`, `refresh_token`) can be registered alongside.",
        "allOf": [
          {
            "type": "object",
            "required": [
              "redirect_uris"
            ],
            "properties": {
              "redirect_uris": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "format": "uri"
                },
                "description": "Redirect URIs for the `authorization_code` grant.\nCompared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3).\n"
              }
            }
          },
          {
            "$ref": "#/components/schemas/RegisterClientMetadata"
          }
        ]
      },
      "RegisterClientCredentials": {
        "title": "client_credentials",
        "description": "`grant_types` is provided and does not include `authorization_code` — typically machine-to-machine clients using `client_credentials`. No redirect URI is needed.",
        "allOf": [
          {
            "$ref": "#/components/schemas/RegisterClientMetadata"
          },
          {
            "type": "object",
            "required": [
              "grant_types"
            ],
            "properties": {
              "grant_types": {
                "not": {
                  "contains": {
                    "const": "authorization_code"
                  }
                }
              }
            }
          }
        ]
      },
      "RegisterClientObject": {
        "description": "Client registration metadata per RFC 7591 Section 2, using the standard snake_case field names.\nAll fields are optional, with one conditional exception.\n`redirect_uris` is required whenever the effective grant types include `authorization_code`.\nThis requirement still applies when `grant_types` is omitted: `authorization_code` is part of the default (RFC 7591 Section 2).\n",
        "anyOf": [
          {
            "$ref": "#/components/schemas/RegisterClientAuthorizationCode"
          },
          {
            "$ref": "#/components/schemas/RegisterClientCredentials"
          }
        ]
      },
      "OAuth2Client": {
        "type": "object",
        "description": "OAuth2 client information response per RFC 7591 Section 3.2.1, using the standard snake_case field names.\nReturned by the registration endpoint and the RFC 7592 client configuration endpoint.\n",
        "properties": {
          "client_id": {
            "type": "string",
            "description": "Client identifier issued by the authorization server."
          },
          "client_secret": {
            "type": "string",
            "description": "Client secret issued by the authorization server. Store it securely."
          },
          "client_id_issued_at": {
            "type": "integer",
            "format": "int64",
            "description": "Time when the client_id is issued, represented as seconds since epoch (RFC 7591)."
          },
          "client_secret_expires_at": {
            "type": "integer",
            "format": "int64",
            "description": "Time at which the client_secret expires, represented as seconds since epoch. `0` indicates the secret does not expire (RFC 7591)."
          },
          "registration_client_uri": {
            "type": "string",
            "format": "uri",
            "description": "URL of the client configuration endpoint for managing this client registration (RFC 7592)."
          },
          "registration_access_token": {
            "type": "string",
            "description": "Bearer token for the client configuration endpoint (RFC 7592). Store it securely."
          },
          "client_name": {
            "type": "string",
            "description": "Client name (registered metadata). Omitted for clients registered without a name."
          },
          "redirect_uris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Registered redirect URIs."
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "client_credentials",
                "refresh_token"
              ]
            },
            "description": "Registered grant types."
          },
          "scope": {
            "type": "string",
            "description": "Space-separated registered scopes."
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "enum": [
              "client_secret_basic",
              "client_secret_post"
            ],
            "description": "Registered token endpoint authentication method. Defaults to `client_secret_basic` when not requested at registration (RFC 7591 Section 2).\nThe token and revocation endpoints accept both methods for every client regardless.\n"
          }
        },
        "required": [
          "client_id",
          "client_secret",
          "client_id_issued_at",
          "client_secret_expires_at",
          "registration_client_uri",
          "registration_access_token",
          "redirect_uris",
          "grant_types",
          "scope",
          "token_endpoint_auth_method"
        ]
      },
      "OAuthError": {
        "type": "object",
        "description": "OAuth2 error response, as defined by RFC 6749 Section 5.2 (token endpoint), RFC 7591 Section 3.2.2 (registration endpoint), and RFC 7009 (revocation endpoint).\nThe OAuth2 endpoints return this shape instead of the `application/problem+json` format used by the rest of the API.\n",
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code.",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unauthorized_client",
              "unsupported_grant_type",
              "unsupported_response_type",
              "invalid_scope",
              "invalid_client_metadata",
              "invalid_redirect_uri",
              "invalid_token",
              "access_denied",
              "server_error"
            ]
          },
          "error_description": {
            "type": "string",
            "description": "Human-readable explanation of the error."
          }
        },
        "required": [
          "error"
        ]
      },
      "UpdateClientObject": {
        "description": "Client registration update request per RFC 7592 Section 2.2: the full set of registration metadata, plus the required `client_id` of the client being updated.\nOmitted metadata fields are reset to their registration defaults.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/RegisterClientObject"
          },
          {
            "type": "object",
            "required": [
              "client_id"
            ],
            "properties": {
              "client_id": {
                "type": "string",
                "description": "Required (RFC 7592 Section 2.2). Must match the client being updated. Credentials cannot be changed through this endpoint."
              },
              "client_secret": {
                "type": "string",
                "description": "Optional. If present, must match the client's current secret."
              }
            }
          }
        ]
      },
      "TokenResponse": {
        "type": "object",
        "description": "Access token response per RFC 6749 Section 5.1.",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "The issued access token (opaque string)."
          },
          "token_type": {
            "type": "string",
            "description": "Type of the issued token.",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Access token lifetime in seconds.",
            "example": 3600
          },
          "refresh_token": {
            "type": "string",
            "description": "Refresh token, returned for the `authorization_code` grant when the client is registered for the `refresh_token` grant type, and on every refresh (tokens rotate on use).\nNever returned for the `client_credentials` grant (RFC 6749 Section 4.4.3).\n"
          },
          "scope": {
            "type": "string",
            "description": "Space-separated scopes granted to the token.",
            "example": "menu:read orders:read"
          }
        },
        "required": [
          "access_token",
          "token_type",
          "expires_in"
        ]
      },
      "AuthorizationServerMetadata": {
        "type": "object",
        "description": "OAuth2 authorization server metadata per RFC 8414.",
        "properties": {
          "issuer": {
            "type": "string",
            "format": "uri",
            "description": "The authorization server's issuer identifier."
          },
          "authorization_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "token_endpoint": {
            "type": "string",
            "format": "uri"
          },
          "registration_endpoint": {
            "type": "string",
            "format": "uri",
            "description": "Dynamic client registration endpoint (RFC 7591)."
          },
          "revocation_endpoint": {
            "type": "string",
            "format": "uri",
            "description": "Token revocation endpoint (RFC 7009)."
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_modes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "revocation_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "description": "PKCE code challenge methods supported (RFC 7636).",
            "items": {
              "type": "string"
            }
          },
          "authorization_response_iss_parameter_supported": {
            "type": "boolean",
            "description": "Whether authorization responses carry the `iss` parameter (RFC 9207)."
          },
          "service_documentation": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint",
          "response_types_supported"
        ]
      },
      "OrderNotification": {
        "type": "object",
        "required": [
          "orderId",
          "orderStatus",
          "timestamp"
        ],
        "properties": {
          "orderId": {
            "type": "string",
            "description": "Unique order identifier."
          },
          "orderStatus": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "When the event occurred."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad request - invalid input parameters.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Unauthorized - authorization required.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden - insufficient permissions.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict - entity already exists.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "OAuth2BadRequest": {
        "description": "OAuth2 error (RFC 6749 Section 5.2 / RFC 7591 Section 3.2.2).\nThe `error` field carries the standard OAuth2 error code.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthError"
            }
          }
        }
      },
      "OAuth2ServerError": {
        "description": "Unexpected server error, reported with the `server_error` error code.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthError"
            }
          }
        }
      },
      "OAuth2Unauthorized": {
        "description": "Client authentication failed (`error: invalid_client`, RFC 6749 Section 5.2) or the presented bearer token is invalid (`error: invalid_token`, RFC 6750).\nThe response carries a `WWW-Authenticate` challenge naming the expected authentication scheme.\n",
        "headers": {
          "WWW-Authenticate": {
            "description": "Authentication challenge, for example: `Basic realm=\"redocly-cafe\"` or `Bearer realm=\"redocly-cafe\", error=\"invalid_token\"`.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OAuthError"
            }
          }
        }
      }
    }
  }
}