{
  "openapi": "3.1.0",
  "info": {
    "title": "WorldFeedMonitor API",
    "version": "1.0.0",
    "summary": "Global, AI-enriched news feed API.",
    "description": "The **WorldFeedMonitor API** streams structured, AI-enriched news from newsrooms around the world as clean JSON.\n\nFeeds are ingested from thousands of RSS sources, deduplicated, categorized, and indexed for search. A single search endpoint lets you filter by country, continent, region, language, category, keyword and date, while a set of metadata endpoints expose the reference data (countries, sources, languages, categories) you use to build those filters.\n\n## Base URL\n\nThe API is served from its own host:\n\n```\nhttps://api.worldfeedmonitor.com/v1\n```\n\nThat host serves the API and nothing else, and it is the only address the API answers on.\n\n## Authentication\n\nAll endpoints except account creation require **HTTP Basic** authentication. Your API **key** is the username and your API **secret** is the password. Credentials are issued from your dashboard.\n\n```\nAuthorization: Basic base64(api_key:api_secret)\n```\n\nWith `curl`, pass them via `-u`:\n\n```bash\ncurl -u \"YOUR_API_KEY:YOUR_API_SECRET\" \\\n  \"https://api.worldfeedmonitor.com/v1/feeds/search?country=USA\"\n```\n\n## Pagination\n\nList responses return 25 items per page. Use the `page` query parameter to page through results and read the `meta` block (`total_pages`, `has_next`, `has_previous`) to navigate.\n\n## Your plan\n\nEvery plan carries an allowance, and searches are scoped to the countries the plan includes.\n\n- **Requests** are limited per minute, per day and per month, and every endpoint counts against them — metadata lookups as well as searches.\n- **Countries** must be chosen in your dashboard when the plan limits them. A search naming a country outside that selection is refused with `403`, and a search naming none is restricted to the selection rather than the whole index.\n- **Results** are reachable only as deep as the plan allows. `meta.total_items` reports what you can reach, not what exists, and a page beyond that limit is refused with `422`.\n\nRead the current figures from your dashboard; they differ per plan.\n\n## Errors\n\nThe API uses conventional HTTP status codes.\n\n| Code | Meaning |\n|------|---------|\n| `401` | Missing, malformed or invalid credentials. |\n| `403` | Valid credentials, but the account is not active or the plan does not include what was asked for. Retrying unchanged will not succeed. |\n| `422` | Validation error, with a per-field `errors` object. |\n| `429` | A plan allowance is spent. `window` names which one; `Retry-After` gives the seconds until it resets. |",
    "contact": {
      "name": "WorldFeedMonitor",
      "url": "https://worldfeedmonitor.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://worldfeedmonitor.com/terms"
    },
    "x-logo": {
      "url": "/wfm-logo.svg",
      "altText": "WorldFeedMonitor",
      "href": "https://worldfeedmonitor.com"
    }
  },
  "servers": [
    {
      "url": "https://api.worldfeedmonitor.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {
      "name": "Feeds",
      "description": "Search the global news index."
    },
    {
      "name": "Metadata",
      "description": "Reference data used to build search filters — countries, sources, continents, regions, languages and categories. All values are cached and refreshed daily."
    }
  ],
  "paths": {
    "/feeds/search": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "summary": "Search feeds",
        "operationId": "searchFeeds",
        "description": "Search the global, AI-enriched news index. All filters are optional and combine with AND semantics. When omitted, `from_date` defaults to yesterday and `to_date` defaults to today, so an unfiltered call returns the most recent 24 hours of news.\n\nTwo limits shape the response and are not visible in the results themselves. The search is always restricted to the countries your plan includes, whether or not `country` is given — so an unfiltered call covers your selection, not the whole index. And `meta.total_items` is capped at the deepest result your plan can reach, so it reports what is available to you rather than what matched.",
        "parameters": [
          {
            "name": "keywords",
            "in": "query",
            "required": false,
            "description": "Full-text query matched against article title and content. One or two words must all appear; for longer queries, three in four. The exact phrase ranks higher.",
            "schema": {
              "type": "string"
            },
            "example": "election results"
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Matched against the article title alone, for when a keyword is too broad.",
            "schema": {
              "type": "string"
            },
            "example": "budget"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Country, as an ISO 3166-1 alpha-2 **or** alpha-3 code — both are accepted, in any case. Discover values via `GET /metadata/countries`. Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "USA",
              "ITA"
            ]
          },
          {
            "name": "continent",
            "in": "query",
            "required": false,
            "description": "Continent key or uuid. Discover values via `GET /metadata/continents`. Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "europe"
            ]
          },
          {
            "name": "continent_region",
            "in": "query",
            "required": false,
            "description": "Continent region key or uuid. Discover values via `GET /metadata/continent-regions`. Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "southern_europe"
            ]
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "description": "ISO 639-1 language code. Discover values via `GET /metadata/languages` (`code`). Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "en",
              "it"
            ]
          },
          {
            "name": "macro_category",
            "in": "query",
            "required": false,
            "description": "Macro category key or uuid. Discover values via `GET /metadata/macro-categories`. Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "sports"
            ]
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "The same filter as `macro_category`, under the name this endpoint has always used. Kept for existing callers. Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "sports"
            ]
          },
          {
            "name": "publisher_category",
            "in": "query",
            "required": false,
            "description": "The publisher's own category, key or uuid. Discover values via `GET /metadata/categories`. Repeat the parameter or separate values with commas to match any of several.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "calcio"
            ]
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "Narrow to one or more publishers. A source key (preferred), a uuid, or an exact source name. Names are not unique — several publishers share one — so a name matches an arbitrary one of them; the key from /metadata/sources identifies exactly one.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": "rnz-pacific-mh"
          },
          {
            "name": "source_type",
            "in": "query",
            "description": "What the publisher **is** — key or uuid. Discover values via `GET /metadata/source-types`. Repeatable; several values match any of them. Independent of `sector`: giving both narrows to publishers that are one **and** cover the other.\n\nSources added before classification, and the few the classifier could not place, carry a null `source.type` and match no value of this filter.",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true,
            "example": "regulator"
          },
          {
            "name": "sector",
            "in": "query",
            "description": "What the publisher is **about** — key or uuid. Discover values via `GET /metadata/sectors`, or `GET /metadata/source-types/{source_type}/sectors` for the ones a given type can take. Repeatable; several values match any of them.\n\n`general` is the answer for a publisher with no subject speciality, which is most national news outlets — it is a value to filter on, not a missing one.",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true,
            "example": "pharmaceuticals"
          },
          {
            "name": "has_image",
            "in": "query",
            "required": false,
            "description": "True returns only articles that carry an image; false only those that do not.",
            "schema": {
              "type": "boolean"
            },
            "example": true
          },
          {
            "name": "from_date",
            "in": "query",
            "required": false,
            "description": "Lower bound (inclusive) on publish date, `YYYY-MM-DD`. Defaults to yesterday.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-07-01"
          },
          {
            "name": "to_date",
            "in": "query",
            "required": false,
            "description": "Upper bound (inclusive) on publish date, `YYYY-MM-DD`. Defaults to today.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-07-19"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Order of the results. `relevance` puts the best match first, newest first among equals; `desc` and `asc` order by publication date. Defaults to `relevance` when `keywords` or `title` is given, otherwise `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "relevance",
                "desc",
                "asc"
              ]
            },
            "example": "relevance"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number. 25 items per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "A page of matching feed items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedSearchResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/categories": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List categories",
        "operationId": "listCategories",
        "description": "Raw categories as published by the source feeds, which number in the tens of thousands across languages — this endpoint is paginated and expects to be narrowed.\n\nFor a small, stable taxonomy to build against, use `GET /metadata/macro-categories` instead: twenty-one entries that do not grow with the feed catalogue.",
        "parameters": [
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Country, as an ISO 3166-1 alpha-2 **or** alpha-3 code — both are accepted, in any case. Restricts to categories used by feeds from that country.",
            "schema": {
              "type": "string",
              "example": "IT"
            }
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "description": "Language code. Categories are held per language, so the same word in two languages is two entries.",
            "schema": {
              "type": "string",
              "example": "en"
            }
          },
          {
            "name": "macro_category",
            "in": "query",
            "required": false,
            "description": "Restrict to categories mapped to this macro category. Accepts its `key` or its `uuid`. A value matching neither is refused rather than ignored.",
            "schema": {
              "type": "string",
              "example": "sport"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matches categories whose name starts with this term.",
            "schema": {
              "type": "string",
              "maxLength": 100,
              "example": "sport"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of categories.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/continent-regions/{continent_region}": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getContinentRegion",
        "summary": "Continent region",
        "description": "One continent region, addressed by its key or uuid, in any case.\n\nA single record carries no `meta` block — pagination has nothing to say about one thing.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "continent_region",
            "in": "path",
            "required": true,
            "description": "The continent region key or uuid.",
            "schema": {
              "type": "string"
            },
            "example": "southern_europe"
          }
        ],
        "responses": {
          "200": {
            "description": "The continent region.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uuid": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No continent region by that key or uuid."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/metadata/continents": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List continents",
        "operationId": "listContinents",
        "description": "All continents. Use `key` as the `continent` filter on `/feeds/search`.",
        "responses": {
          "200": {
            "description": "Every continent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContinentListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/continents/{continent_key}/regions": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List regions of a continent",
        "operationId": "listContinentRegions",
        "description": "Sub-regions belonging to a continent. Use `key` as the `continent_region` filter on `/feeds/search`.",
        "parameters": [
          {
            "name": "continent_key",
            "in": "path",
            "required": true,
            "description": "Continent key. Discover valid values via `GET /metadata/continents` (`key`).",
            "schema": {
              "type": "string"
            },
            "example": "europe"
          }
        ],
        "responses": {
          "200": {
            "description": "The continent's regions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegionListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/continents/{continent}": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getContinent",
        "summary": "Continent",
        "description": "One continent, addressed by its key or uuid, in any case.\n\nA single record carries no `meta` block — pagination has nothing to say about one thing.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "continent",
            "in": "path",
            "required": true,
            "description": "The continent key or uuid.",
            "schema": {
              "type": "string"
            },
            "example": "europe"
          }
        ],
        "responses": {
          "200": {
            "description": "The continent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uuid": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No continent by that key or uuid."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/metadata/countries": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List countries",
        "operationId": "listCountries",
        "description": "All countries with ISO codes. Use `iso_3` as the `country` filter on `/feeds/search`.",
        "responses": {
          "200": {
            "description": "Every country.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CountryListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/countries/{country_code}/sources": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List sources",
        "operationId": "listCountrySources",
        "description": "News sources published from one country.\n\nThe country goes where it belongs: under `/metadata/countries`, with `sources` last, because the last collection in the path is what comes back.",
        "parameters": [
          {
            "name": "country_code",
            "in": "path",
            "required": false,
            "description": "Country, as an ISO 3166-1 alpha-2 **or** alpha-3 code — both are accepted, in any case. Discover valid values via `GET /metadata/countries` (`iso_2` or `iso_3`).",
            "schema": {
              "type": "string"
            },
            "example": "US"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of sources.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/languages": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List languages",
        "operationId": "listLanguages",
        "description": "All languages. Use `code` as the `language` filter on `/feeds/search`.",
        "responses": {
          "200": {
            "description": "Every language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LanguageListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/macro-categories": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List macro-categories",
        "operationId": "listMacroCategories",
        "description": "Top-level AI categories. Use `key` as the `category` filter on `/feeds/search`.",
        "responses": {
          "200": {
            "description": "The macro taxonomy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MacroCategoryListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/macro-categories/{macro_category}": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getMacroCategory",
        "summary": "Macro category",
        "description": "One macro category, addressed by its key or uuid, in any case.\n\nA single record carries no `meta` block — pagination has nothing to say about one thing.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "macro_category",
            "in": "path",
            "required": true,
            "description": "The macro category key or uuid.",
            "schema": {
              "type": "string"
            },
            "example": "sports"
          }
        ],
        "responses": {
          "200": {
            "description": "The macro category.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uuid": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No macro category by that key or uuid."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/metadata/macro-categories/{macro_category}/categories": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "Categories within a macro category",
        "operationId": "listCategoriesByMacroCategory",
        "description": "The categories listing scoped to one macro category, which is taken from the path by `key` or `uuid`. Every other filter still applies, so this is the same listing narrowed rather than a different one.\n\nUse it to walk the taxonomy: `GET /metadata/macro-categories` for the twenty-one stable entries, then this for the raw feed categories mapped beneath one of them.",
        "parameters": [
          {
            "name": "macro_category",
            "in": "path",
            "required": true,
            "description": "The macro category, by `key` or `uuid`.",
            "schema": {
              "type": "string",
              "example": "sport"
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Country, as an ISO 3166-1 alpha-2 **or** alpha-3 code — both are accepted, in any case. Restricts to categories used by feeds from that country.",
            "schema": {
              "type": "string",
              "example": "IT"
            }
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "description": "Language code. Categories are held per language, so the same word in two languages is two entries.",
            "schema": {
              "type": "string",
              "example": "en"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Matches categories whose name starts with this term.",
            "schema": {
              "type": "string",
              "maxLength": 100,
              "example": "sport"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of categories.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "404": {
            "description": "No macro category matches the given key or uuid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/sectors": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List sectors",
        "operationId": "listSectors",
        "description": "What a publisher is **about** — thirty-four values, fixed. Use `key` as the `sector` filter on `/feeds/search`. `general` is the value for a publisher with no subject speciality and is the most common answer, not a missing one.",
        "responses": {
          "200": {
            "description": "List sectors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SectorListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/sectors/{sector}": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getSector",
        "summary": "Sector",
        "description": "One sector, addressed by its key or uuid, in any case.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "sector",
            "in": "path",
            "required": true,
            "description": "The sector key or uuid.",
            "schema": {
              "type": "string"
            },
            "example": "pharmaceuticals"
          }
        ],
        "responses": {
          "200": {
            "description": "Sector.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DescribedEntity"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No macro category by that key or uuid."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/metadata/source-types": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "List source types",
        "operationId": "listSourceTypes",
        "description": "What a publisher **is** — eight values, fixed. Use `key` as the `source_type` filter on `/feeds/search`. Each entry carries the sentence that defines it, which is the same definition the classifier works from.",
        "responses": {
          "200": {
            "description": "List source types.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceTypeListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/source-types/{source_type}": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getSourceType",
        "summary": "Source type",
        "description": "One source type, addressed by its key or uuid, in any case.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "source_type",
            "in": "path",
            "required": true,
            "description": "The source type key or uuid.",
            "schema": {
              "type": "string"
            },
            "example": "regulator"
          }
        ],
        "responses": {
          "200": {
            "description": "Source type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DescribedEntity"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No macro category by that key or uuid."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/metadata/source-types/{source_type}/sectors": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "Sectors a source type can take",
        "operationId": "listSectorsForSourceType",
        "description": "The sectors this kind of publisher can be about. The pairing is part of the taxonomy rather than a suggestion: `source_type=government&sector=semiconductors` matches nothing because no such publisher is classifiable, and this is how to find that out without guessing.",
        "responses": {
          "200": {
            "description": "Sectors a source type can take.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SectorListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "source_type",
            "in": "path",
            "required": true,
            "description": "The source type key or uuid.",
            "schema": {
              "type": "string"
            },
            "example": "company"
          }
        ]
      }
    },
    "/metadata/sources": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "summary": "Sources",
        "operationId": "listSources",
        "description": "Every news source, paginated.\n\nFor the sources of one country, use `GET /metadata/countries/{country_code}/sources`.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Results per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of sources.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Authenticated, but not permitted. Either the account is not active — a signup that never completed payment, or one deactivated after repeated payment failures — or the search asks for a country the plan does not include. Retrying unchanged will not succeed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "description": "The plan's allowance for one of its windows is spent. `window` names which one and `retry_after` gives the seconds until it resets.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds until the window resets."
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "The allowance for the window that ran out."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Calls left in the current minute."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          }
        }
      }
    },
    "/metadata/sources/{source}": {
      "get": {
        "tags": [
          "Metadata"
        ],
        "operationId": "getSource",
        "summary": "Source",
        "description": "One news source, addressed by its uuid or its exact name.\n\nThe country it belongs to comes with it: a caller arriving from a search result has an identifier and little else, and the country is usually what they need next.\n\nA single record carries no `meta` block.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "parameters": [
          {
            "name": "source",
            "in": "path",
            "required": true,
            "description": "The source key or uuid, either of which identifies exactly one source. Its exact name is still accepted, but names are not unique and an ambiguous one returns an arbitrary match.",
            "schema": {
              "type": "string"
            },
            "example": "rnz-pacific-mh"
          }
        ],
        "responses": {
          "200": {
            "description": "The source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uuid": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string",
                          "description": "A readable identifier, unique across the catalogue. Source names are not unique — several publishers share one — so this or the uuid is what addresses exactly one source.",
                          "example": "rnz-pacific-mh"
                        },
                        "name": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        },
                        "type": {
                          "type": "object",
                          "nullable": true,
                          "description": "What the publisher is. Null when the source has not been classified yet.",
                          "properties": {
                            "uuid": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "key": {
                              "type": "string",
                              "example": "media"
                            },
                            "name": {
                              "type": "string",
                              "example": "Media"
                            }
                          }
                        },
                        "sector": {
                          "type": "object",
                          "nullable": true,
                          "description": "What the publisher is about. Null when the source has not been classified yet.",
                          "properties": {
                            "uuid": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "key": {
                              "type": "string",
                              "example": "general"
                            },
                            "name": {
                              "type": "string",
                              "example": "General"
                            }
                          }
                        },
                        "country": {
                          "type": "object",
                          "nullable": true,
                          "properties": {
                            "uuid": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "iso_2": {
                              "type": "string"
                            },
                            "iso_3": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "No source by that uuid or name."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/stats": {
      "get": {
        "tags": [
          "Feeds"
        ],
        "operationId": "getStats",
        "summary": "Corpus statistics",
        "description": "How much is in the index, how fast it is arriving, and how recent the newest article is.\n\nFigures cover the whole corpus, not the slice your plan reaches, and are cached briefly — they are counts, not news, and do not change meaningfully second to second.\n\nThis call counts against your plan allowance like any other.",
        "security": [
          {
            "basicAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The corpus as it stands.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "corpus": {
                      "type": "object",
                      "properties": {
                        "articles": {
                          "type": "integer",
                          "description": "Articles held in the index."
                        },
                        "sources": {
                          "type": "integer",
                          "description": "Publishers being read."
                        },
                        "feeds": {
                          "type": "integer",
                          "description": "Feed addresses being read across those publishers."
                        },
                        "countries_covered": {
                          "type": "integer"
                        },
                        "languages": {
                          "type": "integer"
                        }
                      }
                    },
                    "ingested": {
                      "type": "object",
                      "description": "New arrivals: one finished day, and each rolling window.",
                      "properties": {
                        "yesterday": {
                          "type": "object",
                          "description": "The last finished calendar day, on Europe/Amsterdam time. The figure the homepage prints; `date` says which day it is.",
                          "properties": {
                            "date": {
                              "type": "string",
                              "format": "date"
                            },
                            "articles": {
                              "type": "integer"
                            },
                            "sources_added": {
                              "type": "integer"
                            },
                            "feeds_added": {
                              "type": "integer"
                            }
                          }
                        },
                        "last_24h": {
                          "type": "object",
                          "properties": {
                            "articles": {
                              "type": "integer"
                            },
                            "sources_added": {
                              "type": "integer"
                            },
                            "feeds_added": {
                              "type": "integer"
                            }
                          }
                        },
                        "last_7d": {
                          "type": "object",
                          "properties": {
                            "articles": {
                              "type": "integer"
                            },
                            "sources_added": {
                              "type": "integer"
                            },
                            "feeds_added": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    },
                    "freshness": {
                      "type": "object",
                      "properties": {
                        "newest_article_published_at": {
                          "type": "string",
                          "nullable": true,
                          "example": "2026-09-03 09:41:02",
                          "description": "When the most recent article says it was published."
                        },
                        "newest_article_indexed_at": {
                          "type": "string",
                          "nullable": true,
                          "example": "2026-09-03 09:47:15",
                          "description": "When we read it. The gap between the two is the ingestion delay."
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic auth. Username = API key, password = API secret."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, malformed, or invalid credentials.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "summary": "No Authorization header",
                "value": {
                  "message": "Missing authorization header"
                }
              },
              "malformed": {
                "summary": "Malformed header",
                "value": {
                  "message": "Invalid authorization header"
                }
              },
              "invalid": {
                "summary": "Wrong key/secret",
                "value": {
                  "message": "Invalid credentials"
                }
              }
            }
          }
        }
      },
      "ValidationError": {
        "description": "One or more parameters failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationError"
            }
          }
        }
      }
    },
    "schemas": {
      "FeedSearchResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/SearchMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Feed"
            }
          }
        }
      },
      "SearchMeta": {
        "type": "object",
        "description": "Pagination and echo of the applied filters.",
        "properties": {
          "total_pages": {
            "type": "integer",
            "example": 12
          },
          "items_per_page": {
            "type": "integer",
            "example": 25
          },
          "total_items": {
            "type": "integer",
            "example": 297
          },
          "current_page": {
            "type": "integer",
            "example": 1
          },
          "has_previous": {
            "type": "boolean",
            "example": false
          },
          "has_next": {
            "type": "boolean",
            "example": true
          },
          "applied_filters": {
            "type": "object",
            "description": "What the search was understood to ask for, after every value was resolved. Filters you did not use are absent rather than null.",
            "properties": {
              "macro_category": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Macro category keys, resolved from whichever form was sent."
              },
              "publisher_category": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The publisher's own category keys."
              },
              "language": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "ISO 639-1 language codes."
              },
              "country": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "ISO 3166-1 alpha-3 codes, whichever form was sent."
              },
              "continent": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Continent keys."
              },
              "continent_region": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Continent region keys."
              },
              "source": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Source uuids."
              },
              "keywords": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "has_image": {
                "type": "boolean"
              },
              "from_date": {
                "type": "string",
                "format": "date"
              },
              "to_date": {
                "type": "string",
                "format": "date"
              },
              "sort": {
                "type": "string",
                "enum": [
                  "asc",
                  "desc"
                ]
              }
            }
          }
        }
      },
      "Feed": {
        "type": "object",
        "description": "A single AI-enriched news article.",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid",
            "example": "0f3d9c8a-6b1e-4a2f-9c11-2d7e5b8a1c34"
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          },
          "country": {
            "type": "object",
            "properties": {
              "uuid": {
                "type": "string",
                "format": "uuid"
              },
              "iso_2": {
                "type": "string",
                "example": "US"
              },
              "iso_3": {
                "type": "string",
                "example": "USA"
              },
              "name": {
                "type": "string",
                "example": "United States"
              }
            }
          },
          "continent": {
            "$ref": "#/components/schemas/KeyedEntity"
          },
          "continent_region": {
            "$ref": "#/components/schemas/KeyedEntity"
          },
          "language": {
            "type": "object",
            "properties": {
              "uuid": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string",
                "example": "English"
              },
              "local_name": {
                "type": "string",
                "example": "English"
              },
              "code": {
                "type": "string",
                "example": "en"
              },
              "locale": {
                "type": "string",
                "example": "en_us"
              }
            }
          },
          "title": {
            "type": "string",
            "example": "Central bank holds rates steady amid cooling inflation"
          },
          "author": {
            "type": "string",
            "nullable": true,
            "description": "Who the feed credits for the article. Null when the feed names no one, which is most of the time.",
            "example": "Jane Okonjo"
          },
          "content": {
            "type": "string",
            "description": "Plain-text article summary (HTML stripped).",
            "example": "The central bank left its benchmark rate unchanged on Wednesday..."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://example-news.com/articles/central-bank-holds"
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            }
          },
          "macro_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KeyedEntity"
            }
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "example": "https://example-news.com/img/hero.jpg"
          },
          "published_at": {
            "type": "string",
            "nullable": true,
            "example": "2026-07-19 14:32:07",
            "description": "Publish timestamp, `Y-m-d H:i:s`. Null when the feed published no date — no date is invented for it."
          },
          "published_timezone": {
            "type": "string",
            "nullable": true,
            "example": "America/New_York",
            "description": "Timezone the item was published in. Null when the feed published no date."
          }
        }
      },
      "Source": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string",
            "description": "A readable identifier, unique across the catalogue. Source names are not unique — several publishers share one — so this or the uuid is what addresses exactly one source.",
            "example": "rnz-pacific-mh"
          },
          "name": {
            "type": "string",
            "example": "Example News"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://example-news.com"
          },
          "type": {
            "type": "object",
            "nullable": true,
            "description": "What the publisher is. Null when the source has not been classified yet.",
            "properties": {
              "uuid": {
                "type": "string",
                "format": "uuid"
              },
              "key": {
                "type": "string",
                "example": "regulator"
              },
              "name": {
                "type": "string",
                "example": "Regulator"
              }
            }
          },
          "sector": {
            "type": "object",
            "nullable": true,
            "description": "What the publisher is about. Null when the source has not been classified yet.",
            "properties": {
              "uuid": {
                "type": "string",
                "format": "uuid"
              },
              "key": {
                "type": "string",
                "example": "pharmaceuticals"
              },
              "name": {
                "type": "string",
                "example": "Pharmaceuticals"
              }
            }
          }
        }
      },
      "Category": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "original_name": {
            "type": "string",
            "example": "Monetary Policy"
          },
          "key": {
            "type": "string",
            "example": "monetary_policy"
          }
        }
      },
      "KeyedEntity": {
        "type": "object",
        "description": "A reference entity identified by a stable `key`.",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Europe"
          },
          "key": {
            "type": "string",
            "example": "europe"
          }
        }
      },
      "CountryMeta": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "United States"
          },
          "iso_2": {
            "type": "string",
            "example": "US"
          },
          "iso_3": {
            "type": "string",
            "example": "USA"
          }
        }
      },
      "LanguageMeta": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "English"
          },
          "local_name": {
            "type": "string",
            "example": "English"
          },
          "code": {
            "type": "string",
            "example": "en"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "example": "Invalid credentials"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "example": "The country field is required."
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": {
              "country": [
                "The selected country is invalid."
              ]
            }
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "description": "The plan's request allowance for one of its windows has been used up.",
        "properties": {
          "message": {
            "type": "string",
            "example": "You have used your plan allowance of 30 requests per minute."
          },
          "window": {
            "type": "string",
            "enum": [
              "minute",
              "day",
              "month"
            ],
            "description": "Which allowance ran out."
          },
          "limit": {
            "type": "integer",
            "example": 30
          },
          "retry_after": {
            "type": "integer",
            "description": "Seconds until that window resets.",
            "example": 24
          }
        }
      },
      "ForbiddenError": {
        "type": "object",
        "description": "The credentials are valid but the account or plan does not permit the request.",
        "properties": {
          "message": {
            "type": "string",
            "example": "Your plan does not include ESP. Your countries: ITA, FRA."
          },
          "status": {
            "type": "string",
            "description": "Present when the account itself is not active.",
            "example": "inactive"
          }
        }
      },
      "CategoryListResponse": {
        "type": "object",
        "description": "A page of categories.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KeyedEntity"
            }
          }
        }
      },
      "ListMeta": {
        "type": "object",
        "description": "Describes the page returned. Collections that are not paginated report a single page covering the whole set.",
        "properties": {
          "total_items": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "items_per_page": {
            "type": "integer"
          },
          "current_page": {
            "type": "integer"
          },
          "has_previous": {
            "type": "boolean"
          },
          "has_next": {
            "type": "boolean"
          },
          "applied_filters": {
            "type": "object",
            "description": "The filters in effect, echoed back."
          }
        }
      },
      "CountryListResponse": {
        "type": "object",
        "description": "Every country, in one page.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CountryMeta"
            }
          }
        }
      },
      "LanguageListResponse": {
        "type": "object",
        "description": "Every language, in one page.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LanguageMeta"
            }
          }
        }
      },
      "ContinentListResponse": {
        "type": "object",
        "description": "Every continent, in one page.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KeyedEntity"
            }
          }
        }
      },
      "RegionListResponse": {
        "type": "object",
        "description": "The continent's regions, in one page.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KeyedEntity"
            }
          }
        }
      },
      "MacroCategoryListResponse": {
        "type": "object",
        "description": "The macro taxonomy, in one page.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KeyedEntity"
            }
          }
        }
      },
      "SourceListResponse": {
        "type": "object",
        "description": "A page of sources.",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Source"
            }
          }
        }
      },
      "DescribedEntity": {
        "type": "object",
        "description": "A vocabulary entry, with the sentence that defines it.",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string",
            "example": "regulator"
          },
          "name": {
            "type": "string",
            "example": "Regulator"
          },
          "description": {
            "type": "string",
            "example": "A supervisory body with statutory power over a market."
          }
        }
      },
      "SourceTypeListResponse": {
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DescribedEntity"
            }
          }
        }
      },
      "SectorListResponse": {
        "type": "object",
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/ListMeta"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DescribedEntity"
            }
          }
        }
      }
    }
  }
}
