{
  "openapi": "3.1.0",
  "info": {
    "title": "YALA-BINA Public API",
    "description": "Public endpoints for the YALA-BINA travel marketplace. These endpoints serve machine-readable content (sitemap, prerendered pages) and handle operational webhooks. The primary data interface is the Supabase PostgREST API, authenticated via API key.",
    "version": "1.0.0",
    "contact": {
      "name": "YALA-BINA",
      "url": "https://yala-bina.com",
      "email": "contact@yala-bina.com"
    },
    "x-api-versioning": {
      "strategy": "url-path",
      "current": "v1",
      "deprecation-policy": "Deprecated endpoints receive a Sunset HTTP header at least 90 days before removal. The Deprecation header is set to true on affected responses. Monitor these headers to prepare for migrations."
    }
  },
  "servers": [
    {
      "url": "https://yala-bina.com",
      "description": "Production — v1 (current, stable)"
    }
  ],
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Standard error envelope returned by all endpoints on failure. Compatible with RFC 9457 problem details.",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "status"],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code",
                "examples": ["NOT_FOUND", "VALIDATION_ERROR", "RATE_LIMITED", "INTERNAL_ERROR"]
              },
              "message": {
                "type": "string",
                "description": "Human-readable error description"
              },
              "status": {
                "type": "integer",
                "description": "HTTP status code"
              }
            }
          },
          "links": {
            "type": "object",
            "description": "Recovery links for agents",
            "properties": {
              "sitemap": { "type": "string", "format": "uri" },
              "openapi": { "type": "string", "format": "uri" },
              "developers": { "type": "string", "format": "uri" },
              "llms": { "type": "string", "format": "uri" }
            }
          }
        }
      },
      "Trip": {
        "type": "object",
        "description": "A trip listing on YALA-BINA",
        "properties": {
          "slug": { "type": "string", "description": "URL-friendly identifier" },
          "title": { "type": "string", "description": "Trip title" },
          "description": { "type": "string", "description": "Full trip description" },
          "price_per_person": { "type": "number", "description": "Price in MAD per person" },
          "duration_days": { "type": "integer", "description": "Trip duration in days" },
          "departure_city": { "type": "string", "description": "Primary departure city" },
          "destinations": { "type": "array", "items": { "type": "string" }, "description": "Destination names" },
          "categories": { "type": "array", "items": { "type": "string" }, "description": "Category tags" },
          "agency_name": { "type": "string", "description": "Organizing agency name" },
          "departure_dates": { "type": "array", "items": { "type": "string", "format": "date" }, "description": "Available departure dates" },
          "photos": { "type": "array", "items": { "type": "string", "format": "uri" }, "description": "Photo URLs" }
        }
      },
      "Agency": {
        "type": "object",
        "description": "A verified travel agency on YALA-BINA",
        "properties": {
          "slug": { "type": "string" },
          "agency_name": { "type": "string" },
          "city": { "type": "string" },
          "description": { "type": "string" },
          "phone": { "type": "string" },
          "verified": { "type": "boolean" }
        }
      },
      "BlogPost": {
        "type": "object",
        "description": "A blog article",
        "properties": {
          "slug": { "type": "string" },
          "title": { "type": "string" },
          "excerpt": { "type": "string" },
          "content": { "type": "string" },
          "published_at": { "type": "string", "format": "date-time" },
          "cover_image": { "type": "string", "format": "uri" }
        }
      },
      "SearchResult": {
        "type": "object",
        "description": "Search results page data",
        "properties": {
          "trips": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Trip" }
          },
          "total": { "type": "integer" },
          "filters_applied": {
            "type": "object",
            "properties": {
              "categories": { "type": "array", "items": { "type": "string" } },
              "departure_cities": { "type": "array", "items": { "type": "string" } },
              "destination": { "type": "string" },
              "sort": { "type": "string" }
            }
          }
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Maximum number of requests allowed in the current window",
        "schema": { "type": "integer" }
      },
      "RateLimit-Remaining": {
        "description": "Number of requests remaining in the current window",
        "schema": { "type": "integer" }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the rate limit window resets",
        "schema": { "type": "integer" }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (on 429 responses)",
        "schema": { "type": "integer" }
      }
    }
  },
  "paths": {
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "summary": "XML Sitemap",
        "description": "Returns an XML sitemap listing all published trips, blog posts, agency profiles, and static pages. Cached for 1 hour.",
        "responses": {
          "200": {
            "description": "Sitemap XML document",
            "content": {
              "application/xml": {
                "schema": { "type": "string", "description": "Complete XML sitemap" }
              }
            }
          },
          "500": {
            "description": "Server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "OpenAPI specification",
        "description": "This document. Describes the public API surface of YALA-BINA. Returns an OpenAPI 3.1 JSON document.",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON document",
            "content": {
              "application/json": {
                "schema": { "type": "object", "description": "OpenAPI 3.1 specification object" }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "summary": "LLM-friendly site description",
        "description": "Plain-text summary of the site purpose, structure, machine-readable resources, and agent instructions, following the llms.txt convention.",
        "responses": {
          "200": {
            "description": "Plain text site summary with agent instructions",
            "content": {
              "text/plain": {
                "schema": { "type": "string", "description": "Markdown-formatted site description with when-to-use guidance" }
              }
            }
          }
        }
      }
    },
    "/search": {
      "get": {
        "operationId": "searchTrips",
        "summary": "Search and filter trips",
        "description": "Search page returning trips filtered by category, destination, departure city, date range, and price. Accepts text/markdown via content negotiation for machine-readable results.",
        "parameters": [
          {
            "name": "categories",
            "in": "query",
            "description": "Comma-separated category filters: omra, morocco-domestic, international, adventure, city-cultural, beach-coast, desert, mountain",
            "schema": { "type": "string", "examples": ["omra", "morocco-domestic,adventure"] }
          },
          {
            "name": "departure_cities",
            "in": "query",
            "description": "Comma-separated departure city names",
            "schema": { "type": "string", "examples": ["Casablanca", "Marrakech,Rabat"] }
          },
          {
            "name": "destination",
            "in": "query",
            "description": "Free-text destination search",
            "schema": { "type": "string", "examples": ["Turquie"] }
          },
          {
            "name": "regions",
            "in": "query",
            "description": "Comma-separated region filters",
            "schema": { "type": "string" }
          },
          {
            "name": "sortBy",
            "in": "query",
            "description": "Sort order for results",
            "schema": { "type": "string", "enum": ["newest", "price", "price_desc", "duration", "popularity"], "default": "newest" }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results. Returns HTML by default, or structured data with Accept: text/markdown.",
            "content": {
              "text/html": {
                "schema": { "type": "string", "description": "Rendered search results page" }
              },
              "text/markdown": {
                "schema": { "type": "string", "description": "Markdown-formatted trip listings" }
              }
            }
          },
          "404": {
            "description": "Invalid search path",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/trips/{slug}": {
      "get": {
        "operationId": "getTripDetails",
        "summary": "Trip detail page",
        "description": "Full details of a trip including itinerary, pricing matrix, photos, departure dates, and agency info. Bots receive prerendered HTML with JSON-LD structured data.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL-friendly trip identifier (e.g. voyage-merzouga-desert-sable-212374)",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Trip detail page with full structured data",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML page containing JSON-LD TouristTrip structured data"
                }
              }
            }
          },
          "404": {
            "description": "Trip not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/agencies/{slug}": {
      "get": {
        "operationId": "getAgencyProfile",
        "summary": "Agency profile page",
        "description": "Public profile of a verified travel agency, including their active trip listings, contact info, and verification status.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL-friendly agency identifier",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Agency profile page",
            "content": {
              "text/html": {
                "schema": { "type": "string", "description": "HTML page with agency details and trip listings" }
              }
            }
          },
          "404": {
            "description": "Agency not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/blog": {
      "get": {
        "operationId": "getBlogList",
        "summary": "Blog listing page",
        "description": "Lists all published blog articles about travel in Morocco and international destinations.",
        "responses": {
          "200": {
            "description": "Blog listing with article summaries",
            "content": {
              "text/html": {
                "schema": { "type": "string", "description": "HTML page with blog article cards" }
              }
            }
          }
        }
      }
    },
    "/blog/{slug}": {
      "get": {
        "operationId": "getBlogArticle",
        "summary": "Blog article page",
        "description": "Full blog article with travel content, images, and author info.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL-friendly blog article identifier",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Full blog article",
            "content": {
              "text/html": {
                "schema": { "type": "string", "description": "HTML page with article content" }
              }
            }
          },
          "404": {
            "description": "Article not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/v1/trips": {
      "get": {
        "operationId": "listTrips",
        "summary": "List all published trips (JSON API)",
        "description": "Returns a paginated, filterable list of all published trips with full structured data. This is the primary machine-readable endpoint for agents to discover the YALA-BINA catalog.",
        "parameters": [
          { "name": "page", "in": "query", "description": "Page number (default: 1)", "schema": { "type": "integer", "default": 1, "minimum": 1 } },
          { "name": "limit", "in": "query", "description": "Results per page (default: 50, max: 100)", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 100 } },
          { "name": "destination", "in": "query", "description": "Filter by destination name", "schema": { "type": "string", "examples": ["Istanbul", "Merzouga"] } },
          { "name": "departure_city", "in": "query", "description": "Filter by departure city", "schema": { "type": "string", "examples": ["Casablanca", "Oujda"] } },
          { "name": "category", "in": "query", "description": "Filter by category", "schema": { "type": "string", "enum": ["omra", "morocco-domestic", "international", "adventure", "desert", "mountain", "beach-coast", "city-cultural"] } },
          { "name": "country", "in": "query", "description": "Filter by country", "schema": { "type": "string" } },
          { "name": "max_price", "in": "query", "description": "Maximum price in MAD", "schema": { "type": "number" } },
          { "name": "min_price", "in": "query", "description": "Minimum price in MAD", "schema": { "type": "number" } },
          { "name": "duration", "in": "query", "description": "Exact duration in days", "schema": { "type": "integer" } },
          { "name": "sort", "in": "query", "description": "Sort order", "schema": { "type": "string", "enum": ["newest", "price", "price_desc", "duration", "popularity"], "default": "newest" } }
        ],
        "responses": {
          "200": {
            "description": "Paginated trip list with metadata",
            "headers": {
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["total", "page", "limit", "total_pages", "trips"],
                  "properties": {
                    "total": { "type": "integer", "description": "Total matching trips" },
                    "page": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "total_pages": { "type": "integer" },
                    "filters_applied": { "type": "object", "additionalProperties": { "type": "string" } },
                    "sort": { "type": "string" },
                    "links": {
                      "type": "object",
                      "properties": {
                        "self": { "type": "string", "format": "uri" },
                        "first": { "type": "string", "format": "uri" },
                        "last": { "type": "string", "format": "uri" },
                        "prev": { "type": "string", "format": "uri" },
                        "next": { "type": "string", "format": "uri" }
                      }
                    },
                    "trips": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Trip" }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": { "Retry-After": { "$ref": "#/components/headers/Retry-After" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "500": {
            "description": "Server error",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/voyages": {
      "get": {
        "operationId": "getVoyagesCatalog",
        "summary": "Server-rendered trip catalog (HTML)",
        "description": "Full server-rendered HTML catalog of all published trips with Schema.org TouristTrip structured data, crawlable pagination, and filters. No JavaScript required.",
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "destination", "in": "query", "schema": { "type": "string" } },
          { "name": "departure", "in": "query", "schema": { "type": "string" } },
          { "name": "category", "in": "query", "schema": { "type": "string" } },
          { "name": "max_price", "in": "query", "schema": { "type": "number" } },
          { "name": "duration", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "HTML catalog page with Schema.org TouristTrip JSON-LD for every trip",
            "content": { "text/html": { "schema": { "type": "string" } } }
          }
        }
      }
    },
    "/developers": {
      "get": {
        "operationId": "getDeveloperPortal",
        "summary": "Developer portal",
        "description": "API documentation, machine-readable resources, integration guides, and quickstart for the YALA-BINA travel marketplace.",
        "responses": {
          "200": {
            "description": "Developer portal page",
            "content": {
              "text/html": {
                "schema": { "type": "string", "description": "HTML developer documentation page" }
              }
            }
          }
        }
      }
    },
    "/api/{path}": {
      "get": {
        "operationId": "apiCatchAll",
        "summary": "API catch-all",
        "description": "Returns a structured JSON 404 for any unrecognized API path, with recovery links.",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "404": {
            "description": "API resource not found — returns structured JSON with recovery links",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "Retry-After": { "$ref": "#/components/headers/Retry-After" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimit-Limit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimit-Remaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  }
}
