{
  "openapi": "3.1.0",
  "info": {
    "title": "Eatzino Connect API",
    "version": "1.0.0",
    "summary": "Put a restaurant's menu into Eatzino and keep it in sync with its website.",
    "description": "Restaurants create an API key in the Eatzino partner dashboard (Developer API). Guide for AI coding agents: https://eatzino.com/developers/ai.md",
    "contact": { "name": "Eatzino partner team", "email": "partner@eatzino.com", "url": "https://eatzino.com/developers" }
  },
  "servers": [{ "url": "https://api.eatzino.com/api/v1/connect" }],
  "security": [{ "apiKey": [] }],
  "paths": {
    "/restaurant": {
      "get": {
        "operationId": "getRestaurant",
        "summary": "The restaurant this key belongs to",
        "responses": {
          "200": { "description": "Restaurant", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RestaurantResponse" } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/menu": {
      "get": {
        "operationId": "getMenu",
        "summary": "The current menu, in the format PUT /menu accepts",
        "responses": {
          "200": { "description": "Menu", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MenuResponse" } } } },
          "401": { "$ref": "#/components/responses/Error" }
        }
      },
      "put": {
        "operationId": "syncMenu",
        "summary": "Create or update the whole menu in one call",
        "description": "Fields left out keep their value. mode=replace also removes API-created items that are no longer sent. Nothing is saved when validation fails (422).",
        "parameters": [{ "name": "dry_run", "in": "query", "schema": { "type": "boolean" }, "description": "Validate and count only" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MenuInput" } } } },
        "responses": {
          "200": { "description": "Saved (or dry run)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SyncResult" } } } },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "413": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/dishes/{id}": {
      "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Your dish id, or ez-<uuid> from GET /menu" }],
      "patch": {
        "operationId": "patchDish",
        "summary": "Change one dish (price, availability, texts, photo …)",
        "parameters": [{ "name": "dry_run", "in": "query", "schema": { "type": "boolean" } }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/DishPatch" }] } } }
        },
        "responses": {
          "200": { "description": "Saved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SyncResult" } } } },
          "404": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" }
        }
      },
      "delete": {
        "operationId": "deleteDish",
        "summary": "Remove one dish",
        "responses": {
          "200": { "description": "Removed", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "removed": { "type": "string" } } } } } },
          "404": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/images": {
      "post": {
        "operationId": "uploadImage",
        "summary": "Upload a photo and get a URL to use as a dish image",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "string", "format": "binary" } }, "required": ["file"] } },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "data": { "type": "string", "description": "Base64 image (optionally a data: URL)" },
                  "url": { "type": "string", "format": "uri", "description": "Public image URL to copy" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Stored", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "url": { "type": "string", "format": "uri" } } } } } },
          "422": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/public/restaurants/{restaurantId}/menu": {
      "get": {
        "operationId": "getPublicMenu",
        "summary": "No key: the live menu guests can order (CORS open, cached 60 s)",
        "security": [],
        "parameters": [{ "name": "restaurantId", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": {
          "200": { "description": "Menu", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MenuResponse" } } } },
          "404": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "http", "scheme": "bearer", "description": "Restaurant API key (ezk_…) from partner.eatzino.com → Developer API. Server-side only." }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Text": {
        "description": "A text in the request language, or per language",
        "oneOf": [
          { "type": "string" },
          { "type": "object", "properties": { "en": { "type": "string" }, "de": { "type": "string" } }, "additionalProperties": false }
        ]
      },
      "Id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,99}$", "description": "Your own stable id" },
      "Allergen": { "type": "string", "enum": ["gluten", "crustaceans", "eggs", "fish", "peanuts", "soy", "milk", "nuts", "celery", "mustard", "sesame", "sulphites", "lupin", "molluscs"] },
      "Additive": { "type": "string", "enum": ["colouring", "preservative", "antioxidant", "flavour_enhancer", "sulphured", "blackened", "waxed", "phosphate", "sweetener", "phenylalanine", "caffeine", "quinine", "taurine"] },
      "Label": { "type": "string", "enum": ["vegetarian", "vegan", "halal", "spicy", "very_spicy", "gluten_free", "lactose_free"] },
      "Option": {
        "type": "object",
        "description": "A required single choice such as size. Price per combination = dish price + chosen extras.",
        "required": ["name", "choices"],
        "properties": {
          "id": { "$ref": "#/components/schemas/Id" },
          "name": { "$ref": "#/components/schemas/Text" },
          "choices": {
            "type": "array", "minItems": 1, "maxItems": 30,
            "items": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string", "maxLength": 80 }, "price": { "type": "number", "minimum": 0, "description": "Extra on top of the dish price" } } }
          }
        }
      },
      "DishPatch": {
        "type": "object",
        "properties": {
          "name": { "$ref": "#/components/schemas/Text" },
          "description": { "$ref": "#/components/schemas/Text" },
          "price": { "type": "number", "exclusiveMinimum": 0, "maximum": 100000, "description": "In the restaurant's currency, e.g. 9.5. With options: cheapest combination" },
          "image": { "type": ["string", "null"], "format": "uri" },
          "available": { "type": "boolean" },
          "vegetarian": { "type": "boolean" },
          "labels": { "type": "array", "items": { "$ref": "#/components/schemas/Label" } },
          "allergens": { "type": ["array", "null"], "items": { "$ref": "#/components/schemas/Allergen" }, "description": "[] = contains none, null = not declared" },
          "additives": { "type": ["array", "null"], "items": { "$ref": "#/components/schemas/Additive" } },
          "options": { "type": "array", "maxItems": 4, "items": { "$ref": "#/components/schemas/Option" } },
          "addons": { "type": "array", "items": { "type": "string" }, "description": "Add-on ids" },
          "category": { "type": "string", "description": "PATCH only: move to this category id" }
        }
      },
      "Dish": {
        "allOf": [
          { "$ref": "#/components/schemas/DishPatch" },
          { "type": "object", "required": ["name", "price"], "properties": { "id": { "$ref": "#/components/schemas/Id" } } }
        ]
      },
      "Category": {
        "type": "object",
        "required": ["name", "dishes"],
        "properties": {
          "id": { "$ref": "#/components/schemas/Id" },
          "name": { "$ref": "#/components/schemas/Text" },
          "description": { "$ref": "#/components/schemas/Text" },
          "image": { "type": ["string", "null"], "format": "uri" },
          "available": { "type": "boolean" },
          "dishes": { "type": "array", "items": { "$ref": "#/components/schemas/Dish" } }
        }
      },
      "Addon": {
        "type": "object",
        "required": ["name", "price"],
        "properties": {
          "id": { "$ref": "#/components/schemas/Id" },
          "name": { "$ref": "#/components/schemas/Text" },
          "price": { "type": "number", "minimum": 0 },
          "available": { "type": "boolean" }
        }
      },
      "MenuInput": {
        "type": "object",
        "required": ["categories"],
        "properties": {
          "language": { "type": "string", "enum": ["en", "de"], "default": "en" },
          "mode": { "type": "string", "enum": ["merge", "replace"], "default": "merge" },
          "currency": { "type": "string", "description": "Must equal the restaurant's currency if sent" },
          "dry_run": { "type": "boolean" },
          "categories": { "type": "array", "maxItems": 80, "items": { "$ref": "#/components/schemas/Category" } },
          "addons": { "type": "array", "maxItems": 300, "items": { "$ref": "#/components/schemas/Addon" } }
        }
      },
      "Counts": {
        "type": "object",
        "properties": { "created": { "type": "integer" }, "updated": { "type": "integer" }, "unchanged": { "type": "integer" }, "removed": { "type": "integer" } }
      },
      "SyncResult": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "dry_run": { "type": "boolean" },
          "mode": { "type": "string" },
          "summary": {
            "type": "object",
            "properties": {
              "categories": { "$ref": "#/components/schemas/Counts" },
              "dishes": { "$ref": "#/components/schemas/Counts" },
              "addons": { "$ref": "#/components/schemas/Counts" },
              "images": { "type": "object", "properties": { "downloaded": { "type": "integer" }, "failed": { "type": "integer" }, "not_checked": { "type": "integer" } } }
            }
          },
          "warnings": { "type": "array", "items": { "$ref": "#/components/schemas/Problem" } },
          "menu_url": { "type": "string", "format": "uri" }
        }
      },
      "MenuResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "currency": { "type": "string" },
          "categories": {
            "type": "array",
            "items": {
              "allOf": [
                { "$ref": "#/components/schemas/Category" },
                { "type": "object", "properties": { "dishes": { "type": "array", "items": { "allOf": [{ "$ref": "#/components/schemas/Dish" }, { "type": "object", "properties": { "eatzino_id": { "type": "string", "description": "Use with data-eatzino-product on an order button" } } }] } } } }
              ]
            }
          },
          "addons": { "type": "array", "items": { "$ref": "#/components/schemas/Addon" } }
        }
      },
      "RestaurantResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "restaurant": {
            "type": "object",
            "properties": {
              "id": { "type": "integer" },
              "name": { "type": "object" },
              "currency": { "type": "string" },
              "timezone": { "type": "string" },
              "status": { "type": "string" },
              "visible_to_guests": { "type": "boolean" },
              "page_url": { "type": "object" },
              "ordering": { "type": "object", "description": "Ready-to-paste HTML snippets for online ordering" },
              "menu": { "type": "object" }
            }
          },
          "key": { "type": "object", "properties": { "name": { "type": "string" }, "can_write": { "type": "boolean" } } }
        }
      },
      "Problem": { "type": "object", "properties": { "path": { "type": "string" }, "message": { "type": "string" } } },
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "const": false },
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "enum": ["missing_key", "invalid_key", "read_only_key", "rate_limited", "invalid_json", "invalid_menu", "too_large", "not_found", "invalid_image", "server_error"] },
              "message": { "type": "string" },
              "details": { "type": "array", "items": { "$ref": "#/components/schemas/Problem" } }
            }
          }
        }
      }
    }
  }
}
