{
  "openapi": "3.1.0",
  "info": {
    "title": "Брест Гид — public content + programmatic API",
    "description": "Russian-language travel directory for Brest, Belarus. Public read-only API surfaces: (a) HTML content with Schema.org JSON-LD via sitemap, (b) markdown summaries for AI agents (llms.txt, llms-full.txt, agents.md), (c) programmatic JSON endpoints under /api/v1/* for search, listing, and partner deeplink resolution, (d) MCP server at /api/mcp implementing JSON-RPC 2.0 with 7 tools (search_*, get_by_slug, list_published).",
    "version": "1.1.0",
    "contact": {
      "name": "Брест Гид",
      "email": "roman.rb.aliev@gmail.com",
      "url": "https://brest-gid.ru/contact"
    },
    "license": {
      "name": "CC-BY-NC",
      "url": "https://brest-gid.ru/about"
    }
  },
  "servers": [
    {
      "url": "https://brest-gid.ru",
      "description": "Production"
    }
  ],
  "paths": {
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "summary": "Site URL inventory",
        "description": "XML sitemap (~100 URLs post-S24 curation: catalog primary pages, detail pages, articles).",
        "responses": {
          "200": {
            "description": "Sitemap XML",
            "content": {
              "application/xml": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsManifest",
        "summary": "Short site summary for AI agents",
        "description": "Markdown manifest following llms.txt convention. Section links + quick facts about Brest.",
        "responses": {
          "200": {
            "description": "Markdown text",
            "content": {
              "text/markdown": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullContent",
        "summary": "Full content dump for AI agents (one-shot read)",
        "description": "Extended markdown with all key facts. Designed for agents to answer most queries without further crawling.",
        "responses": {
          "200": {
            "description": "Markdown text",
            "content": {
              "text/markdown": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsPolicy",
        "summary": "Crawler access policy",
        "description": "robots.txt with explicit AI agent allowlist (GPTBot, ClaudeBot, PerplexityBot, GoogleOther) + tier-2 training crawler denylist.",
        "responses": {
          "200": {
            "description": "Text/plain robots.txt",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/.well-known/agent-card.json": {
      "get": {
        "operationId": "getAgentCard",
        "summary": "A2A agent card",
        "description": "Agent capability card following A2A protocol. Lists site skills.",
        "responses": {
          "200": {
            "description": "JSON agent card",
            "content": {
              "application/json": { "schema": { "type": "object" } }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpManifest",
        "summary": "MCP server manifest",
        "description": "MCP 2025-06-18 manifest pointing к /api/mcp endpoint. Lists 7 tools available для AI agents.",
        "responses": {
          "200": {
            "description": "JSON manifest",
            "content": {
              "application/json": { "schema": { "type": "object" } }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Health check",
        "description": "Returns service status + timestamp. Public, no auth.",
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "enum": ["ok"] },
                    "timestamp": { "type": "string", "format": "date-time" }
                  },
                  "required": ["status", "timestamp"]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "operationId": "searchGet",
        "summary": "Search across catalogs (GET)",
        "description": "NLWeb-compatible search. Returns Schema.org-typed results from 5 catalogs + articles.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Query string (Russian or English)"
          },
          {
            "name": "collection",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["cafes", "accommodation", "transfers", "excursions", "sights", "articles"]
            },
            "description": "Restrict to one collection (default: search all)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResponse" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "searchPost",
        "summary": "Search across catalogs (POST)",
        "description": "Same as GET but body-based for AI function calling.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SearchRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResponse" }
              }
            }
          }
        }
      }
    },
    "/api/v1/booking-intent": {
      "post": {
        "operationId": "bookingIntent",
        "summary": "Partner deeplink resolver",
        "description": "Returns WhatsApp/Telegram/phone deeplinks для contact с partner. NO actual booking processed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["slug", "collection"],
                "properties": {
                  "slug": { "type": "string" },
                  "collection": {
                    "type": "string",
                    "enum": ["cafes", "accommodation", "transfers", "excursions", "sights"]
                  },
                  "intent": {
                    "type": "string",
                    "enum": ["whatsapp", "telegram", "phone", "web", "best"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Partner contact info",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BookingIntentResponse" }
              }
            }
          },
          "404": {
            "description": "Slug not found or unpublished"
          }
        }
      }
    },
    "/api/v1/list/{collection}": {
      "get": {
        "operationId": "listCollectionGet",
        "summary": "Batch list per collection",
        "description": "Cursor-paginated listing. No full-text search — use /api/v1/search.",
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": ["cafes", "accommodation", "transfers", "excursions", "sights", "articles"]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListResponse" }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "listCollectionPost",
        "summary": "Batch list per collection (POST)",
        "description": "Same as GET but body-based.",
        "parameters": [
          {
            "name": "collection",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": ["cafes", "accommodation", "transfers", "excursions", "sights", "articles"]
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "limit": { "type": "integer", "minimum": 1, "maximum": 50 },
                  "offset": { "type": "integer", "minimum": 0 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paginated list",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListResponse" }
              }
            }
          }
        }
      }
    },
    "/api/mcp": {
      "post": {
        "operationId": "mcpEndpoint",
        "summary": "MCP server (JSON-RPC 2.0)",
        "description": "Implements MCP protocol. Methods: initialize, tools/list, tools/call. 7 tools available — see /.well-known/mcp.json для discovery.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["jsonrpc", "id", "method"],
                "properties": {
                  "jsonrpc": { "type": "string", "enum": ["2.0"] },
                  "id": { "type": ["string", "number"] },
                  "method": { "type": "string" },
                  "params": { "type": "object" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application/json": { "schema": { "type": "object" } }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {},
    "schemas": {
      "SearchRequest": {
        "type": "object",
        "required": ["query"],
        "properties": {
          "query": { "type": "string", "description": "Search term (Russian or English)" },
          "collection": {
            "type": "string",
            "enum": ["cafes", "accommodation", "transfers", "excursions", "sights", "articles"]
          },
          "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": ["query", "results", "total"],
        "properties": {
          "query": { "type": "string" },
          "collection": { "type": ["string", "null"] },
          "total": { "type": "integer" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SearchHit" }
          }
        }
      },
      "SearchHit": {
        "type": "object",
        "required": ["name", "slug", "url", "schema_type", "snippet", "collection"],
        "properties": {
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "schema_type": {
            "type": "string",
            "description": "Schema.org type",
            "enum": [
              "Restaurant",
              "LodgingBusiness",
              "Service",
              "TouristTrip",
              "TouristAttraction",
              "Article"
            ]
          },
          "snippet": { "type": "string", "maxLength": 250 },
          "collection": { "type": "string" }
        }
      },
      "BookingIntentResponse": {
        "type": "object",
        "required": ["slug", "collection", "partner_name", "contact"],
        "properties": {
          "slug": { "type": "string" },
          "collection": { "type": "string" },
          "name": { "type": ["string", "null"] },
          "detail_url": { "type": "string", "format": "uri" },
          "partner_name": {
            "type": "string",
            "enum": [
              "telegram",
              "whatsapp",
              "phone",
              "external_partner",
              "website",
              "direct",
              "none"
            ]
          },
          "partner_url": { "type": ["string", "null"] },
          "contact": {
            "type": "object",
            "properties": {
              "phone": { "type": ["string", "null"] },
              "whatsapp": { "type": ["string", "null"] },
              "telegram": { "type": ["string", "null"] },
              "website": { "type": ["string", "null"] }
            }
          }
        }
      },
      "ListResponse": {
        "type": "object",
        "required": ["collection", "items", "total", "limit", "offset", "has_more"],
        "properties": {
          "collection": { "type": "string" },
          "items": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/ListItem" }
          },
          "total": { "type": "integer" },
          "limit": { "type": "integer" },
          "offset": { "type": "integer" },
          "has_more": { "type": "boolean" }
        }
      },
      "ListItem": {
        "type": "object",
        "required": ["slug", "name", "url"],
        "properties": {
          "slug": { "type": "string" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "address": { "type": ["string", "null"] },
          "price_from": { "type": ["number", "null"] }
        }
      }
    }
  }
}
