{
  "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.0.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`",
            "example": "-price"
          },
          {
            "$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.",
            "example": "category:beverage"
          },
          {
            "$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`",
            "example": "Latte"
          },
          {
            "$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.",
            "example": "status:placed"
          },
          {
            "$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`",
            "example": "-createdAt"
          },
          {
            "$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`",
            "example": "John"
          }
        ],
        "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/Filter",
            "required": true,
            "description": "Selects the order whose items to return, using a single `orderId:<value>` pair\n(for example, `orderId:ord_01h1s5z6vf2mm1mz3hevnn9va7`).\n\nThis filter is required and only the `orderId` field is supported.",
            "example": "orderId:ord_01h1s5z6vf2mm1mz3hevnn9va7"
          }
        ],
        "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"
            },
            "example": "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"
            },
            "example": "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 for dynamic client registration.\nThis endpoint implements the Dynamic Client Registration Protocol (RFC 7591), using camelCase field names instead of the RFC's snake_case convention (e.g., `redirectUris` instead of `redirect_uris`, `grantTypes` instead of `grant_types`).\nThe `name` field is required. Other fields are optional. If not provided:\n\n- `redirectUris` defaults to an empty array. Note: When using the `authorization_code` grant type, `redirectUris` must be provided (per RFC 7591 Section 2).\n- `scopes` defaults to all available scopes (menu:read, menu:write, orders:read, orders:write, revenue:read)\n- `grantTypes` defaults to `authorization_code` and `client_credentials`\n\nThese defaults interact: a request that supplies only `name` pairs `authorization_code` with an empty `redirectUris`, which is not a usable combination.\nSupply `redirectUris` explicitly to register the `authorization_code` grant, or set `grantTypes` to `client_credentials` alone for a client that needs no redirect URI.\n\nRefresh tokens require no registration and `refresh_token` is not a value you can register in `grantTypes`.\nThe token endpoint returns a refresh token alongside every access token it issues for the `authorization_code` grant, and accepts `grant_type=refresh_token` from any client presenting a refresh token issued to it.\nThe `client_credentials` grant returns no refresh token (RFC 6749 Section 4.4.3); those clients request a new access token with their own credentials instead.\n\nReturns the registered client information per RFC 7591, including:\n\n- `clientId` and `clientSecret` (must be stored securely)\n- `clientIdIssuedAt` and `clientSecretExpiresAt` timestamps\n- All registered client metadata (name, redirectUris, scopes, grantTypes)\n",
        "operationId": "registerOAuth2Client",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterClientObject"
              },
              "examples": {
                "RegisterClientObject": {
                  "dataValue": {
                    "name": "auth",
                    "scopes": [
                      "menu:read",
                      "menu:write",
                      "orders:read",
                      "orders:write",
                      "revenue:read"
                    ],
                    "grantTypes": [
                      "client_credentials"
                    ]
                  }
                },
                "RegisterClientForAuthorizationCode": {
                  "dataValue": {
                    "name": "pos-terminal",
                    "redirectUris": [
                      "https://api.cafe.redocly.com/callback"
                    ],
                    "scopes": [
                      "menu:read",
                      "orders:read",
                      "orders:write"
                    ],
                    "grantTypes": [
                      "authorization_code"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "OAuth2 client registered successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuth2Client"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    }
  },
  "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`.\n\n### Differences from the OAuth2 specifications\n\nA standard OAuth2 client library can drive these flows, with the following to account for.\n\nTwo behaviors do not conform to the specifications:\n\n- **Errors use RFC 9457 problem+json, not RFC 6749 Section 5.2.** Failures return `application/problem+json` with `type`, `title`, `status`, and `instance`. There is no `error` or `error_description` field, so the standard codes (`invalid_grant`, `invalid_client`, `unsupported_grant_type`) never appear — branch on the HTTP status and `title` instead. A refresh token that is expired, already rotated, or unrecognized returns `400` with a `title` of `Refresh token has expired` or `Invalid refresh token`, where a conformant server would return `error: invalid_grant`.\n- **`refresh_token` is not a registrable grant type.** RFC 7591 Section 2 lists it, but `/oauth2/register` accepts only `authorization_code` and `client_credentials` in `grantTypes`. Refreshing requires no registration: holding a refresh token issued to the client is the authorization. A consequence is that refresh capability cannot be disabled per client — every `authorization_code` grant returns a refresh token, so a client intended for a shared or public device cannot be registered without one.\n\nTwo are choices the specifications leave to the server:\n\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 the OAuth2 Security Best Current Practice 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 with every access token; the client credentials flow returns none (RFC 6749 Section 4.4.3).\n- **`scope` accepts commas.** The space-delimited form required by RFC 6749 is always accepted and recommended; comma-separated values are additionally tolerated.\n",
        "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."
      }
    },
    "parameters": {
      "After": {
        "name": "after",
        "in": "query",
        "required": false,
        "description": "Use the `endCursor` as a value for the `after` parameter to get the next page.",
        "schema": {
          "type": "string"
        }
      },
      "Before": {
        "name": "before",
        "in": "query",
        "required": false,
        "description": "Use the `startCursor` as a value for the `before` parameter to get the previous page.\n",
        "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"
        }
      },
      "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"
        }
      },
      "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"
        }
      },
      "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
        },
        "example": 10
      },
      "MenuItemId": {
        "name": "menuItemId",
        "in": "path",
        "description": "ID of the menu item to retrieve.",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^prd_[0-9abcdefghjkmnpqrstvwxyz]{26}$"
        }
      },
      "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}$"
        },
        "example": "ord_01h1s5z6vf2mm1mz3hevnn9va7"
      }
    },
    "schemas": {
      "Page": {
        "type": "object",
        "properties": {
          "endCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Use with the `after` query parameter to load the next page of data.\nWhen `null`, there is no data.\nThe cursor is opaque and internal structure is subject to change."
          },
          "startCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Use with the `before` query parameter to load the previous page of data.\nWhen `null`, there is no data.\nThe cursor is opaque and internal structure is subject to change."
          },
          "hasNextPage": {
            "type": "boolean",
            "description": "Indicates if there is a next page with items."
          },
          "hasPrevPage": {
            "type": "boolean",
            "description": "Indicates if there is a previous page with items."
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 10,
            "description": "Value showing how many items are in the page limit."
          },
          "total": {
            "type": "integer",
            "description": "Count of items across all pages.",
            "minimum": 0
          }
        },
        "required": [
          "endCursor",
          "startCursor",
          "hasNextPage",
          "hasPrevPage",
          "limit",
          "total"
        ]
      },
      "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": {
            "allOf": [
              {
                "$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": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MenuItem"
              }
            ],
            "description": "Menu item that is part of the order.",
            "readOnly": true
          },
          "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"
        ]
      },
      "RegisterClientObject": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Client name."
          },
          "redirectUris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "default": [],
            "description": "List of redirect URIs (optional, defaults to empty array)."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "menu:read",
                "menu:write",
                "orders:read",
                "orders:write",
                "revenue:read"
              ]
            },
            "default": [
              "menu:read",
              "menu:write",
              "orders:read",
              "orders:write",
              "revenue:read"
            ],
            "description": "List of scopes."
          },
          "grantTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "client_credentials"
              ]
            },
            "default": [
              "authorization_code",
              "client_credentials"
            ],
            "description": "List of grant types. `refresh_token` is not registrable; any client holding a refresh token may present it at the token endpoint."
          }
        },
        "required": [
          "name"
        ]
      },
      "OAuth2Client": {
        "type": "object",
        "description": "OAuth2 client registration response. Per RFC 7591, includes the client identifier, secret, timestamps, and all registered client metadata.",
        "properties": {
          "clientId": {
            "type": "string",
            "description": "Client identifier issued by the authorization server."
          },
          "clientSecret": {
            "type": "string",
            "description": "Client secret issued by the authorization server."
          },
          "clientIdIssuedAt": {
            "type": "integer",
            "format": "int64",
            "description": "Time when the client_id is issued, represented as seconds since epoch (RFC7591)."
          },
          "clientSecretExpiresAt": {
            "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)."
          },
          "name": {
            "type": "string",
            "description": "Client name (registered metadata)."
          },
          "redirectUris": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "List of redirect URIs (registered metadata)."
          },
          "registrationClientUri": {
            "type": "string",
            "format": "uri",
            "description": "URL of the client configuration endpoint for managing this client registration (RFC 7592)."
          },
          "registrationAccessToken": {
            "type": "string",
            "description": "Access token to be used at the client configuration endpoint for managing this client registration (RFC 7592)."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "menu:read",
                "menu:write",
                "orders:read",
                "orders:write",
                "revenue:read"
              ]
            },
            "description": "List of scopes (registered metadata)."
          },
          "grantTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "authorization_code",
                "client_credentials"
              ]
            },
            "description": "List of grant types (registered metadata)."
          }
        },
        "required": [
          "clientId",
          "clientSecret",
          "clientIdIssuedAt",
          "clientSecretExpiresAt",
          "registrationClientUri",
          "registrationAccessToken"
        ]
      },
      "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"
            }
          }
        }
      }
    }
  }
}