openapi: 3.1.0 info: title: NewsService API version: 1.0.0 security: - WorldMonitorKey: [] - ApiKeyHeader: [] servers: - url: https://api.worldmonitor.app paths: /api/news/v1/summarize-article: post: parameters: - name: Idempotency-Key in: header description: Optional client-generated idempotency key. Retrying a POST with the same key and an identical request body replays the original response (only the status, body, and Content-Type are reproduced) instead of re-executing; reusing the key with a different body is rejected with 422. For mutations this avoids duplicating the side effect, while for batch-read POSTs it replays a cached snapshot that can be up to 24 hours stale. Keys are scoped per authenticated caller (falling back to the source IP for unauthenticated endpoints) and retained for 24 hours. required: false example: "4f8b9c2e-1a3d-4b6f-8e0a-2c5d7f9b1e34" schema: type: string minLength: 1 maxLength: 255 pattern: "^[\\x21-\\x7E]{1,255}$" tags: - NewsService summary: SummarizeArticle description: Generate an article summary with language-model provider selection and fallback support, returning the chosen provider, model and reported processing errors. operationId: SummarizeArticle requestBody: content: application/json: example: "bodies": - "example" "geoContext": "example" "headlines": - "example" "lang": "en" "mode": "brief" "provider": "openrouter" schema: $ref: '#/components/schemas/SummarizeArticleRequest' required: true responses: "200": description: Successful response headers: Idempotency-Key: schema: type: string description: The idempotency key echoed from the request. Present only when the client opted into idempotency. Idempotent-Replayed: schema: type: boolean description: true when this response was replayed from an earlier request with the same key, false on the first (original) request. Present only when the client opted into idempotency. content: application/json: example: "error": "example" "errorType": "all" "fallback": true "model": "example" "provider": "openrouter" schema: $ref: '#/components/schemas/SummarizeArticleResponse' "400": description: Validation error, invalid Idempotency-Key header, or malformed JSON request body content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - type: object required: - error - message properties: error: type: string message: type: string - $ref: '#/components/schemas/InvalidRequestBodyError' "409": description: A request with this Idempotency-Key is still being processed headers: Idempotency-Key: schema: type: string description: The idempotency key supplied by the client. Retry-After: schema: type: string description: Seconds to wait before retrying the in-flight request. content: application/json: schema: type: object required: - error - message properties: error: type: string message: type: string "422": description: The Idempotency-Key was already used with a different request body headers: Idempotency-Key: schema: type: string description: The idempotency key supplied by the client. content: application/json: schema: type: object required: - error - message properties: error: type: string message: type: string "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: API access requires an active subscription (the API key's subscription is inactive or expired). headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/summarize-article-cache: get: tags: - NewsService summary: GetSummarizeArticleCache description: GetSummarizeArticleCache returns an AI summary already stored by SummarizeArticle for a deterministic cache key. Hits are CDN-cacheable; misses are not. operationId: GetSummarizeArticleCache parameters: - name: cache_key in: query description: Deterministic cache key computed by buildSummaryCacheKey(). required: false example: "summary:v1:example-cache" schema: type: string pattern: '^summary:v\d+:[a-z0-9:_-]{3,120}$' - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "error": "example" "errorType": "all" "fallback": true "model": "example" "provider": "openrouter" schema: $ref: '#/components/schemas/SummarizeArticleResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: API access requires an active subscription (the API key's subscription is inactive or expired). headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-feed-digest: get: tags: - NewsService summary: ListFeedDigest description: ListFeedDigest returns RSS headlines grouped by category for a site variant (full, tech, finance, happy, commodity, energy) and language, with coverage state. operationId: ListFeedDigest parameters: - name: variant in: query description: 'Digest variant: full, tech, finance, happy, commodity, energy. Unsupported variants fall back to full.' required: false example: "example" schema: type: string - name: lang in: query description: ISO 639-1 language code of a supported digest language (en, fr, ar, etc.). Unsupported codes are rejected with 400. required: false example: "en" schema: type: string - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "categories": "exampleKey": "items": - "corroborationCount": 42 "credibilityScore": 42 "importanceScore": 42 "isAlert": true "link": "https://example.com/worldmonitor" "source": "example" "title": "example" "coverage": "attemptedAt": "2026-01-15T12:00:00Z" "categoryCompleted": 1 "categoryStates": "exampleKey": "example" "categoryTotal": 1 "droppedCategoryCap": 1 "feedStatuses": "exampleKey": "example" "generatedAt": "2026-01-15T12:00:00Z" schema: $ref: '#/components/schemas/ListFeedDigestResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: API access requires an active subscription (the API key's subscription is inactive or expired). headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-country-headlines: get: tags: - NewsService summary: ListCountryHeadlines description: ListCountryHeadlines reads fresh country reporting from existing RSS caches without fetching publishers or applying dashboard category caps. operationId: ListCountryHeadlines parameters: - name: country_codes in: query description: ISO 3166-1 alpha-2 codes. Repeated query parameter, at most 250 countries. required: true style: form explode: true example: - "US" schema: type: array maxItems: 250 minItems: 1 items: type: string - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "countries": "exampleKey": "items": - "link": "https://example.com/worldmonitor" "publishedAt": 1717200000000 "source": "example" "title": "example" "feedCached": 1 "feedTotal": 1 "readAt": "2026-01-15T12:00:00Z" "state": "example" schema: $ref: '#/components/schemas/ListCountryHeadlinesResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: API access requires an active subscription (the API key's subscription is inactive or expired). headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-media-perspective: get: tags: - NewsService summary: ListMediaPerspective description: ListMediaPerspective groups headlines about one country by outlet home country and audience. PRO-gated. Requires entitlement tier >= 1. operationId: ListMediaPerspective security: - WorldMonitorKey: [] - ApiKeyHeader: [] - BearerAuth: [] parameters: - name: subject_country in: query description: ISO2. required: true example: "SA" schema: type: string - name: outlet_countries in: query description: ISO2, at most 30. required: false style: form explode: true example: - "US" schema: type: array items: type: string - name: audience in: query description: domestic, international or diaspora. required: false example: "domestic" schema: type: string - name: window_hours in: query description: 24, 48, 72 or 96. required: false "schema": "type": "integer" "format": "int32" "enum": - 0 - 24 - 48 - 72 - 96 example: 0 - name: outlet_regions in: query description: UN M49 region slugs, e.g. northern-europe. required: false style: form explode: true example: - "northern-europe" schema: type: array items: type: string - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "feedCached": 812 "feedTotal": 812 "groups": - "audience": "international" "framing": "critical": 0 "favorable": 0 "hostile": 0 "method": "" "model": "" "neutral": 0 "promptVersion": "" "unlabeled": 1 "headlineCount": 1 "headlines": - "englishTitle": "Saudi Arabia hosts ceasefire talks" "framing": "" "link": "https://www.bbc.co.uk/news/articles/example" "outlet": "BBC World" "ownershipClass": "public-service" "propagandaRisk": "low" "publishedAt": 1717200000000 "publisherFamily": "bbc" "stateAffiliated": "" "tier": 1 "title": "Saudi Arabia hosts ceasefire talks" "titleLanguage": "en" "homeCountry": "GB" "outletCount": 1 "noCoverageCountries": [] "readAt": "2026-10-27T12:00:00.000Z" "state": "complete" "subjectCountry": "SA" "unattributedCount": 0 "windowHours": 72 schema: $ref: '#/components/schemas/ListMediaPerspectiveResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: PRO entitlement access denied. headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-news-pool: get: tags: - NewsService summary: ListNewsPool description: 'ListNewsPool merges cached news for several editions and content languages, one item per article URL: English from the warmed digest cells, other languages from their feeds'' cached parses. Reads cache only; never builds a digest or fetches a feed. PRO-gated. Requires entitlement tier >= 1.' operationId: ListNewsPool security: - WorldMonitorKey: [] - ApiKeyHeader: [] - BearerAuth: [] parameters: - name: languages in: query description: Content languages (ISO 639-1), at most 8. Empty means en. required: false style: form explode: true example: - "fr" schema: type: array maxItems: 8 items: type: string - name: editions in: query description: Digest editions, e.g. full, tech, finance. Empty means full. required: false style: form explode: true example: - "full" schema: type: array maxItems: 8 items: type: string - name: category in: query description: One category key within the selected editions. required: false example: "us" schema: type: string maxLength: 32 - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "cells": - "edition": "full" "items": 1 "language": "fr" "state": "complete" "generatedAt": "2026-01-15T12:00:00Z" "items": - "category": "us" "editions": - "full" "item": "corroborationCount": 42 "credibilityScore": 42 "importanceScore": 42 "isAlert": true "link": "https://example.com/worldmonitor" "source": "example" "title": "example" "language": "fr" "state": "complete" schema: $ref: '#/components/schemas/ListNewsPoolResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: PRO entitlement access denied. headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-source-items: get: tags: - NewsService summary: ListSourceItems description: ListSourceItems returns the newest cached items of named sources in one category, for a dashboard panel whose enabled sources fell below the digest's per-category cap. Reads cached feed parses only; never fetches a feed. operationId: ListSourceItems parameters: - name: editions in: query description: |- Digest editions to look for each source in, in order, e.g. full, tech. Empty means full. required: false style: form explode: true example: - "full" schema: type: array maxItems: 8 items: type: string - name: category in: query description: One category key within the selected editions. required: true example: "europe" schema: type: string minLength: 1 maxLength: 32 - name: sources in: query description: |- Catalog source names, at most 24, each at most 120 characters. Order and duplicates do not matter. required: true style: form explode: true example: - "France 24" schema: type: array maxItems: 24 minItems: 1 items: type: string maxLength: 120 - name: lang in: query description: |- ISO 639-1 language code of a supported digest language (en, fr, ar, etc.); empty means en. Unsupported codes are rejected with 400. Only sources this language's digest fetches are read. required: false example: "en" schema: type: string maxLength: 8 - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "generatedAt": "2026-01-15T12:00:00Z" "items": - "corroborationCount": 42 "credibilityScore": 42 "importanceScore": 42 "isAlert": true "link": "https://example.com/worldmonitor" "source": "France 24" "title": "example" "sources": - "edition": "full" "items": 1 "source": "France 24" "state": "found" schema: $ref: '#/components/schemas/ListSourceItemsResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/UnauthorizedError' "403": description: API access requires an active subscription (the API key's subscription is inactive or expired). headers: X-Billing-Verification: description: Present when the 403 is a billing-provider-confirmed subscription lapse (value subscription_lapsed, matching the body `code`). schema: type: string content: application/json: schema: $ref: '#/components/schemas/ForbiddenError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' "503": description: Service unavailable. Billing-verification responses include code and X-Billing-Verification; other gateway infrastructure failures use the generic GatewayError shape. headers: Retry-After: description: Seconds to wait before retrying (1-60). schema: type: string X-Billing-Verification: description: Billing-verification state that produced this response (matches the body `code`). schema: type: string X-Validation-Mode: description: Present with value degraded when user API-key validation is temporarily unavailable. schema: type: string X-RateLimit-Mode: description: Present with value degraded when a fail-closed rate-limit dependency is unavailable. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/BillingVerificationError' - $ref: '#/components/schemas/GatewayError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-label-changes: get: tags: - NewsService summary: ListLabelChanges description: ListLabelChanges returns stories whose corroboration label moved between single-publisher and corroborated in the last seven days, with the publishers behind each change. operationId: ListLabelChanges security: [] parameters: - name: story_id in: query description: Story identity hash (64 hex characters). Empty lists changes across all stories. required: false example: "example-id" schema: type: string - name: limit in: query description: Maximum changes returned, newest first. 0 uses the default of 50; at most 200. required: false example: 25 schema: type: integer format: int32 - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "changes": - "at": 1767182400000 "credibilityScore": 72 "field": "corroboration" "link": "https://example.com/news/synthetic-example" "next": "corroborated" "nextPublishers": 2 "previous": "single-publisher" "previousPublishers": 1 "reason": "2 publisher families now carry this story (was 1)." "sources": - "name": "Example Wire (synthetic)" "tier": 1 - "name": "Example Daily (synthetic)" "tier": 2 "storyId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "title": "Synthetic example story" "trigger": "digest" "counts": "at": 1767182400000 "corroborated": 64 "singlePublisher": 51 "stories": 120 "tier4Only": 3 "unknown": 2 "retentionDays": 7 "state": "ok" schema: $ref: '#/components/schemas/ListLabelChangesResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/submit-error-report: post: parameters: - name: Idempotency-Key in: header description: Optional client-generated idempotency key. Retrying a POST with the same key and an identical request body replays the original response (only the status, body, and Content-Type are reproduced) instead of re-executing; reusing the key with a different body is rejected with 422. For mutations this avoids duplicating the side effect, while for batch-read POSTs it replays a cached snapshot that can be up to 24 hours stale. Keys are scoped per authenticated caller (falling back to the source IP for unauthenticated endpoints) and retained for 24 hours. required: false example: "4f8b9c2e-1a3d-4b6f-8e0a-2c5d7f9b1e34" schema: type: string minLength: 1 maxLength: 255 pattern: "^[\\x21-\\x7E]{1,255}$" tags: - NewsService summary: SubmitErrorReport description: SubmitErrorReport stores a reader's report that a label or story is wrong, for an operator to review. No account required; rate-limited; never applied automatically. operationId: SubmitErrorReport security: [] requestBody: content: application/json: example: "contact": "example" "message": "Example WorldMonitor observation." "storyId": "example-id" "url": "https://example.com/worldmonitor" schema: $ref: '#/components/schemas/SubmitErrorReportRequest' required: true responses: "200": description: Successful response headers: Idempotency-Key: schema: type: string description: The idempotency key echoed from the request. Present only when the client opted into idempotency. Idempotent-Replayed: schema: type: boolean description: true when this response was replayed from an earlier request with the same key, false on the first (original) request. Present only when the client opted into idempotency. content: application/json: example: "reportId": "example-id" "status": "example" schema: $ref: '#/components/schemas/SubmitErrorReportResponse' "400": description: Validation error, invalid Idempotency-Key header, or malformed JSON request body content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - type: object required: - error - message properties: error: type: string message: type: string - $ref: '#/components/schemas/InvalidRequestBodyError' "409": description: A request with this Idempotency-Key is still being processed headers: Idempotency-Key: schema: type: string description: The idempotency key supplied by the client. Retry-After: schema: type: string description: Seconds to wait before retrying the in-flight request. content: application/json: schema: type: object required: - error - message properties: error: type: string message: type: string "422": description: The Idempotency-Key was already used with a different request body headers: Idempotency-Key: schema: type: string description: The idempotency key supplied by the client. content: application/json: schema: type: object required: - error - message properties: error: type: string message: type: string "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/list-error-reports: get: tags: - NewsService summary: ListErrorReports description: |- ListErrorReports lists stored reports for review. Operators only. Requires the X-Trust-Review-Key header (the server's TRUST_REVIEW_SECRET), not an API key; without it the response is 401. operationId: ListErrorReports security: - TrustReviewKey: [] parameters: - name: status in: query description: pending (default) | reviewed. required: false example: "example" schema: type: string - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "reports": - "contact": "example" "createdAt": 1717200000000 "id": "example-id" "message": "Example WorldMonitor observation." "publicNote": "example" "state": "example" schema: $ref: '#/components/schemas/ListErrorReportsResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "401": description: Missing or invalid X-Trust-Review-Key, or review is not configured on the server. content: application/json: schema: $ref: '#/components/schemas/Error' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/review-error-report: post: tags: - NewsService summary: ReviewErrorReport description: |- ReviewErrorReport accepts or rejects a report. An accepted report's public note is added to the label log. Operators only. Requires the X-Trust-Review-Key header (the server's TRUST_REVIEW_SECRET), not an API key; without it the response is 401. operationId: ReviewErrorReport security: - TrustReviewKey: [] requestBody: content: application/json: example: "decision": "example" "publicNote": "example" "reportId": "example-id" schema: $ref: '#/components/schemas/ReviewErrorReportRequest' required: true responses: "200": description: Successful response content: application/json: example: "report": "contact": "example" "createdAt": 1717200000000 "id": "example-id" "message": "Example WorldMonitor observation." "publicNote": "example" schema: $ref: '#/components/schemas/ReviewErrorReportResponse' "400": description: Validation error or malformed JSON request body content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/InvalidRequestBodyError' "401": description: Missing or invalid X-Trust-Review-Key, or review is not configured on the server. content: application/json: schema: $ref: '#/components/schemas/Error' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/search-fact-checks: get: tags: - NewsService summary: SearchFactChecks description: SearchFactChecks returns ratings that IFCN-verified fact-checkers published about claims similar to the one given, for citation with attribution. operationId: SearchFactChecks security: [] parameters: - name: claim in: query description: The claim or headline, up to 300 characters. required: false example: "example" schema: type: string - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "caveat": "Ratings are the fact-checkers' own, quoted with attribution from IFCN-verified signatories. WorldMonitor does not rate claims, and a rating never changes the coverage result above." "factChecks": - "claimReviewed": "A synthetic example claim, similar to the claim checked" "publisher": "Example Fact-Checker (synthetic)" "rating": "Example rating" "reviewedAt": "2026-01-15T12:00:00.000Z" "site": "example.com" "title": "Synthetic example fact-check" "url": "https://example.com/fact-check/synthetic-example" "state": "ok" schema: $ref: '#/components/schemas/SearchFactChecksResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' /api/news/v1/search-claim-headlines: get: tags: - NewsService summary: SearchClaimHeadlines description: SearchClaimHeadlines returns the fresh headlines from every configured feed that share a claim's words, so the /verify page and verify_claim can build its coverage card. operationId: SearchClaimHeadlines security: [] parameters: - name: claim in: query description: The claim or headline, up to 300 characters. Empty returns only the counts. required: false example: "example" schema: type: string - name: jmespath in: query description: |- Optional JMESPath expression applied server-side to project or reduce the JSON response before it is returned (mirrors the MCP jmespath argument). Invalid expressions, expressions larger than 1024 UTF-8 bytes, or projections that exceed the 256 KB output cap return HTTP 400 with a {_jmespath_error, original_keys} envelope. Grammar and worked examples: https://www.worldmonitor.app/docs/mcp-jmespath. required: false example: "keys(@)" schema: type: string responses: "200": description: Successful response content: application/json: example: "feedCached": 241 "feedTotal": 241 "headlines": - "link": "https://example.com/news/synthetic-example" "publishedAt": 1768478400000 "source": "Example Feed" "title": "Synthetic example headline sharing the claim's words" "readAt": "2026-01-15T12:00:00.000Z" "searchedFeeds": 216 "searchedHeadlines": 4140 "state": "complete" schema: $ref: '#/components/schemas/SearchClaimHeadlinesResponse' "400": description: Validation error content: application/json: schema: oneOf: - $ref: '#/components/schemas/ValidationError' - $ref: '#/components/schemas/JmespathProjectionError' "429": description: Rate limit exceeded. headers: X-RateLimit-Limit: description: Maximum requests allowed in the active rate-limit window. schema: type: string X-RateLimit-Remaining: description: Requests remaining in the active rate-limit window. schema: type: string X-RateLimit-Reset: description: Unix epoch milliseconds when the active rate-limit window resets. schema: type: string Retry-After: description: Seconds to wait before retrying the request. schema: type: string content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/RateLimitError' default: description: Gateway or handler error response. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/GatewayError' components: securitySchemes: WorldMonitorKey: type: apiKey in: header name: X-WorldMonitor-Key description: User-issued WorldMonitor API key. ApiKeyHeader: type: apiKey in: header name: X-Api-Key description: Alias header for the WorldMonitor API key (X-WorldMonitor-Key). BearerAuth: type: http scheme: bearer description: 'Bearer token: a Clerk-issued JWT for browser session flows, passed as Authorization: Bearer .' TrustReviewKey: type: apiKey in: header name: X-Trust-Review-Key description: Operator review key (the server TRUST_REVIEW_SECRET). Required by the error-report review operations; a WorldMonitor API key does not authorize them. schemas: JmespathProjectionError: description: Returned when a REST jmespath projection is invalid or exceeds the expression/output byte limits. properties: _jmespath_error: description: Projection error discriminator and details. type: string original_keys: description: Top-level keys or shape of the unprojected response. items: type: string type: array required: - _jmespath_error - original_keys type: object UnauthorizedError: type: object properties: error: type: string description: Human-readable error message. required: - error description: Returned when the API key is missing, malformed, or lacks current API access. Error: type: object properties: message: type: string description: Error message (e.g., 'user not found', 'database connection failed') description: Error is returned when a handler encounters an error. It contains a simple error message that the developer can customize. BillingVerificationError: type: object description: "Returned with HTTP 503 when paid access cannot be confirmed right now: the billing provider is re-verifying a recently expired subscription, or the entitlement backend is unreachable. Retryable — honor Retry-After." properties: error: type: string description: Human-readable billing-verification failure reason. code: type: string enum: - renewal_verification_pending - renewal_verification_failed - entitlement_verification_unavailable description: Machine-readable billing-verification state, mirrored in the X-Billing-Verification response header. requiredTier: type: integer format: int32 description: Minimum entitlement tier required for this endpoint, when the denial came from a tier gate. required: - error - code InvalidRequestBodyError: type: object description: Returned when a JSON POST request body is empty or malformed. properties: message: type: string description: Invalid request body required: - message GatewayError: type: object description: Returned by gateway infrastructure errors before an RPC handler runs, such as origin, routing, method, authentication, or quota checks. properties: error: oneOf: - type: string - type: object additionalProperties: true description: Gateway error reason or structured gateway failure details. required: - error RateLimitError: type: object description: Returned when a gateway or handler rate limit rejects the request. properties: error: type: string description: Human-readable rate-limit failure reason. required: - error ForbiddenError: type: object properties: error: type: string description: Human-readable entitlement failure reason. code: type: string enum: - subscription_lapsed description: Machine-readable denial code, present when the 403 is a billing-provider-confirmed subscription lapse (mirrored in the X-Billing-Verification response header). requiredTier: type: integer format: int32 description: Minimum entitlement tier required for this endpoint. currentTier: type: integer format: int32 description: Caller entitlement tier when known. planKey: type: string description: Caller plan key when known. required: - error description: Returned when a PRO-gated endpoint denies access because the caller has no resolved authenticated user, entitlements cannot be verified, or the caller lacks the required entitlement tier. FieldViolation: type: object properties: field: type: string description: The field path that failed validation (e.g., 'user.email' for nested fields). For header validation, this will be the header name (e.g., 'X-API-Key') description: type: string description: Human-readable description of the validation violation (e.g., 'must be a valid email address', 'required field missing') required: - field - description description: FieldViolation describes a single validation error for a specific field. ValidationError: type: object properties: violations: type: array items: $ref: '#/components/schemas/FieldViolation' description: List of validation violations required: - violations description: ValidationError is returned when request validation fails. It contains a list of field violations describing what went wrong. SummarizeArticleRequest: type: object properties: provider: type: string minLength: 1 description: 'LLM provider: "ollama", "openrouter"' headlines: type: array items: type: string minItems: 1 description: |- Headlines to summarize. Up to 10 raw headlines are used for request identity/cache matching; the LLM prompt uses up to 5 unique, non-empty sanitized headline/body pairs. minItems: 1 mode: type: string description: 'Summarization mode: "brief", "analysis", "translate", "" (default).' geoContext: type: string description: Geographic signal context to include in the prompt. variant: type: string description: 'Variant: "full", "tech", or target language for translate mode.' lang: type: string description: Output language code, default "en". systemAppend: type: string description: Optional system prompt append for analytical framework instructions. bodies: type: array items: type: string description: |- Optional article bodies paired 1:1 with `headlines`. When bodies[i] is non-empty, the prompt interleaves it as grounding context under headlines[i]; when empty, behavior is identical to headline-only today. Callers may supply a shorter array; missing entries are treated as empty. Each body is subject to the same sanitisation as headlines before reaching the LLM prompt. required: - provider description: SummarizeArticleRequest specifies parameters for LLM article summarization. SummarizeArticleResponse: type: object properties: summary: type: string description: The generated summary text. model: type: string description: Model identifier used for generation. provider: type: string description: Provider that produced the result (or "cache"). tokens: type: integer format: int32 description: Token count from the LLM response. fallback: type: boolean description: Whether the client should try the next provider in the fallback chain. error: type: string description: Error message if the request failed. errorType: type: string description: Error type/name (e.g. "TypeError"). status: type: string enum: - SUMMARIZE_STATUS_UNSPECIFIED - SUMMARIZE_STATUS_SUCCESS - SUMMARIZE_STATUS_CACHED - SUMMARIZE_STATUS_SKIPPED - SUMMARIZE_STATUS_ERROR description: SummarizeStatus indicates the outcome of a summarization request. statusDetail: type: string description: Human-readable detail for non-success statuses (skip reason, etc.). description: SummarizeArticleResponse contains the LLM summarization result. GetSummarizeArticleCacheRequest: type: object properties: cacheKey: type: string description: Deterministic cache key computed by buildSummaryCacheKey(). description: GetSummarizeArticleCacheRequest looks up a pre-computed summary by cache key. ListFeedDigestRequest: type: object properties: variant: type: string description: 'Digest variant: full, tech, finance, happy, commodity, energy. Unsupported variants fall back to full.' lang: type: string description: ISO 639-1 language code of a supported digest language (en, fr, ar, etc.). Unsupported codes are rejected with 400. ListFeedDigestResponse: type: object properties: categories: type: object additionalProperties: $ref: '#/components/schemas/CategoryBucket' description: Per-category buckets — keys match category names from feed config feedStatuses: type: object additionalProperties: type: string description: |- Per-feed status — only non-ok states emitted; absent key implies ok. Values: empty (feed returned 0 items), timeout (timed out during fetch), all-undated (every parsed item lacked a usable date), partial-undated (some parsed items lacked a usable date). generatedAt: type: string description: ISO 8601 timestamp of when this digest was generated coverage: $ref: '#/components/schemas/DigestCoverage' CategoriesEntry: type: object properties: key: type: string value: $ref: '#/components/schemas/CategoryBucket' FeedStatusesEntry: type: object properties: key: type: string value: type: string CategoryBucket: type: object properties: items: type: array items: $ref: '#/components/schemas/NewsItem' NewsItem: type: object properties: source: type: string minLength: 1 description: Source feed name. title: type: string minLength: 1 description: Article headline. link: type: string description: Article URL. publishedAt: type: integer format: int64 description: 'Publication time, as Unix epoch milliseconds.. Warning: Values > 2^53 may lose precision in JavaScript' isAlert: type: boolean description: Whether this article triggered an alert condition. threat: $ref: '#/components/schemas/ThreatClassification' location: $ref: '#/components/schemas/GeoCoordinates' locationName: type: string description: Human-readable location name. importanceScore: type: integer format: int32 description: |- Composite importance score. The base 0-100 score uses severity × 55% + source tier × 20% + corroboration × 15% + recency × 10%, then may add an 18-point diplomacy/flashpoint boost and 4 points per entity-level corroborating source, capped at five sources. The final score can exceed 100; with current boosts it is approximately capped at 138. corroborationCount: type: integer format: int32 description: |- Number of distinct sources that reported the same story in this digest cycle. "Same story" is determined by edit-tolerant story-identity clustering (headlines describing one event with different wording, ordering, source suffixes, truncation, or morphology corroborate each other) — not exact-title matching. See shared/story-identity.js. storyMeta: $ref: '#/components/schemas/StoryMeta' snippet: type: string description: |- Cleaned article description from the RSS/Atom / / / tag: HTML-stripped, entity-decoded, whitespace-normalised, clipped to 400 chars. Empty string when unavailable or indistinguishable from the headline — consumers must fall back to the headline for display/LLM grounding in that case. tickers: type: array items: type: string maxItems: 8 description: |- Equity ticker symbols mentioned by the story, extracted at ingest from cashtags ($AAPL) and the curated company dictionary (shared/stocks.json). Uppercase symbols, deduplicated, capped at 8. Empty when no tracked ticker is mentioned. See shared/ticker-extract.js. maxItems: 8 credibilityScore: type: integer format: int32 description: |- Credibility score (0-100), distinct from importance_score. Measures source reliability / truthfulness, not newsworthiness. The base score uses source tier × 30% + propaganda risk × 50% + independent corroboration × 20%. High propaganda-risk sources (state-controlled media) are capped at 40 so a highly newsworthy RT/TASS story still scores low. Third-party bias/factuality ratings are reserved and not currently applied. See docs/methodology/news-credibility.mdx. required: - source - title description: NewsItem represents a single news article from RSS feed aggregation. ThreatClassification: type: object properties: level: type: string enum: - THREAT_LEVEL_UNSPECIFIED - THREAT_LEVEL_LOW - THREAT_LEVEL_MEDIUM - THREAT_LEVEL_HIGH - THREAT_LEVEL_CRITICAL description: ThreatLevel represents the assessed threat level of a news event. category: type: string description: Event category. confidence: type: number maximum: 1 minimum: 0 format: double description: Confidence score (0.0 to 1.0). source: type: string description: |- Classification source — "keyword", "keyword-historical-downgrade", or "llm". description: ThreatClassification represents an AI-assessed threat level for a news item. GeoCoordinates: type: object properties: latitude: type: number maximum: 90 minimum: -90 format: double description: Latitude in decimal degrees (-90 to 90). longitude: type: number maximum: 180 minimum: -180 format: double description: Longitude in decimal degrees (-180 to 180). description: GeoCoordinates represents a geographic location using WGS84 coordinates. StoryMeta: type: object properties: firstSeen: type: integer format: int64 description: 'Epoch ms when the story first appeared in any digest cycle.. Warning: Values > 2^53 may lose precision in JavaScript' mentionCount: type: integer format: int32 description: Total number of digest cycles in which this story appeared. sourceCount: type: integer format: int32 description: |- Number of unique sources reporting this story in the CURRENT digest cycle; 1 for unclustered items. phase: type: string enum: - STORY_PHASE_UNSPECIFIED - STORY_PHASE_BREAKING - STORY_PHASE_DEVELOPING - STORY_PHASE_SUSTAINED - STORY_PHASE_FADING description: StoryPhase represents the lifecycle stage of a tracked news story. description: StoryMeta carries cross-cycle persistence data attached to each news item. DigestCoverage: type: object properties: state: type: string description: |- complete | partial | stale | unavailable. complete: every configured category had at least one completed eligible feed and the build did not hit the global deadline. partial: content exists, but a category had no completed feed or the build hit the global deadline. stale: accepted older content is served after a failed or gate-held attempt (durable last-good serving, #7084). unavailable: no eligible content could be served. attemptedAt: type: string description: ISO 8601 time of the latest build attempt (attempt identity). itemsServed: type: integer format: int32 description: Items served in this response, after every gate. publisherCount: type: integer format: int32 description: Distinct publishers among the served items (normalized, this cycle only). feedTotal: type: integer format: int32 description: |- Eligible feeds configured for this variant+language, and how many completed (with or without items). feedCompleted: type: integer format: int32 categoryTotal: type: integer format: int32 description: Configured categories, and how many had at least one completed feed. categoryCompleted: type: integer format: int32 categoryStates: type: object additionalProperties: type: string description: 'Per-category state: ok (a feed completed) | missing (none did).' droppedFeedCap: type: integer format: int32 description: |- Per-gate drop counts, named by gate position in the pipeline: feed cap, undated-item, freshness floor, category cap. droppedUndated: type: integer format: int32 droppedFreshness: type: integer format: int32 droppedCategoryCap: type: integer format: int32 servedStale: type: boolean description: |- True when accepted older content is replayed after a failed or gate-held attempt (#7084); counts still describe the served body. staleAgeSeconds: type: integer format: int32 description: Content age since acceptance, seconds (0 when fresh, at most 21600). staleReason: type: string description: 'Closed stale reason: empty-rebuild | build-error | gate-held (empty when fresh).' itemsConsidered: type: integer format: int32 description: Items ranked before the category cap (served count on old replays). publishersConsidered: type: integer format: int32 description: Distinct publishers among those ranked items. description: |- Content vs attempt state for one digest response (#7085). generated_at keeps the CONTENT identity (when the served items were produced); attempted_at keeps the latest BUILD identity — the two diverge when stale content is served after a failed or gate-held attempt (#7084). CategoryStatesEntry: type: object properties: key: type: string value: type: string ListCountryHeadlinesRequest: type: object properties: countryCodes: type: array items: type: string description: ISO 3166-1 alpha-2 codes. Repeated query parameter, at most 250 countries. maxItems: 250 minItems: 1 required: - countryCodes description: ListCountryHeadlinesRequest selects countries from the full English RSS pool. ListCountryHeadlinesResponse: type: object properties: countries: type: object additionalProperties: $ref: '#/components/schemas/CountryHeadlineBucket' description: Requested country codes with matching headlines; absent countries had no match. state: type: string description: complete | partial | unavailable. A failed revocation read is unavailable. feedTotal: type: integer format: int32 description: Configured full-variant feeds reachable from the English digest. feedCached: type: integer format: int32 description: Feeds whose parsed cache was available, including cached empty feeds. readAt: type: string description: Time the caches were read; article freshness is determined by published_at. description: ListCountryHeadlinesResponse describes cache availability independently of country matches. CountriesEntry: type: object properties: key: type: string value: $ref: '#/components/schemas/CountryHeadlineBucket' CountryHeadlineBucket: type: object properties: items: type: array items: $ref: '#/components/schemas/CountryHeadline' maxItems: 5 description: CountryHeadlineBucket contains at most five distinct article URLs. CountryHeadline: type: object properties: source: type: string description: 'Publisher name: trusted origin publisher or registered feed name.' title: type: string description: Headline that names the requested country. link: type: string description: HTTPS article URL. publishedAt: type: integer format: int64 description: 'Publisher publication time in Unix epoch milliseconds.. Warning: Values > 2^53 may lose precision in JavaScript' description: CountryHeadline is a dated article from a configured RSS feed, before dashboard category caps. ListMediaPerspectiveRequest: type: object properties: subjectCountry: type: string maxLength: 2 minLength: 2 description: ISO2. outletCountries: type: array items: type: string maxItems: 30 description: ISO2, at most 30. maxItems: 30 audience: type: string description: domestic, international or diaspora. "windowHours": "type": "integer" "format": "int32" "description": "24, 48, 72 or 96." "enum": - 0 - 24 - 48 - 72 - 96 outletRegions: type: array items: type: string maxItems: 5 description: UN M49 region slugs, e.g. northern-europe. maxItems: 5 required: - subjectCountry ListMediaPerspectiveResponse: type: object properties: subjectCountry: type: string windowHours: type: integer format: int32 groups: type: array items: $ref: '#/components/schemas/MediaPerspectiveGroup' noCoverageCountries: type: array items: type: string unattributedCount: type: integer format: int32 description: No outlet profile or multi-publisher feed. state: type: string description: complete, partial or unavailable. feedTotal: type: integer format: int32 feedCached: type: integer format: int32 readAt: type: string MediaPerspectiveGroup: type: object properties: homeCountry: type: string audience: type: string outletCount: type: integer format: int32 description: Publisher families. headlineCount: type: integer format: int32 description: Before the 20-headline cap. framing: $ref: '#/components/schemas/MediaPerspectiveFramingCounts' headlines: type: array items: $ref: '#/components/schemas/MediaPerspectiveHeadline' maxItems: 20 MediaPerspectiveFramingCounts: type: object properties: favorable: type: integer format: int32 neutral: type: integer format: int32 critical: type: integer format: int32 hostile: type: integer format: int32 unlabeled: type: integer format: int32 method: type: string description: e.g. llm-headline-stance. model: type: string promptVersion: type: string MediaPerspectiveHeadline: type: object properties: title: type: string englishTitle: type: string description: Machine translation unless English; untrusted. titleLanguage: type: string outlet: type: string publisherFamily: type: string tier: type: integer format: int32 ownershipClass: type: string stateAffiliated: type: string propagandaRisk: type: string framing: type: string description: favorable, neutral, critical or hostile. publishedAt: type: integer format: int64 description: 'Warning: Values > 2^53 may lose precision in JavaScript' link: type: string ListNewsPoolRequest: type: object properties: languages: type: array items: type: string maxItems: 8 description: Content languages (ISO 639-1), at most 8. Empty means en. maxItems: 8 editions: type: array items: type: string maxItems: 8 description: Digest editions, e.g. full, tech, finance. Empty means full. maxItems: 8 category: type: string maxLength: 32 description: One category key within the selected editions. ListNewsPoolResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/NewsPoolItem' cells: type: array items: $ref: '#/components/schemas/NewsPoolCell' state: type: string description: complete, partial, stale or unavailable. generatedAt: type: string NewsPoolItem: type: object properties: item: $ref: '#/components/schemas/NewsItem' language: type: string description: ISO 639-1 content language of the outlet. Titles are untranslated. editions: type: array items: type: string description: Editions that carry this article, in catalog order. category: type: string NewsPoolCell: type: object properties: edition: type: string language: type: string state: type: string description: |- English digest cell: complete or partial (a fresh build), stale (a last-good replay), or missing. Other languages: complete (every feed cached), partial (some), or missing (none). items: type: integer format: int32 description: Items this cell contributed after the language and category filters. staleAgeSeconds: type: integer format: int32 generatedAt: type: string ListSourceItemsRequest: type: object properties: editions: type: array items: type: string maxItems: 8 description: |- Digest editions to look for each source in, in order, e.g. full, tech. Empty means full. maxItems: 8 category: type: string maxLength: 32 minLength: 1 description: One category key within the selected editions. sources: type: array items: type: string maxItems: 24 minItems: 1 description: |- Catalog source names, at most 24, each at most 120 characters. Order and duplicates do not matter. maxItems: 24 minItems: 1 lang: type: string maxLength: 8 description: |- ISO 639-1 language code of a supported digest language (en, fr, ar, etc.); empty means en. Unsupported codes are rejected with 400. Only sources this language's digest fetches are read. required: - category - sources ListSourceItemsResponse: type: object properties: items: type: array items: $ref: '#/components/schemas/NewsItem' maxItems: 72 sources: type: array items: $ref: '#/components/schemas/SourceItemsState' maxItems: 24 generatedAt: type: string SourceItemsState: type: object properties: source: type: string state: type: string description: |- found (a cached parse), missing (no healthy cached parse), or unplaced (no requested edition's digest in this language fetches the source in the category). edition: type: string description: Edition whose cached parse was read; empty unless found. items: type: integer format: int32 ListLabelChangesRequest: type: object properties: storyId: type: string maxLength: 64 description: Story identity hash (64 hex characters). Empty lists changes across all stories. limit: type: integer maximum: 200 minimum: 0 format: int32 description: Maximum changes returned, newest first. 0 uses the default of 50; at most 200. description: ListLabelChangesRequest selects recent corroboration label changes, optionally for one story. ListLabelChangesResponse: type: object properties: changes: type: array items: $ref: '#/components/schemas/LabelChange' state: type: string description: ok | unavailable. Unavailable means the log could not be read, not that it is empty. retentionDays: type: integer format: int32 description: Retention window in days. counts: $ref: '#/components/schemas/LabelCounts' description: ListLabelChangesResponse lists changes from the last seven days, newest first. LabelChange: type: object properties: storyId: type: string description: Story identity hash; empty for a correction that names no story. field: type: string description: corroboration (a label moved) | correction (an accepted reader report). previous: type: string description: 'Previous state: single-publisher | corroborated; empty for a correction.' next: type: string description: 'New state: single-publisher | corroborated; empty for a correction.' previousPublishers: type: integer format: int32 description: Publisher families behind the previous label; 0 for a correction. nextPublishers: type: integer format: int32 description: Publisher families behind the new label; 0 for a correction. at: type: integer format: int64 description: 'Time the change was recorded, Unix epoch milliseconds.. Warning: Values > 2^53 may lose precision in JavaScript' trigger: type: string description: 'What recorded it: digest (automatic) | report (an accepted error report).' reason: type: string description: Plain-language reason, in English. sources: type: array items: $ref: '#/components/schemas/LabelChangePublisher' title: type: string description: Story headline when the change was recorded. link: type: string description: |- Representative article URL. Empty when it failed the publisher link gate, was revoked after the change was recorded, or the revocation set could not be read. credibilityScore: type: integer format: int32 description: Story CRED score 0-100 when the change was recorded; -1 when unknown. description: |- LabelChange records one story moving between single-publisher and corroborated coverage, or one correction a reviewer accepted from a reader report. LabelChangePublisher: type: object properties: name: type: string description: Curated publisher name, or the feed label when no family is curated. tier: type: integer format: int32 description: Declared source tier 1-4; 0 when no feed label declares one. description: LabelChangePublisher is one publisher family behind the new label. LabelCounts: type: object properties: at: type: integer format: int64 description: 'When the digest counted, Unix epoch milliseconds.. Warning: Values > 2^53 may lose precision in JavaScript' stories: type: integer format: int32 description: Stories counted. corroborated: type: integer format: int32 description: Two or more publisher families, not all tier 4. singlePublisher: type: integer format: int32 description: One publisher family. tier4Only: type: integer format: int32 description: Two or more families, every one a declared tier-4 outlet. unknown: type: integer format: int32 description: No evidence to judge. description: LabelCounts is how many stories in the latest full English digest carry each label. SubmitErrorReportRequest: type: object properties: message: type: string maxLength: 2000 minLength: 10 description: What is wrong, in the reader's words. storyId: type: string maxLength: 64 description: Story identity hash the report concerns, when known (64 hex characters). url: type: string maxLength: 2048 description: Article or WorldMonitor URL the report concerns, when known. contact: type: string maxLength: 320 description: Optional email for a reply. Never published. website: type: string description: 'Honeypot. Leave empty: a filled value is accepted and discarded.' description: SubmitErrorReportRequest is a reader's report that a WorldMonitor label or story is wrong. Stored for review; never applied automatically. SubmitErrorReportResponse: type: object properties: status: type: string description: received | queue-full. reportId: type: string description: Report id, empty unless received. description: SubmitErrorReportResponse acknowledges a stored report. ListErrorReportsRequest: type: object properties: status: type: string maxLength: 16 description: pending (default) | reviewed. description: ListErrorReportsRequest selects reports for review. Requires the X-Trust-Review-Key header. ListErrorReportsResponse: type: object properties: reports: type: array items: $ref: '#/components/schemas/ErrorReport' state: type: string description: ok | unavailable. description: ListErrorReportsResponse lists reports, oldest pending first or newest reviewed first. ErrorReport: type: object properties: id: type: string description: Report id. status: type: string description: pending | accepted | rejected. message: type: string description: Reader's message. storyId: type: string description: Story identity hash, if given. url: type: string description: URL, if given. contact: type: string description: Reply email, if given. createdAt: type: integer format: int64 description: 'Submission time, Unix epoch milliseconds.. Warning: Values > 2^53 may lose precision in JavaScript' reviewedAt: type: integer format: int64 description: 'Review time, Unix epoch milliseconds; 0 while pending.. Warning: Values > 2^53 may lose precision in JavaScript' publicNote: type: string description: Operator's public note, published with an accepted report. description: ErrorReport is one stored report as an operator sees it. ReviewErrorReportRequest: type: object properties: reportId: type: string maxLength: 64 minLength: 1 description: Report id. decision: type: string maxLength: 16 description: accept | reject. publicNote: type: string maxLength: 500 description: 'Public note. Required to accept: it is what the label log publishes.' description: ReviewErrorReportRequest accepts or rejects one report. Requires the X-Trust-Review-Key header. ReviewErrorReportResponse: type: object properties: report: $ref: '#/components/schemas/ErrorReport' description: ReviewErrorReportResponse returns the reviewed report. SearchFactChecksRequest: type: object properties: claim: type: string maxLength: 300 description: The claim or headline, up to 300 characters. description: SearchFactChecksRequest names the claim to look up published fact-checks for. SearchFactChecksResponse: type: object properties: factChecks: type: array items: $ref: '#/components/schemas/FactCheck' state: type: string description: ok | unavailable | not-configured. Only ok means the search ran; an empty list is then no published fact-check found. caveat: type: string description: Fixed reminder that ratings are the fact-checkers' own and never change the coverage result. description: SearchFactChecksResponse lists published fact-checks of claims similar to the one searched. FactCheck: type: object properties: publisher: type: string description: The fact-checker's name as it publishes it. site: type: string description: The IFCN-allowlisted domain the link belongs to. rating: type: string description: The fact-checker's own rating, verbatim. WorldMonitor does not rate claims. reviewedAt: type: string description: When the fact-check was published, ISO 8601. url: type: string description: Link to the fact-check. title: type: string description: The fact-check's headline; empty when the feed gives none. claimReviewed: type: string description: 'The claim the fact-checker rated: similar to the claim searched, not necessarily the same.' description: FactCheck is one rating an IFCN-verified fact-checker published about a similar claim, cited with attribution. SearchClaimHeadlinesRequest: type: object properties: claim: type: string maxLength: 300 description: The claim or headline, up to 300 characters. Empty returns only the counts. description: SearchClaimHeadlinesRequest names the claim to search fresh headlines for. SearchClaimHeadlinesResponse: type: object properties: headlines: type: array items: $ref: '#/components/schemas/ClaimHeadline' searchedHeadlines: type: integer format: int32 description: Fresh headlines searched. searchedFeeds: type: integer format: int32 description: Feeds those headlines came from. feedTotal: type: integer format: int32 description: Configured full-variant English feeds. feedCached: type: integer format: int32 description: Feeds whose parsed cache was available. state: type: string description: complete | partial | unavailable. Unavailable searched nothing. readAt: type: string description: When the feed caches were read, ISO 8601. description: SearchClaimHeadlinesResponse holds the headlines that share the claim's words, for building its coverage card. ClaimHeadline: type: object properties: source: type: string description: Registered feed name. title: type: string link: type: string description: Article URL; empty when the ingest gate removed it. publishedAt: type: integer format: int64 description: 'Publication time in Unix epoch milliseconds.. Warning: Values > 2^53 may lose precision in JavaScript' description: ClaimHeadline is one fresh headline from a configured feed.