{
  "openapi": "3.1.0",
  "info": {
    "title": "Potravinka.cz Public API",
    "version": "1.0.0",
    "description": "Read-only JSON API for AI agents to search and browse Czech grocery prices across 11+ stores (Lidl, Kaufland, Albert, Billa, Penny, Tesco, Globus, Makro, Rohlík.cz, Košík.cz, COOP). All prices in CZK including VAT. See /agents.md for usage guide.",
    "contact": {
      "name": "David Vávra",
      "url": "https://potravinka.cz/support"
    },
    "license": {
      "name": "Public read-only data — attribution requested"
    }
  },
  "servers": [
    { "url": "https://potravinka.cz", "description": "Production" }
  ],
  "tags": [
    { "name": "search", "description": "Product search" },
    { "name": "browse", "description": "Browse by category or store" },
    { "name": "product", "description": "Single product detail and related data" },
    { "name": "comparison", "description": "Cross-store price comparisons" }
  ],
  "paths": {
    "/api/search/smart": {
      "get": {
        "tags": ["search"],
        "summary": "Czech accent-insensitive keyword search (recommended)",
        "description": "Best general-purpose search. Accent-insensitive (`maslo` matches `máslo`).",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string", "minLength": 2 }, "description": "Search term, min 2 chars" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } },
          { "name": "store", "in": "query", "schema": { "$ref": "#/components/schemas/StoreName" }, "description": "Restrict to one store" }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchResponse" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/search": {
      "get": {
        "tags": ["search"],
        "summary": "Basic keyword search",
        "description": "Simpler variant of /api/search/smart. Prefer /api/search/smart for Czech queries.",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "schema": { "type": "string", "minLength": 2 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10, "maximum": 100 } },
          { "name": "instant", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Skip total-count query for faster response (autocomplete mode)" }
        ],
        "responses": {
          "200": { "description": "Search results", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchResponse" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/category/{slug}": {
      "get": {
        "tags": ["browse"],
        "summary": "Products in a category (semantically ranked)",
        "parameters": [
          { "name": "slug", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/CategorySlug" } }
        ],
        "responses": {
          "200": {
            "description": "Category products",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CategoryResponse" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/store/{storeName}": {
      "get": {
        "tags": ["browse"],
        "summary": "All active products in a store",
        "parameters": [
          { "name": "storeName", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/StoreName" } }
        ],
        "responses": {
          "200": {
            "description": "Store catalog",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StoreResponse" } } }
          },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/products/{id}": {
      "get": {
        "tags": ["product"],
        "summary": "Single product detail",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Product detail",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "product": { "$ref": "#/components/schemas/Product" } } } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/products/{id}/price-history": {
      "get": {
        "tags": ["product"],
        "summary": "Price history for a product",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "days", "in": "query", "schema": { "type": "integer", "default": 30, "minimum": 1, "maximum": 365 } }
        ],
        "responses": {
          "200": {
            "description": "Price history with stats",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PriceHistoryResponse" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/products/{id}/matches": {
      "get": {
        "tags": ["comparison"],
        "summary": "Find semantically similar products (across stores)",
        "description": "Returns products similar to {id} with similarity score, price difference, and savings percent.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10, "maximum": 50 } },
          { "name": "otherStores", "in": "query", "schema": { "type": "boolean" }, "description": "Only matches from other stores (most useful for price comparison)" },
          { "name": "sameStore", "in": "query", "schema": { "type": "boolean" }, "description": "Only matches from the same store" },
          { "name": "store", "in": "query", "schema": { "$ref": "#/components/schemas/StoreName" }, "description": "Filter to a specific store" },
          { "name": "minSimilarity", "in": "query", "schema": { "type": "number", "default": 0.55, "minimum": 0, "maximum": 1 } }
        ],
        "responses": {
          "200": {
            "description": "Matched products",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MatchesResponse" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/products/biggest-savings": {
      "get": {
        "tags": ["comparison"],
        "summary": "Top cross-store savings opportunities",
        "description": "Pairs of semantically similar products across different stores, sorted by savings percentage. Heavy endpoint — cached 5 minutes.",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 12, "maximum": 50 } },
          { "name": "minSimilarity", "in": "query", "schema": { "type": "number", "default": 0.80, "minimum": 0.55, "maximum": 1 } },
          { "name": "minSavings", "in": "query", "schema": { "type": "number", "default": 3 }, "description": "Minimum CZK price difference" }
        ],
        "responses": {
          "200": {
            "description": "Savings pairs",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SavingsResponse" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "StoreName": {
        "type": "string",
        "enum": ["LIDL", "Kaufland", "Albert", "BILLA", "Penny", "Tesco", "Globus", "Makro", "rohlik.cz", "Kosik.cz", "COOP"]
      },
      "CategorySlug": {
        "type": "string",
        "enum": ["mleko", "pecivo", "maslo", "vejce", "syr", "jogurt", "maso", "uzeniny", "ovoce", "zelenina", "napoje", "pivo", "vino", "cukrovinky", "cerealie", "testoviny", "ryze", "olej", "kava", "caj"]
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string", "description": "Czech product name" },
          "price": { "type": "number", "description": "CZK incl. VAT" },
          "amount": { "type": "string", "nullable": true, "description": "e.g. '250 g', '1 l'" },
          "store": { "$ref": "#/components/schemas/StoreName" },
          "imageUrl": { "type": "string", "nullable": true, "format": "uri" },
          "image": { "type": "string", "nullable": true, "format": "uri", "description": "Alias for imageUrl used by search endpoints" },
          "validUntil": { "type": "string", "nullable": true, "format": "date-time", "description": "Promo expiry; null for permanent assortment" },
          "lastUpdated": { "type": "string", "format": "date-time" },
          "stock": { "type": "integer", "description": "Always 1 — groceries assumed in stock" }
        }
      },
      "ProductWithSimilarity": {
        "allOf": [
          { "$ref": "#/components/schemas/Product" },
          { "type": "object", "properties": { "similarity": { "type": "number", "minimum": 0, "maximum": 1 }, "viewCount": { "type": "integer" } } }
        ]
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } },
          "totalResults": { "type": "integer" },
          "query": { "type": "string" },
          "categories": { "type": "array", "items": {} }
        }
      },
      "CategoryResponse": {
        "type": "object",
        "properties": {
          "category": {
            "type": "object",
            "properties": {
              "id": { "$ref": "#/components/schemas/CategorySlug" },
              "name": { "type": "string" },
              "emoji": { "type": "string" },
              "keywords": { "type": "array", "items": { "type": "string" } }
            }
          },
          "products": { "type": "array", "items": { "$ref": "#/components/schemas/ProductWithSimilarity" } },
          "stores": { "type": "array", "items": { "$ref": "#/components/schemas/StoreName" } },
          "total": { "type": "integer" },
          "hasMore": { "type": "boolean" }
        }
      },
      "StoreResponse": {
        "type": "object",
        "properties": {
          "storeName": { "$ref": "#/components/schemas/StoreName" },
          "products": { "type": "array", "items": { "$ref": "#/components/schemas/Product" } },
          "total": { "type": "integer" }
        }
      },
      "PriceHistoryResponse": {
        "type": "object",
        "properties": {
          "product": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "name": { "type": "string" },
              "currentPrice": { "type": "number" },
              "currency": { "type": "string" },
              "store": { "$ref": "#/components/schemas/StoreName" },
              "amount": { "type": "string", "nullable": true },
              "imageUrl": { "type": "string", "nullable": true }
            }
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "price": { "type": "number" },
                "recordedAt": { "type": "string", "format": "date-time" }
              }
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "minPrice": { "type": "number" },
              "maxPrice": { "type": "number" },
              "avgPrice": { "type": "number" },
              "priceChangePercent": { "type": "number" },
              "dataPoints": { "type": "integer" }
            }
          },
          "days": { "type": "integer" }
        }
      },
      "MatchesResponse": {
        "type": "object",
        "properties": {
          "product": { "$ref": "#/components/schemas/Product" },
          "matches": {
            "type": "array",
            "items": {
              "allOf": [
                { "$ref": "#/components/schemas/Product" },
                {
                  "type": "object",
                  "properties": {
                    "similarity": { "type": "number" },
                    "priceDifference": { "type": "number", "description": "Source price minus match price; positive = source is more expensive" },
                    "savingsPercent": { "type": "integer" }
                  }
                }
              ]
            }
          },
          "totalMatches": { "type": "integer" }
        }
      },
      "SavingsResponse": {
        "type": "object",
        "properties": {
          "savings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "cheapestProduct": { "$ref": "#/components/schemas/Product" },
                "expensiveProduct": { "$ref": "#/components/schemas/Product" },
                "savings": { "type": "number", "description": "CZK saved" },
                "savingsPercent": { "type": "integer" },
                "similarity": { "type": "number" }
              }
            }
          },
          "totalFound": { "type": "integer" },
          "cached": { "type": "boolean" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" },
          "retryAfter": { "type": "integer" }
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "Resource not found",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Honor Retry-After header.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds until reset" },
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ServerError": {
        "description": "Server error",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
