{
  "openapi": "3.1.0",
  "info": {
    "title": "Tastavo Developer API",
    "version": "1.0.0",
    "description": "Ready-made dishes from European restaurant menus with prices, declared allergens, diet tags, estimated nutrition, opening hours and deep links. Facts come from venues; values marked as estimates are ours. Attribution (\"Data: Tastavo\" with the deep link) is required wherever results are shown. Responses may be cached for 24 hours at most."
  },
  "servers": [
    {
      "url": "https://api.tastavo.com"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "paths": {
    "/v1/dishes/search": {
      "post": {
        "operationId": "searchDishes",
        "summary": "Search dishes by diet, allergens, calories, budget and location",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchDishesInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Matching dishes with their venues",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls allowed per minute on the plan"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the minute window frees a slot"
              },
              "X-Quota-Month-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Month-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchDishesResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (WWW-Authenticate carries the OAuth resource metadata URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota used up. `upgrade_url` in the body; `PAYMENT-REQUIRED` header holds x402 v2 requirements (USDC on Base) for paying per call",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "base64 JSON x402 requirements"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Venue or dish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-minute limit reached; retry after `Retry-After` seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "searchDishesGet",
        "summary": "Same search with query parameters",
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Latitude of the search centre (with lng); alternatively city"
          },
          {
            "name": "lng",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Longitude"
          },
          {
            "name": "radius_m",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Radius in metres, 100–50000 (plan caps it)"
          },
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "City slug as in tastavo.com URLs"
          },
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Free text in any site language"
          },
          {
            "name": "exclude_allergens",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated EU allergen letters, e.g. A,G"
          },
          {
            "name": "diet",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated: vegetarian, vegan, gluten_free, lactose_free, halal_friendly, no_pork, no_alcohol"
          },
          {
            "name": "kcal_min",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Minimum estimated kcal per portion"
          },
          {
            "name": "kcal_max",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Maximum estimated kcal per portion"
          },
          {
            "name": "price_max_minor",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Maximum price in minor units of the venue currency"
          },
          {
            "name": "currency",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO 4217, with price_max_minor"
          },
          {
            "name": "open_at",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "ISO 8601 time or `now`"
          },
          {
            "name": "strict_allergens",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Also exclude dishes with undeclared allergens"
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Response language"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "1–100 (plan caps it)"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "From next_cursor of the previous page"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching dishes with their venues",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls allowed per minute on the plan"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the minute window frees a slot"
              },
              "X-Quota-Month-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Month-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchDishesResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (WWW-Authenticate carries the OAuth resource metadata URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota used up. `upgrade_url` in the body; `PAYMENT-REQUIRED` header holds x402 v2 requirements (USDC on Base) for paying per call",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "base64 JSON x402 requirements"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Venue or dish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-minute limit reached; retry after `Retry-After` seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/venues/nearby": {
      "get": {
        "operationId": "nearbyVenues",
        "summary": "Venues with a published menu around a point",
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "lng",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "radius_m",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "open_at",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cuisine",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated cuisines"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Nearest venues first",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls allowed per minute on the plan"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the minute window frees a slot"
              },
              "X-Quota-Month-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Month-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NearbyVenuesResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (WWW-Authenticate carries the OAuth resource metadata URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota used up. `upgrade_url` in the body; `PAYMENT-REQUIRED` header holds x402 v2 requirements (USDC on Base) for paying per call",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "base64 JSON x402 requirements"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Venue or dish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-minute limit reached; retry after `Retry-After` seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/venues/{venue_id}": {
      "get": {
        "operationId": "getVenue",
        "summary": "Venue facts",
        "parameters": [
          {
            "name": "venue_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Response language (en, de, fr, it, es, pl, cs, hu, ru, uk, tr, bs, ro, zh-Hans)"
          }
        ],
        "responses": {
          "200": {
            "description": "Venue facts",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls allowed per minute on the plan"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the minute window frees a slot"
              },
              "X-Quota-Month-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Month-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Venue"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (WWW-Authenticate carries the OAuth resource metadata URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota used up. `upgrade_url` in the body; `PAYMENT-REQUIRED` header holds x402 v2 requirements (USDC on Base) for paying per call",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "base64 JSON x402 requirements"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Venue or dish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-minute limit reached; retry after `Retry-After` seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/venues/{venue_id}/menu": {
      "get": {
        "operationId": "getMenu",
        "summary": "The whole published menu of a venue",
        "parameters": [
          {
            "name": "venue_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Response language (en, de, fr, it, es, pl, cs, hu, ru, uk, tr, bs, ro, zh-Hans)"
          }
        ],
        "responses": {
          "200": {
            "description": "Sections and dishes",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls allowed per minute on the plan"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the minute window frees a slot"
              },
              "X-Quota-Month-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Month-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (WWW-Authenticate carries the OAuth resource metadata URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota used up. `upgrade_url` in the body; `PAYMENT-REQUIRED` header holds x402 v2 requirements (USDC on Base) for paying per call",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "base64 JSON x402 requirements"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Venue or dish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-minute limit reached; retry after `Retry-After` seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/dishes/{venue_id}/{dish_id}": {
      "get": {
        "operationId": "getDish",
        "summary": "Dish card with nutrition and allergens",
        "parameters": [
          {
            "name": "venue_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dish_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Response language (en, de, fr, it, es, pl, cs, hu, ru, uk, tr, bs, ro, zh-Hans)"
          }
        ],
        "responses": {
          "200": {
            "description": "Dish card",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls allowed per minute on the plan"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the minute window frees a slot"
              },
              "X-Quota-Month-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Quota-Month-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DishDetail"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token (WWW-Authenticate carries the OAuth resource metadata URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Monthly quota used up. `upgrade_url` in the body; `PAYMENT-REQUIRED` header holds x402 v2 requirements (USDC on Base) for paying per call",
            "headers": {
              "PAYMENT-REQUIRED": {
                "schema": {
                  "type": "string"
                },
                "description": "base64 JSON x402 requirements"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token lacks the required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Venue or dish not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-minute limit reached; retry after `Retry-After` seconds",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token from the Tastavo account (tsv_live_…). Never put it in the query string."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          },
          "details": {},
          "upgrade_url": {
            "type": "string"
          },
          "retry_after_s": {
            "type": "integer"
          }
        },
        "required": [
          "error",
          "message",
          "request_id"
        ]
      },
      "Attribution": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "text",
          "url"
        ]
      },
      "SearchDishesInput": {
        "type": "object",
        "properties": {
          "near": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number",
                "minimum": -90,
                "maximum": 90
              },
              "lng": {
                "type": "number",
                "minimum": -180,
                "maximum": 180
              },
              "radius_m": {
                "default": 1500,
                "description": "Search radius in metres; the plan caps it",
                "type": "integer",
                "minimum": 100,
                "maximum": 50000
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "description": "Centre of the search; alternatively pass `city`"
          },
          "city": {
            "description": "City slug as in tastavo.com URLs, e.g. `wien`",
            "type": "string",
            "pattern": "^[a-z0-9-]{2,40}$"
          },
          "query": {
            "description": "Free text: dish or venue name in any of the site languages",
            "type": "string",
            "minLength": 2,
            "maxLength": 80
          },
          "exclude_allergens": {
            "default": [],
            "description": "EU allergen letters (A gluten … R molluscs) the dish must not contain",
            "maxItems": 14,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "A",
                "B",
                "C",
                "D",
                "E",
                "F",
                "G",
                "H",
                "L",
                "M",
                "N",
                "O",
                "P",
                "R"
              ]
            }
          },
          "diet": {
            "default": [],
            "maxItems": 7,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "vegetarian",
                "vegan",
                "gluten_free",
                "lactose_free",
                "halal_friendly",
                "no_pork",
                "no_alcohol"
              ]
            }
          },
          "kcal": {
            "description": "Estimated kcal per portion (ranges)",
            "type": "object",
            "properties": {
              "min": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "max": {
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 5000
              }
            }
          },
          "price": {
            "description": "Maximum price in minor units of the venue currency",
            "type": "object",
            "properties": {
              "max_minor": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              "currency": {
                "type": "string",
                "pattern": "^[A-Z]{3}$"
              }
            },
            "required": [
              "max_minor"
            ]
          },
          "open_at": {
            "description": "ISO 8601 time or `now`: only venues open then",
            "type": "string",
            "maxLength": 32
          },
          "strict_allergens": {
            "default": false,
            "description": "true: also exclude dishes whose allergens the venue did not declare",
            "type": "boolean"
          },
          "lang": {
            "default": "en",
            "type": "string",
            "enum": [
              "en",
              "de",
              "fr",
              "it",
              "es",
              "pl",
              "cs",
              "hu",
              "ru",
              "uk",
              "tr",
              "bs",
              "ro",
              "zh-Hans"
            ]
          },
          "limit": {
            "default": 20,
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "cursor": {
            "type": "string",
            "maxLength": 64
          }
        }
      },
      "GetDishInput": {
        "type": "object",
        "properties": {
          "venue_id": {
            "type": "string",
            "pattern": "^[a-z0-9-]{3,80}$"
          },
          "dish_id": {
            "type": "string",
            "pattern": "^d\\d{1,4}$"
          },
          "lang": {
            "default": "en",
            "type": "string",
            "enum": [
              "en",
              "de",
              "fr",
              "it",
              "es",
              "pl",
              "cs",
              "hu",
              "ru",
              "uk",
              "tr",
              "bs",
              "ro",
              "zh-Hans"
            ]
          }
        },
        "required": [
          "venue_id",
          "dish_id"
        ]
      },
      "GetVenueInput": {
        "type": "object",
        "properties": {
          "venue_id": {
            "type": "string",
            "pattern": "^[a-z0-9-]{3,80}$"
          },
          "lang": {
            "default": "en",
            "type": "string",
            "enum": [
              "en",
              "de",
              "fr",
              "it",
              "es",
              "pl",
              "cs",
              "hu",
              "ru",
              "uk",
              "tr",
              "bs",
              "ro",
              "zh-Hans"
            ]
          }
        },
        "required": [
          "venue_id"
        ]
      },
      "GetMenuInput": {
        "type": "object",
        "properties": {
          "venue_id": {
            "type": "string",
            "pattern": "^[a-z0-9-]{3,80}$"
          },
          "lang": {
            "default": "en",
            "type": "string",
            "enum": [
              "en",
              "de",
              "fr",
              "it",
              "es",
              "pl",
              "cs",
              "hu",
              "ru",
              "uk",
              "tr",
              "bs",
              "ro",
              "zh-Hans"
            ]
          }
        },
        "required": [
          "venue_id"
        ]
      },
      "NearbyVenuesInput": {
        "type": "object",
        "properties": {
          "near": {
            "type": "object",
            "properties": {
              "lat": {
                "type": "number",
                "minimum": -90,
                "maximum": 90
              },
              "lng": {
                "type": "number",
                "minimum": -180,
                "maximum": 180
              },
              "radius_m": {
                "default": 1500,
                "description": "Search radius in metres; the plan caps it",
                "type": "integer",
                "minimum": 100,
                "maximum": 50000
              }
            },
            "required": [
              "lat",
              "lng"
            ],
            "description": "Centre of the search; alternatively pass `city`"
          },
          "open_at": {
            "type": "string",
            "maxLength": 32
          },
          "cuisine": {
            "default": [],
            "maxItems": 10,
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 32
            }
          },
          "limit": {
            "default": 10,
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          }
        },
        "required": [
          "near"
        ]
      },
      "Dish": {
        "type": "object",
        "description": "A dish with facts (names, price, declared allergens) and, on extended plans, estimates (likely allergens, kcal range).",
        "properties": {
          "dish_id": {
            "type": "string"
          },
          "venue_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "name_orig": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "section": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "variants": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "price_minor": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          },
          "allergens_printed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "EU allergen names printed on the menu or declared by the venue (facts)"
          },
          "allergen_status": {
            "type": "string",
            "enum": [
              "declared",
              "printed",
              "not_declared"
            ]
          },
          "allergens_likely": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Estimate from the usual recipe (extended plans)"
          },
          "diet": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "kcal_range": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "min": {
                "type": "integer"
              },
              "max": {
                "type": "integer"
              }
            },
            "description": "Estimate per portion (extended plans)"
          },
          "nutrition_confidence": {
            "type": "string",
            "enum": [
              "estimate",
              "declared"
            ]
          },
          "distance_m": {
            "type": "integer"
          },
          "open_now": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "deep_link": {
            "type": "string",
            "format": "uri"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_type": {
            "type": "string",
            "enum": [
              "photo",
              "illustration"
            ]
          }
        },
        "required": [
          "dish_id",
          "venue_id",
          "name",
          "name_orig",
          "price_minor",
          "allergens_printed",
          "allergen_status",
          "diet",
          "deep_link"
        ]
      },
      "VenueSummary": {
        "type": "object",
        "properties": {
          "venue_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "cuisine": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_level": {
            "type": [
              "integer",
              "null"
            ]
          },
          "open_now": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "claimed": {
            "type": "boolean"
          },
          "deep_link": {
            "type": "string"
          }
        },
        "required": [
          "venue_id",
          "name",
          "city",
          "country",
          "deep_link"
        ]
      },
      "Venue": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "venue_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "country": {
                "type": "string"
              },
              "lat": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "lng": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "cuisine": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "price_level": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "open_now": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "claimed": {
                "type": "boolean"
              },
              "deep_link": {
                "type": "string"
              }
            },
            "required": [
              "venue_id",
              "name",
              "city",
              "country",
              "deep_link"
            ]
          },
          {
            "type": "object",
            "properties": {
              "website": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "booking_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "hours": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "{tz, week: {mo..su: [[open, close]]}}"
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "dish_count": {
                "type": "integer"
              },
              "price_min": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "price_max": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "menu_source": {
                "type": "string",
                "enum": [
                  "site",
                  "scan",
                  "owner"
                ]
              },
              "fetched_at": {
                "type": "string",
                "format": "date-time"
              },
              "langs": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "distance_m": {
                "type": "integer"
              }
            }
          }
        ]
      },
      "SearchDishesResult": {
        "type": "object",
        "properties": {
          "dishes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Dish"
            }
          },
          "venues": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/VenueSummary"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_candidates": {
            "type": "integer"
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          },
          "disclaimer": {
            "type": "string"
          },
          "suggestions": {
            "type": "object",
            "properties": {
              "radius_m": {
                "type": "integer"
              }
            }
          }
        },
        "required": [
          "dishes",
          "venues",
          "next_cursor",
          "attribution",
          "disclaimer"
        ],
        "example": {
          "dishes": [
            {
              "dish_id": "d0",
              "venue_id": "wien-1",
              "name": "Tafelspitz",
              "name_orig": "Tafelspitz",
              "price_minor": 2490,
              "currency": "EUR",
              "allergens_printed": [
                "celery"
              ],
              "allergen_status": "printed",
              "diet": [],
              "deep_link": "https://tastavo.com/wien/cafe-sperl/d0"
            }
          ],
          "venues": {},
          "next_cursor": null,
          "total_candidates": 1,
          "attribution": {
            "text": "Data: Tastavo",
            "url": "https://tastavo.com/wien/cafe-sperl"
          },
          "disclaimer": "…"
        }
      },
      "MenuResult": {
        "type": "object",
        "properties": {
          "venue_id": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "lang": {
            "type": "string"
          },
          "sections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "dishes": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Dish"
                  }
                }
              }
            }
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          },
          "disclaimer": {
            "type": "string"
          }
        },
        "required": [
          "venue_id",
          "version",
          "sections",
          "attribution"
        ]
      },
      "DishDetail": {
        "allOf": [
          {
            "type": "object",
            "description": "A dish with facts (names, price, declared allergens) and, on extended plans, estimates (likely allergens, kcal range).",
            "properties": {
              "dish_id": {
                "type": "string"
              },
              "venue_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "name_orig": {
                "type": "string"
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "section": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "price_minor": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "variants": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "price_minor": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                }
              },
              "allergens_printed": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "EU allergen names printed on the menu or declared by the venue (facts)"
              },
              "allergen_status": {
                "type": "string",
                "enum": [
                  "declared",
                  "printed",
                  "not_declared"
                ]
              },
              "allergens_likely": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Estimate from the usual recipe (extended plans)"
              },
              "diet": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "kcal_range": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "min": {
                    "type": "integer"
                  },
                  "max": {
                    "type": "integer"
                  }
                },
                "description": "Estimate per portion (extended plans)"
              },
              "nutrition_confidence": {
                "type": "string",
                "enum": [
                  "estimate",
                  "declared"
                ]
              },
              "distance_m": {
                "type": "integer"
              },
              "open_now": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "deep_link": {
                "type": "string",
                "format": "uri"
              },
              "image_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "image_type": {
                "type": "string",
                "enum": [
                  "photo",
                  "illustration"
                ]
              }
            },
            "required": [
              "dish_id",
              "venue_id",
              "name",
              "name_orig",
              "price_minor",
              "allergens_printed",
              "allergen_status",
              "diet",
              "deep_link"
            ]
          },
          {
            "type": "object",
            "properties": {
              "about": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ingredients": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "history": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "nutrition": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "portion_g": {
                    "type": "number"
                  },
                  "kcal": {
                    "type": "number"
                  },
                  "protein_g": {
                    "type": "number"
                  },
                  "fat_g": {
                    "type": "number"
                  },
                  "carbs_g": {
                    "type": "number"
                  }
                },
                "description": "Estimate per portion (extended plans)"
              },
              "venue": {
                "$ref": "#/components/schemas/VenueSummary"
              },
              "attribution": {
                "$ref": "#/components/schemas/Attribution"
              },
              "disclaimer": {
                "type": "string"
              }
            }
          }
        ]
      },
      "NearbyVenuesResult": {
        "type": "object",
        "properties": {
          "venues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Venue"
            }
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          }
        },
        "required": [
          "venues",
          "attribution"
        ]
      }
    }
  }
}