{
  "openapi": "3.1.0",
  "info": {
    "title": "Gingerguide City Catalog API",
    "version": "1.0.0",
    "summary": "Read-only catalog of Gingerguide audio walking tours.",
    "description": "Public, read-only, no-auth HTTP API for the Gingerguide catalog of European cities and their narrated audio-walking-tour sights.\n\n## Versioning\nThe current generation is **v1**, served under `/api/v1`. Unversioned `/api/cities.json` (and friends) are stable aliases of v1 and will not break without a `/api/v2` release.\n\nBreaking changes ship in a new path prefix (`/api/v2`). When an old generation is retired it is marked `deprecated: true` in this document and responses include `Deprecation` and `Sunset` headers (RFC 8594) for at least 90 days before removal.\n\n## Authentication\nNone. Do not send API keys or OAuth tokens.\n\n## Errors\n4xx/5xx bodies use RFC 9457 `application/problem+json` with `code`, `detail`, and a `hint` telling an agent how to recover.\n\n## Rate limits\n60 requests per 60-second window per client IP. Successful responses include `RateLimit` and `RateLimit-Policy`. HTTP 429 includes `Retry-After`.\n\n## When to use\nWalking-tour planning for a covered European city. Not for bookings, live transit, or cities outside the catalog.",
    "contact": {
      "name": "Gingerguide developer support",
      "url": "https://gingerguide.app/developers/",
      "email": "support@gingertech.co"
    },
    "license": {
      "name": "Terms of use",
      "url": "https://gingerguide.app/en/terms/"
    },
    "termsOfService": "https://gingerguide.app/en/terms/"
  },
  "externalDocs": {
    "description": "Gingerguide developer docs, MCP, and CLI",
    "url": "https://gingerguide.app/developers/"
  },
  "servers": [
    {
      "url": "https://gingerguide.app/api/v1",
      "description": "Catalog API v1 (current). Breaking changes will use /api/v2."
    },
    {
      "url": "https://gingerguide.app/api",
      "description": "Stable v1 aliases (same resources, no /v1 prefix)."
    }
  ],
  "tags": [
    {
      "name": "Catalog",
      "description": "Cities, countries, and narrated sights."
    },
    {
      "name": "Meta",
      "description": "Health and coverage totals."
    }
  ],
  "paths": {
    "/cities.json": {
      "get": {
        "operationId": "listCities",
        "tags": [
          "Catalog"
        ],
        "summary": "List every covered city with its sight count.",
        "description": "Returns the full Gingerguide city catalog: id, display name, country, narrated-sight count, coordinates, and the human web-guide URL. No authentication. Use this to discover valid `id` values for getCity. When to call: a user asked for an audio walking tour of a European city, or you need to resolve a city name to a catalog id.",
        "responses": {
          "200": {
            "description": "The full city list.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header: limit, remaining, and reset (seconds).",
                "schema": {
                  "type": "string",
                  "example": "limit=60, remaining=59, reset=60"
                }
              },
              "RateLimit-Policy": {
                "description": "Declared quota window.",
                "schema": {
                  "type": "string",
                  "example": "60;w=60"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CityList"
                }
              }
            }
          },
          "400": {
            "description": "Bad request. The path or query is malformed. Read `hint` for how to fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city id or API path. GET /api/v1/cities.json for valid ids.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. The catalog API is read-only; use GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Honor Retry-After and RateLimit headers; retry after the window.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Retry; then contact support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/cities/{id}.json": {
      "get": {
        "operationId": "getCity",
        "tags": [
          "Catalog"
        ],
        "summary": "Get one city with its narrated sights.",
        "description": "Returns one city record plus every narrated sight (name, one-line description, category, coordinates, visit minutes). Unknown ids return application/problem+json 404 with code `not_found`. When to call: after listCities (or search_cities via MCP) has given you an id.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "City slug, lowercase ASCII, matching the `id` field from listCities (e.g. \"rome\", \"paris\", \"new-york\" is not covered — Europe only).",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$",
              "minLength": 2,
              "maxLength": 64
            },
            "example": "rome"
          }
        ],
        "responses": {
          "200": {
            "description": "The city record.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header: limit, remaining, and reset (seconds).",
                "schema": {
                  "type": "string",
                  "example": "limit=60, remaining=59, reset=60"
                }
              },
              "RateLimit-Policy": {
                "description": "Declared quota window.",
                "schema": {
                  "type": "string",
                  "example": "60;w=60"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/City"
                }
              }
            }
          },
          "400": {
            "description": "Bad request. The path or query is malformed. Read `hint` for how to fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city id or API path. GET /api/v1/cities.json for valid ids.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. The catalog API is read-only; use GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Honor Retry-After and RateLimit headers; retry after the window.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Retry; then contact support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/health.json": {
      "get": {
        "operationId": "health",
        "tags": [
          "Meta"
        ],
        "summary": "Service health and catalog totals.",
        "description": "Liveness probe plus catalog totals (cities, narrated sights, countries) and the time the snapshot was generated. Use this before a batch of lookups, or to show coverage counts in a reply.",
        "responses": {
          "200": {
            "description": "Health payload.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header: limit, remaining, and reset (seconds).",
                "schema": {
                  "type": "string",
                  "example": "limit=60, remaining=59, reset=60"
                }
              },
              "RateLimit-Policy": {
                "description": "Declared quota window.",
                "schema": {
                  "type": "string",
                  "example": "60;w=60"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "400": {
            "description": "Bad request. The path or query is malformed. Read `hint` for how to fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city id or API path. GET /api/v1/cities.json for valid ids.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. The catalog API is read-only; use GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Honor Retry-After and RateLimit headers; retry after the window.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Retry; then contact support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/countries.json": {
      "get": {
        "operationId": "listCountries",
        "tags": [
          "Catalog"
        ],
        "summary": "List covered countries and how many cities each has.",
        "description": "Aggregates the catalog by country. When to call: a user asked which countries Gingerguide covers, or you want to filter listCities by country.",
        "responses": {
          "200": {
            "description": "Countries with city counts.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header: limit, remaining, and reset (seconds).",
                "schema": {
                  "type": "string",
                  "example": "limit=60, remaining=59, reset=60"
                }
              },
              "RateLimit-Policy": {
                "description": "Declared quota window.",
                "schema": {
                  "type": "string",
                  "example": "60;w=60"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CountryList"
                }
              }
            }
          },
          "400": {
            "description": "Bad request. The path or query is malformed. Read `hint` for how to fix it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown city id or API path. GET /api/v1/cities.json for valid ids.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "405": {
            "description": "Method not allowed. The catalog API is read-only; use GET.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Honor Retry-After and RateLimit headers; retry after the window.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Retry; then contact support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "hint"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code.",
            "examples": [
              "not_found",
              "method_not_allowed",
              "rate_limited"
            ]
          },
          "hint": {
            "type": "string",
            "description": "What an agent should do next."
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "hint": {
                "type": "string"
              }
            }
          }
        }
      },
      "CitySummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name",
          "country",
          "poiCount",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Slug used in getCity.",
            "example": "rome"
          },
          "name": {
            "type": "string",
            "example": "Rome"
          },
          "country": {
            "type": "string",
            "example": "Italy"
          },
          "poiCount": {
            "type": "integer",
            "minimum": 0
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "CityList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "site",
          "generatedAt",
          "count",
          "cities"
        ],
        "properties": {
          "site": {
            "type": "string",
            "format": "uri"
          },
          "generatedAt": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "cities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CitySummary"
            }
          }
        }
      },
      "Sight": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "shortDescription": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "visitDurationMinutes": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "City": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name",
          "country",
          "url",
          "poiCount",
          "sights"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "poiCount": {
            "type": "integer",
            "minimum": 0
          },
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "summary": {
            "type": "string"
          },
          "about": {
            "type": "string"
          },
          "sights": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Sight"
            }
          }
        }
      },
      "Health": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "generatedAt",
          "cities",
          "pois",
          "countries"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "generatedAt": {
            "type": "string"
          },
          "cities": {
            "type": "integer",
            "minimum": 0
          },
          "pois": {
            "type": "integer",
            "minimum": 0
          },
          "countries": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "CountryCount": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "country",
          "cities"
        ],
        "properties": {
          "country": {
            "type": "string"
          },
          "cities": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "CountryList": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "site",
          "generatedAt",
          "count",
          "countries"
        ],
        "properties": {
          "site": {
            "type": "string",
            "format": "uri"
          },
          "generatedAt": {
            "type": "string"
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "countries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CountryCount"
            }
          }
        }
      }
    }
  }
}