{
  "openapi": "3.1.0",
  "info": {
    "title": "Cerveja Barata public API",
    "version": "1.0.0",
    "description": "Real beer prices from bars in Brazil, ranked by price per litre. Open data under the ODbL 1.0: credit \"Cerveja Barata (cervejabarata.com.br)\" and keep derived databases open. Static files cover everything and are free to fetch; the live endpoints answer at most 50 prices, are cached for 10–15 minutes and answer 503 with the static files' address when the database runs low. No key needed.",
    "contact": {
      "name": "Cerveja Barata",
      "email": "contato@cervejabarata.com.br",
      "url": "https://cervejabarata.com.br"
    },
    "license": {
      "name": "ODbL-1.0",
      "url": "https://opendatacommons.org/licenses/odbl/1-0/"
    }
  },
  "servers": [
    {
      "url": "https://cervejabarata.com.br"
    }
  ],
  "components": {
    "schemas": {
      "StatusResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "time": {
            "type": "string",
            "format": "date-time"
          },
          "environment": {
            "type": "string",
            "enum": [
              "production",
              "staging",
              "development"
            ]
          },
          "db": {
            "type": "boolean"
          },
          "rateLimits": {
            "type": "object",
            "properties": {
              "read": {
                "type": "boolean"
              },
              "write": {
                "type": "boolean"
              }
            },
            "required": [
              "read",
              "write"
            ]
          }
        },
        "required": [
          "ok",
          "time",
          "environment",
          "db",
          "rateLimits"
        ]
      },
      "DealsResponse": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string"
          },
          "city_url": {
            "type": "string"
          },
          "as_of": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "deals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicDeal"
            }
          },
          "source": {
            "type": "string",
            "description": "Attribution: Cerveja Barata (cervejabarata.com.br), ODbL"
          },
          "local_time": {
            "type": "string",
            "description": "The city's time (cheapest only)"
          }
        },
        "required": [
          "city",
          "city_url",
          "as_of",
          "count",
          "deals",
          "source"
        ]
      },
      "PublicDeal": {
        "type": "object",
        "properties": {
          "venue": {
            "type": "string"
          },
          "neighbourhood": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "description": "The bar's page on cervejabarata.com.br, to cite"
          },
          "beer": {
            "type": "string"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ]
          },
          "serving": {
            "type": "string",
            "description": "e.g. \"Garrafa 600 ml\" or \"Balde: 5 × Long neck 330 ml\""
          },
          "format": {
            "type": "string"
          },
          "price_brl": {
            "type": "number"
          },
          "price_per_litre_brl": {
            "type": "number",
            "description": "Absent for open bars"
          },
          "open_bar_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price_label": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "regular",
              "happy_hour",
              "promo",
              "combo",
              "open_bar"
            ]
          },
          "when": {
            "type": [
              "string",
              "null"
            ],
            "description": "Happy hours and timed promos, e.g. \"seg–sex 17h–20h\""
          },
          "service_charge_pct": {
            "type": [
              "integer",
              "null"
            ]
          },
          "couvert_brl": {
            "type": [
              "number",
              "null"
            ]
          },
          "conditions": {
            "type": [
              "string",
              "null"
            ]
          },
          "verified_on": {
            "type": "string",
            "description": "City-local date of the last check"
          },
          "possibly_out_of_date": {
            "type": "boolean",
            "description": "Last checked more than 90 days ago"
          },
          "confirmations": {
            "type": "integer"
          },
          "distance_m": {
            "type": "integer",
            "description": "Only from /cheapest with lat and lng"
          }
        },
        "required": [
          "venue",
          "neighbourhood",
          "url",
          "beer",
          "brand",
          "serving",
          "format",
          "price_brl",
          "price_label",
          "kind",
          "when",
          "service_charge_pct",
          "couvert_brl",
          "conditions",
          "verified_on",
          "possibly_out_of_date",
          "confirmations"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable code: invalid_request, unauthorized, forbidden, not_found, duplicate…"
          },
          "message": {
            "type": "string"
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "message"
              ]
            }
          }
        },
        "required": [
          "error"
        ]
      },
      "DegradedResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "degraded"
            ]
          },
          "message": {
            "type": "string"
          },
          "datasets": {
            "type": "string",
            "description": "The static datasets, which keep working: /api/v1/index.json"
          }
        },
        "required": [
          "error",
          "message",
          "datasets"
        ]
      },
      "VenueResponse": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string"
          },
          "city_url": {
            "type": "string"
          },
          "as_of": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "deals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PublicDeal"
            }
          },
          "source": {
            "type": "string",
            "description": "Attribution: Cerveja Barata (cervejabarata.com.br), ODbL"
          },
          "venue": {
            "$ref": "#/components/schemas/PublicVenue"
          }
        },
        "required": [
          "city",
          "city_url",
          "as_of",
          "count",
          "deals",
          "source",
          "venue"
        ]
      },
      "PublicVenue": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The bar's page, once it has a price"
          },
          "kind": {
            "type": "string"
          },
          "drink_on_site": {
            "type": "boolean"
          },
          "neighbourhood": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "instagram": {
            "type": [
              "string",
              "null"
            ]
          },
          "opening_hours": {
            "type": [
              "string",
              "null"
            ]
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "name",
          "url",
          "kind",
          "drink_on_site",
          "neighbourhood",
          "address",
          "lat",
          "lng",
          "instagram",
          "opening_hours",
          "attributes"
        ]
      },
      "Received": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "received",
              "duplicate"
            ],
            "description": "duplicate: the same thing from the same address is already waiting for review"
          }
        },
        "required": [
          "status"
        ]
      },
      "Submission": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "venue": {
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "id"
                ],
                "additionalProperties": false,
                "description": "A bar already on the map"
              },
              {
                "type": "object",
                "properties": {
                  "new": {
                    "$ref": "#/components/schemas/NewVenue"
                  }
                },
                "required": [
                  "new"
                ],
                "additionalProperties": false,
                "description": "A bar that isn't on the map yet"
              }
            ]
          },
          "beer": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 80,
            "description": "As written; null for \"outra / não sei\""
          },
          "format_code": {
            "type": "string",
            "maxLength": 40,
            "description": "A format from the catalog, e.g. garrafa_600"
          },
          "price": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000,
            "description": "What was paid, in currency units (9.99)"
          },
          "units": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          },
          "packaging": {
            "type": "string",
            "enum": [
              "single",
              "balde",
              "torre",
              "combo",
              "dobro"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "regular",
              "happy_hour",
              "promo",
              "combo",
              "open_bar"
            ]
          },
          "open_bar_minutes": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 1440
          },
          "schedules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScheduleDraft"
            },
            "maxItems": 14
          },
          "valid_from": {
            "type": "string",
            "format": "date"
          },
          "valid_until": {
            "type": "string",
            "format": "date"
          },
          "conditions": {
            "type": "string",
            "maxLength": 280
          },
          "service_charge_pct": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30
          },
          "couvert": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "musica_ao_vivo",
                "transmite_jogos",
                "area_externa",
                "pe_na_areia"
              ]
            },
            "maxItems": 4
          },
          "seen_in": {
            "type": "string",
            "enum": [
              "menu",
              "poster",
              "instagram",
              "staff",
              "receipt",
              "other"
            ]
          },
          "link": {
            "type": "string",
            "maxLength": 500,
            "format": "uri"
          },
          "observed_on": {
            "type": "string",
            "format": "date",
            "description": "City-local date the price was seen"
          },
          "notes": {
            "type": "string",
            "maxLength": 500
          },
          "is_owner": {
            "type": "boolean",
            "description": "Sent by the bar's owner or manager"
          },
          "nickname": {
            "type": "string",
            "minLength": 2,
            "maxLength": 40,
            "description": "Credited on the site when the price goes live"
          },
          "email": {
            "type": "string",
            "maxLength": 200,
            "format": "email",
            "description": "Only to say \"seu preço está no ar\"; deleted after"
          },
          "consent": {
            "type": "boolean",
            "description": "LGPD consent: required with an email"
          },
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "city",
          "venue",
          "beer",
          "format_code",
          "price",
          "turnstile"
        ],
        "additionalProperties": false
      },
      "NewVenue": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          },
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          },
          "kind": {
            "type": "string",
            "enum": [
              "bar",
              "boteco",
              "pub",
              "barraca",
              "restaurante",
              "deposito",
              "cervejaria",
              "outro"
            ]
          },
          "on_site": {
            "type": "boolean",
            "description": "People can drink there (a depósito only counts when they can)"
          },
          "address": {
            "type": "string",
            "maxLength": 200
          },
          "instagram": {
            "type": "string",
            "pattern": "^[a-z0-9._]{1,30}$"
          },
          "opening_hours": {
            "type": "string",
            "maxLength": 255,
            "description": "As written (\"seg a sex 17h–1h\")"
          }
        },
        "required": [
          "name",
          "lat",
          "lng",
          "kind",
          "on_site"
        ],
        "additionalProperties": false
      },
      "ScheduleDraft": {
        "type": "object",
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 6
            },
            "minItems": 1,
            "maxItems": 7,
            "description": "0 = Sunday … 6 = Saturday, city time"
          },
          "start": {
            "type": "string",
            "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
            "description": "HH:MM"
          },
          "end": {
            "type": "string",
            "pattern": "^([01]\\d|2[0-4]):[0-5]\\d$",
            "description": "HH:MM; earlier than start when it ends after midnight"
          }
        },
        "required": [
          "days",
          "start",
          "end"
        ]
      },
      "CityRequest": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "minLength": 2,
            "maxLength": 80,
            "description": "The city, as written"
          },
          "state": {
            "type": "string",
            "minLength": 2,
            "maxLength": 40,
            "description": "Its state, as written (BA, Bahia)"
          },
          "email": {
            "type": "string",
            "maxLength": 200,
            "format": "email",
            "description": "Only to say the city is live; deleted after"
          },
          "consent": {
            "type": "boolean",
            "description": "LGPD consent: required with an email"
          },
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "city",
          "turnstile"
        ],
        "additionalProperties": false
      },
      "BarOwnerRequest": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "bar": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120,
            "description": "The bar, as written"
          },
          "venue": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
            "description": "The bar's slug, when the form came from its page"
          },
          "topic": {
            "type": "string",
            "enum": [
              "sponsor",
              "prices",
              "correction",
              "removal",
              "other"
            ]
          },
          "message": {
            "type": "string",
            "maxLength": 1000
          },
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 80,
            "description": "Who to ask for"
          },
          "contact": {
            "type": "string",
            "minLength": 5,
            "maxLength": 120,
            "description": "WhatsApp, phone or email to reply to; deleted after 90 days"
          },
          "consent": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "LGPD consent to be contacted about this request"
          },
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "city",
          "bar",
          "topic",
          "contact",
          "consent",
          "turnstile"
        ],
        "additionalProperties": false
      },
      "MenuPhotoUpload": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "venue": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
            "description": "The bar's slug in that city"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 700000,
              "description": "A JPEG photo as a data URI (base64)"
            },
            "minItems": 1,
            "maxItems": 3
          },
          "contact": {
            "type": "string",
            "minLength": 5,
            "maxLength": 120,
            "description": "Phone or email, only to ask about these prices; deleted after 90 days"
          },
          "is_owner": {
            "type": "boolean",
            "description": "Sent by the bar's owner or manager"
          },
          "ref": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{16}$",
            "description": "The bar's own link token (?ref= on the page, from GET /api/admin/venues/{id}/menu-link): a valid one makes the upload trusted, so its prices inside the hard bounds are approved at once"
          },
          "consent": {
            "type": "boolean",
            "description": "LGPD consent: required with a contact"
          },
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "city",
          "venue",
          "images",
          "turnstile"
        ],
        "additionalProperties": false
      },
      "DealConfirm": {
        "type": "object",
        "properties": {
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "turnstile"
        ],
        "additionalProperties": false
      },
      "DealDispute": {
        "type": "object",
        "properties": {
          "price": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 10000,
            "description": "What it costs now, in currency units (9.99)"
          },
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "turnstile"
        ],
        "additionalProperties": false
      },
      "Report": {
        "type": "object",
        "properties": {
          "deal": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "The price, when the report is about one"
          },
          "venue": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "The bar, when the report is about it"
          },
          "reason": {
            "type": "string",
            "enum": [
              "closed",
              "wrong_venue",
              "wrong_beer",
              "wrong_price",
              "other"
            ]
          },
          "message": {
            "type": "string",
            "maxLength": 500
          },
          "turnstile": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "Cloudflare Turnstile token"
          }
        },
        "required": [
          "reason",
          "turnstile"
        ],
        "additionalProperties": false
      }
    },
    "parameters": {}
  },
  "paths": {
    "/api/v1/status": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "Deployment health and configuration",
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "Search a city's beer prices",
        "description": "The ranking with filters, cheapest per litre first (at most 50). Open bars only with kind=open_bar, by package price. Prices older than 180 days are left out. Answers are cached for a few minutes.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "description": "City slug, e.g. \"salvador\" (see /api/v1/cities.json)"
            },
            "required": true,
            "description": "City slug, e.g. \"salvador\" (see /api/v1/cities.json)",
            "name": "city",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "description": "Bairro slug"
            },
            "required": false,
            "description": "Bairro slug",
            "name": "neighbourhood",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "description": "Brand slug, e.g. \"brahma\""
            },
            "required": false,
            "description": "Brand slug, e.g. \"brahma\"",
            "name": "brand",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_-]{2,40}$",
              "description": "Format type (lata, long_neck, garrafa, litrao, chopp, torre) or code (garrafa_600)"
            },
            "required": false,
            "description": "Format type (lata, long_neck, garrafa, litrao, chopp, torre) or code (garrafa_600)",
            "name": "format",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "regular",
                "happy_hour",
                "promo",
                "combo",
                "open_bar"
              ],
              "description": "Only this kind of price; open bars only with open_bar"
            },
            "required": false,
            "description": "Only this kind of price; open bars only with open_bar",
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0,
              "maximum": 1000,
              "description": "In BRL per litre"
            },
            "required": false,
            "description": "In BRL per litre",
            "name": "max_price_per_litre",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pt-BR",
                "en"
              ],
              "default": "pt-BR",
              "description": "Language of labels, schedules and page links"
            },
            "required": false,
            "description": "Language of labels, schedules and page links",
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Prices",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DealsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city or bar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Degraded mode: use the static datasets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DegradedResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cheapest": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "The cheapest beer right now",
        "description": "Prices on right now in the city's time zone, happy hours included, optionally within radius_m of a point (with each price's distance).",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "description": "City slug, e.g. \"salvador\""
            },
            "required": true,
            "description": "City slug, e.g. \"salvador\"",
            "name": "city",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": -90,
              "maximum": 90
            },
            "required": false,
            "name": "lat",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": -180,
              "maximum": 180
            },
            "required": false,
            "name": "lng",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 100,
              "maximum": 10000,
              "default": 2000,
              "description": "Around lat/lng"
            },
            "required": false,
            "description": "Around lat/lng",
            "name": "radius_m",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_-]{2,40}$",
              "description": "Format type (lata, long_neck, garrafa, litrao, chopp, torre) or code (garrafa_600)"
            },
            "required": false,
            "description": "Format type (lata, long_neck, garrafa, litrao, chopp, torre) or code (garrafa_600)",
            "name": "format",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pt-BR",
                "en"
              ],
              "default": "pt-BR",
              "description": "Language of labels, schedules and page links"
            },
            "required": false,
            "description": "Language of labels, schedules and page links",
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Prices on now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DealsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city or bar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Degraded mode: use the static datasets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DegradedResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/venues/{id}": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "One bar and its prices",
        "description": "The bar's details and every price it has inside the rankings. Ids come from the static venue files.",
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "example": 12
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pt-BR",
                "en"
              ],
              "default": "pt-BR",
              "description": "Language of labels, schedules and page links"
            },
            "required": false,
            "description": "Language of labels, schedules and page links",
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The bar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VenueResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city or bar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Degraded mode: use the static datasets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DegradedResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/submissions": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Send a beer price",
        "description": "A price someone saw, for a person to review before it goes live. Behind Turnstile and a daily cap per address; an email, if any, is kept encrypted only to say the price is live, then deleted.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Submission"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued for review",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unknown city, bar or format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/city-requests": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Ask for your city",
        "description": "Counts toward the next cities. Behind Turnstile and a daily cap per address.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CityRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/bar-owners": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Talk to us about your bar",
        "description": "From a bar's owner or manager: sponsorship, their prices, a correction or a removal. Behind Turnstile and a daily cap per address; the contact is only to reply, and is deleted after 90 days.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BarOwnerRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unknown city",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/menu-photos": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Send photos of a bar's menu",
        "description": "Up to 3 JPEG photos of a listed bar's menu (data URIs, the body at most about 2.2 MB). Answers at once; Workers AI then reads the beer prices into drafts for the AI reviewer, and the photos are not kept. Behind Turnstile and a daily cap per address; a contact, if any, is kept encrypted and deleted after 90 days.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MenuPhotoUpload"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Received: the prices are read in the background",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "The photos are too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unknown city or bar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/deals/{id}/confirm": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Still this price: yes",
        "description": "Once a day per address and price, behind Turnstile. Without open disputes it counts as a verification; from 3 confirmations the price shows “confirmed by N people”.",
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "example": 12
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DealConfirm"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Counted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/deals/{id}/dispute": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Still this price: no",
        "description": "Once a day per address and price, behind Turnstile. With 2 open disputes the price is marked as disputed and goes to the moderators; with 3 it leaves the rankings until checked.",
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "example": 12
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DealDispute"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Counted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/reports": {
      "post": {
        "tags": [
          "public"
        ],
        "summary": "Report a problem with a price or a bar",
        "description": "For a person to look at. Behind Turnstile and a daily cap per address.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Report"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Received"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "The Turnstile check failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unknown price or bar",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Closed: not configured yet, or degraded mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/optout/{token}": {
      "get": {
        "tags": [
          "public"
        ],
        "summary": "A bar's one-click opt-out: the page",
        "description": "The link in every outreach email's footer. A small pt-BR page with one button that POSTs to the same URL; a GET changes nothing (mail clients and link scanners open links). The same page for every token.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,9}\\.[A-Za-z0-9_-]{43}$",
              "description": "The lead's id and its HMAC with DATA_KEY, from the email's link",
              "example": "12.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            },
            "required": true,
            "description": "The lead's id and its HMAC with DATA_KEY, from the email's link",
            "name": "token",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The page with the button",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "public"
        ],
        "summary": "A bar's one-click opt-out",
        "description": "The page's button, or the mail client's RFC 8058 one-click POST (List-Unsubscribe-Post: List-Unsubscribe=One-Click). A valid token moves the bar to do_not_contact on every channel and sends one confirmation email. Unknown, invalid and already used tokens get the same answer; repeating it changes nothing.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,9}\\.[A-Za-z0-9_-]{43}$",
              "description": "The lead's id and its HMAC with DATA_KEY, from the email's link",
              "example": "12.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            },
            "required": true,
            "description": "The lead's id and its HMAC with DATA_KEY, from the email's link",
            "name": "token",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The confirmation page",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/index.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Every dataset, per city, with counts",
        "responses": {
          "200": {
            "description": "Every dataset, per city, with counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/cities.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Active cities",
        "responses": {
          "200": {
            "description": "Active cities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/catalog.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Brands, beers and formats",
        "responses": {
          "200": {
            "description": "Brands, beers and formats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/deals.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Published prices in the city",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Published prices in the city",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/deals.csv": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Published prices in the city (CSV: reais, local dates)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Published prices in the city",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/venues.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Bars with at least one price",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Bars with at least one price",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/venues.csv": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Bars with at least one price (CSV: reais, local dates)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Bars with at least one price",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/venues-all.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Every active bar, priced or not",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Every active bar, priced or not",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/venues-all.csv": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Every active bar, priced or not (CSV: reais, local dates)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Every active bar, priced or not",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/price-index.json": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "The monthly Índice Cerveja Barata",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The monthly Índice Cerveja Barata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/price-index.csv": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "The monthly Índice Cerveja Barata (CSV: reais, local dates)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The monthly Índice Cerveja Barata",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/{city}/neighbourhoods.geojson": {
      "get": {
        "tags": [
          "datasets"
        ],
        "summary": "Bairro boundaries (GeoJSON)",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
              "example": "salvador"
            },
            "required": true,
            "name": "city",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Bairro boundaries (GeoJSON)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "updated_at": {
                      "type": "string",
                      "description": "When the build read the data"
                    },
                    "license": {
                      "type": "string",
                      "enum": [
                        "ODbL-1.0"
                      ]
                    },
                    "attribution": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "required": [
                    "updated_at",
                    "license",
                    "attribution"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": [
          "mcp"
        ],
        "summary": "MCP server (streamable HTTP, stateless)",
        "description": "One JSON-RPC message per POST, answered with JSON (no sessions, no streams). Tools: search_deals, cheapest_now, get_venue, list_neighbourhoods, price_index, catalog, price_per_litre.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "id": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      }
                    ]
                  },
                  "method": {
                    "type": "string",
                    "example": "tools/list"
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "jsonrpc",
                  "method"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The JSON-RPC response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {}
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted"
          }
        }
      }
    }
  },
  "webhooks": {}
}
