{
    "openapi": "3.1.0",
    "info": {
        "title": "OTA News HTTP API",
        "version": "1.0.0",
        "summary": "Read OTA industry news as markdown, or submit content with an API token.",
        "description": "Everything under \"Content\" is public and needs no credentials. The write\nendpoints under /api require a bearer token issued by HolidayHero B.V.\n\nFor editorial automation prefer the MCP server at https://www.ota-news.com/mcp,\ndescribed by https://www.ota-news.com/.well-known/mcp/server-card.json.",
        "contact": {
            "name": "OTA News",
            "email": "hello@ota-news.com",
            "url": "https://www.ota-news.com"
        },
        "termsOfService": "https://www.ota-news.com/terms-of-use"
    },
    "servers": [
        {
            "url": "https://www.ota-news.com",
            "description": "Production"
        }
    ],
    "externalDocs": {
        "description": "Site index for language models",
        "url": "https://www.ota-news.com/llms.txt"
    },
    "tags": [
        {
            "name": "Content",
            "description": "Public, unauthenticated reads."
        },
        {
            "name": "Market data",
            "description": "Share price history for listed OTAs."
        },
        {
            "name": "Ingestion",
            "description": "Token-authenticated writes."
        },
        {
            "name": "Newsletter",
            "description": "Reader subscriptions."
        },
        {
            "name": "Operations",
            "description": "Health and discovery."
        }
    ],
    "paths": {
        "/llms.txt": {
            "get": {
                "tags": [
                    "Content"
                ],
                "operationId": "getSiteIndex",
                "summary": "Full site index as markdown",
                "description": "Every published article, OTA profile, glossary term and page, with links to their markdown representations.",
                "responses": {
                    "200": {
                        "description": "Markdown index",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/{otaSlug}/{articleSlug}.md": {
            "get": {
                "tags": [
                    "Content"
                ],
                "operationId": "getArticleMarkdown",
                "summary": "A single article as markdown",
                "parameters": [
                    {
                        "name": "otaSlug",
                        "in": "path",
                        "required": true,
                        "description": "OTA slug, e.g. booking-com",
                        "schema": {
                            "type": "string"
                        },
                        "example": "booking-com"
                    },
                    {
                        "name": "articleSlug",
                        "in": "path",
                        "required": true,
                        "description": "Article slug",
                        "schema": {
                            "type": "string"
                        },
                        "example": "architecture-of-conversion"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Article metadata followed by the markdown body",
                        "content": {
                            "text/markdown": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    }
                }
            }
        },
        "/search": {
            "get": {
                "tags": [
                    "Content"
                ],
                "operationId": "searchArticles",
                "summary": "Semantic search across articles",
                "description": "Returns HTML by default; send Accept: text/markdown for a compact result list.",
                "parameters": [
                    {
                        "name": "q",
                        "in": "query",
                        "required": true,
                        "description": "Search query. Shorter than three characters returns no results.",
                        "schema": {
                            "type": "string",
                            "minLength": 3
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Search results",
                        "content": {
                            "text/html": {
                                "schema": {
                                    "type": "string"
                                }
                            },
                            "text/markdown": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/feed": {
            "get": {
                "tags": [
                    "Content"
                ],
                "operationId": "getFeed",
                "summary": "RSS feed of recent articles",
                "responses": {
                    "200": {
                        "description": "RSS 2.0 document",
                        "content": {
                            "application/rss+xml": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/sitemap.xml": {
            "get": {
                "tags": [
                    "Content"
                ],
                "operationId": "getSitemap",
                "summary": "XML sitemap of every indexable URL",
                "responses": {
                    "200": {
                        "description": "Sitemap",
                        "content": {
                            "application/xml": {
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/stock/{otaSlug}/history": {
            "get": {
                "tags": [
                    "Market data"
                ],
                "operationId": "getStockHistory",
                "summary": "One year of share prices for a listed OTA, annotated with our coverage",
                "parameters": [
                    {
                        "name": "otaSlug",
                        "in": "path",
                        "required": true,
                        "description": "OTA slug",
                        "schema": {
                            "type": "string"
                        },
                        "example": "booking-com"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Price series and article events",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/StockHistory"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/api/articles": {
            "post": {
                "tags": [
                    "Ingestion"
                ],
                "operationId": "createArticle",
                "summary": "Create an article",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/NewArticle"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Article created",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/CreatedArticle"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/api/images": {
            "post": {
                "tags": [
                    "Ingestion"
                ],
                "operationId": "uploadImage",
                "summary": "Upload an image; it is converted to WebP",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "image"
                                ],
                                "properties": {
                                    "image": {
                                        "type": "string",
                                        "format": "binary",
                                        "description": "JPEG, PNG, GIF or WebP"
                                    },
                                    "directory": {
                                        "type": "string",
                                        "pattern": "^[a-z0-9\\-\\/]+$",
                                        "default": "uploads"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Image stored"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "422": {
                        "$ref": "#/components/responses/ValidationError"
                    }
                }
            }
        },
        "/subscribe": {
            "post": {
                "tags": [
                    "Newsletter"
                ],
                "operationId": "subscribe",
                "summary": "Subscribe an email address to the newsletter",
                "description": "Intended for humans using the site. Agents should not subscribe addresses on a person's behalf without explicit consent.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "email"
                                ],
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Subscription recorded"
                    },
                    "429": {
                        "$ref": "#/components/responses/TooManyRequests"
                    }
                }
            }
        },
        "/up": {
            "get": {
                "tags": [
                    "Operations"
                ],
                "operationId": "healthCheck",
                "summary": "Health check",
                "responses": {
                    "200": {
                        "description": "The application is up"
                    }
                }
            }
        },
        "/.well-known/api-catalog": {
            "get": {
                "tags": [
                    "Operations"
                ],
                "operationId": "getApiCatalog",
                "summary": "RFC 9727 API catalog",
                "responses": {
                    "200": {
                        "description": "Linkset",
                        "content": {
                            "application/linkset+json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "Static API token issued by HolidayHero B.V. For editorial agents use OAuth against the MCP server instead \u2014 see https://www.ota-news.com/auth.md."
            }
        },
        "responses": {
            "NotFound": {
                "description": "No such resource"
            },
            "Unauthorized": {
                "description": "Missing or invalid bearer token"
            },
            "ValidationError": {
                "description": "The payload failed validation"
            },
            "TooManyRequests": {
                "description": "Rate limit exceeded; observe Retry-After"
            }
        },
        "schemas": {
            "NewArticle": {
                "type": "object",
                "required": [
                    "ota_id",
                    "author_id",
                    "title",
                    "body"
                ],
                "properties": {
                    "ota_id": {
                        "type": "integer"
                    },
                    "author_id": {
                        "type": "integer"
                    },
                    "category_id": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "title": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "slug": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "Derived from the title when omitted"
                    },
                    "excerpt": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "body": {
                        "type": "string",
                        "description": "Markdown"
                    },
                    "featured_image": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255
                    },
                    "featured_image_caption": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255
                    },
                    "reading_time_minutes": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "minimum": 0
                    },
                    "is_published": {
                        "type": "boolean",
                        "default": false
                    },
                    "is_featured": {
                        "type": "boolean",
                        "default": false
                    },
                    "published_at": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "meta_title": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 255
                    },
                    "meta_description": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                }
            },
            "CreatedArticle": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string"
                    },
                    "article": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "slug": {
                                "type": "string"
                            },
                            "url": {
                                "type": "string",
                                "format": "uri"
                            }
                        }
                    }
                }
            },
            "StockHistory": {
                "type": "object",
                "properties": {
                    "ticker": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "name": {
                        "type": "string"
                    },
                    "currency": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "primary_color": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "prices": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "date": {
                                    "type": "string",
                                    "format": "date"
                                },
                                "close": {
                                    "type": "number"
                                }
                            }
                        }
                    },
                    "events": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "date": {
                                    "type": "string",
                                    "format": "date"
                                },
                                "title": {
                                    "type": "string"
                                },
                                "excerpt": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "image": {
                                    "type": [
                                        "string",
                                        "null"
                                    ]
                                },
                                "url": {
                                    "type": "string",
                                    "format": "uri"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}