{"openapi":"3.1.0","info":{"title":"ANTESIGNA Enterprise API","version":"1.0.0","description":"Read-only programmatic access to the same data the ANTESIGNA pages show — nothing more, nothing less. All endpoints are GET, return a JSON envelope `{ data, meta? }` (or CSV where noted) and require authentication via the `X-API-Key` header or the browser session cookie. Numbers come from the same engine queries that feed the interface. Thematic intelligence, not financial advice. Honest limits, stated up front: authenticated requests are rate limited at 120 requests per 60-second sliding window per token — design against that figure, the effective ceiling under load can be higher, never lower. The limit in force is declared in meta.rate_limit of every JSON response and in the X-RateLimit-Limit and X-RateLimit-Remaining headers; a rate-limited 429 carries Retry-After in seconds. Repeated INVALID API-key attempts are limited separately (20 failures / 15 minutes / IP → 429, without Retry-After). timeseries, asof and claims paginate with a keyset cursor (meta.next_cursor); the older endpoints still use a single `limit` parameter (max 1000 on export, lower elsewhere). Every authenticated request is metered (route, status, latency) — you can see your own 7-day usage per token in Settings → API tokens. There are no webhooks or push channels yet — polling is the integration model today.","contact":{"email":"support@antesigna.com"}},"servers":[{"url":"/","description":"Same host that serves this spec (e.g. https://www.antesigna.com). Paths below are absolute from the host root."}],"security":[{"ApiKeyAuth":[]}],"paths":{"/api/v1/health":{"get":{"operationId":"getHealth","summary":"System health — live counters","description":"Live counters of what the engine ingested and produced: the quickest way to check the pipeline is breathing. All counts are real database counts over the stated windows.","tags":["monitoring"],"responses":{"200":{"description":"Current counters.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string","const":"ok"},"topics_24h":{"type":"integer","description":"Themes created in the last 24 hours."},"claims_total":{"type":"integer","description":"Total claims extracted, all time."},"articles_24h":{"type":"integer","description":"Articles published in the last 24 hours."},"alerts_24h":{"type":"integer","description":"Integrity alerts raised in the last 24 hours."}},"required":["status","topics_24h","claims_total","articles_24h","alerts_24h"]}},"required":["data"]}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/indices":{"get":{"operationId":"getIndices","summary":"Latest scalar index value per theme","description":"Most recent scalar value of a named index for each theme, over a 7-day window (one row per theme). Only whitelisted index names are exposed; anything else is rejected with 400.","tags":["analytics"],"parameters":[{"name":"name","in":"query","required":false,"description":"Index to read. Anything outside this whitelist is rejected.","schema":{"type":"string","enum":["echo_heterogeneity_index","agenda_density_index","contamination_score","echo_origin_index","r0_narrativo","silence_ratio","hawkes_branching_ratio","narrative_attractor","contrarian_cluster","contradiction_graph"],"default":"echo_heterogeneity_index"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–50 (default 10). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}}],"responses":{"200":{"description":"Latest value per theme.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IndexRow"}},"meta":{"type":"object","properties":{"index":{"type":"string"},"count":{"type":"integer"}}}},"required":["data"]}}}},"400":{"description":"Index name outside the whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/alerts":{"get":{"operationId":"getAlerts","summary":"Recent integrity alerts","description":"Integrity alerts from the last 30 days (passive single-source pickup, possible coordinated amplification, error cascades…), ordered by confidence then recency.","tags":["analytics"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–100 (default 20). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Alert rows.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AlertRow"}},"meta":{"type":"object","properties":{"count":{"type":"integer"}}}},"required":["data"]}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/predictions":{"get":{"operationId":"getPredictions","summary":"Forecasts with probability and horizon","description":"Active forecasts (resolved ones excluded unless resolved=1), newest first. Queued forecasts return probability = null — a filler value is never serialized as an estimate.","tags":["analytics"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–100 (default 20). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"resolved","in":"query","required":false,"description":"Pass resolved=1 to also include resolved forecasts. Any other value keeps the default (active only).","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"Forecast rows.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PredictionRow"}},"meta":{"type":"object","properties":{"count":{"type":"integer"}}}},"required":["data"]}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/export":{"get":{"operationId":"getExport","summary":"Bulk export (CSV or JSON)","description":"The same data the pages show, in bulk, for spreadsheets and models. CSV downloads carry a UTF-8 BOM, RFC-4180 quoting and spreadsheet formula-injection hardening (cells starting with = + - @ TAB CR are prefixed with an apostrophe). Rows are raw and NON-deduplicated — the JSON meta says so explicitly — so counts can legitimately exceed the deduplicated badges shown on the pages. predictions rows additionally include the credible band (credal_lo/credal_hi/credal_level/n_eff), the resolution rule fixed in advance (resolution_spec), resolved_at, outcome and brier_score. forecast_history is the real daily trajectory (prediction_id, day, probability, ci_low, ci_high) — written by the engine cycle, never interpolated. `limit` counts ROWS, not forecasts: rows come ordered by prediction_id then day, so a limit that runs out cuts a trajectory in half.","tags":["export"],"parameters":[{"name":"type","in":"query","required":true,"description":"Dataset to export (strict whitelist).","schema":{"type":"string","enum":["alerts","predictions","indices","forecast_history","analyses","theme_articles","contagion"]}},{"name":"id","in":"query","required":false,"description":"Required when type=theme_articles: the topic id whose primary sources you want. Without it the endpoint answers 400.","schema":{"type":"string"}},{"name":"format","in":"query","required":false,"description":"csv (default; responds as a file download) or json.","schema":{"type":"string","enum":["csv","json"],"default":"csv"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–1000 (default 200). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":200}},{"name":"name","in":"query","required":false,"description":"Only for type=indices: index name, same whitelist as /api/v1/indices.","schema":{"type":"string","enum":["echo_heterogeneity_index","agenda_density_index","contamination_score","echo_origin_index","r0_narrativo","silence_ratio","hawkes_branching_ratio","narrative_attractor","contrarian_cluster","contradiction_graph"],"default":"echo_heterogeneity_index"}}],"responses":{"200":{"description":"The requested dataset. CSV responses have Content-Disposition: attachment (filename antesigna-<type>-<YYYYMMDD>.csv).","content":{"text/csv":{"schema":{"type":"string","description":"RFC-4180 CSV with UTF-8 BOM and formula-injection hardening."}},"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Rows of the selected dataset (columns depend on `type`; see endpoint description)."},"meta":{"type":"object","properties":{"type":{"type":"string"},"count":{"type":"integer"},"window_days":{"type":["integer","null"],"description":"Query window in days (30 for alerts, 7 for indices, null when the whole table is eligible)."},"deduplicated":{"type":"boolean","const":false}}}},"required":["data","meta"]}}}},"400":{"description":"type outside the whitelist, invalid format, or (for type=indices) index name outside the whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/timeseries":{"get":{"operationId":"getTimeseries","summary":"Historical time series of an index","description":"Full history of a named index as persisted by the engine cycle — one row per (theme, produced_at), chronological order, never interpolated. Filter by theme (topic=<id>) OR by source country (country=<cc>, ISO-2): the country filter selects themes with at least one backing article from a source registered in that country — the SOURCE’s country, not the theme’s subject (the only real country anchor in the schema; stated in meta.filters.country_semantics). Keyset pagination on (produced_at, id) via meta.next_cursor. format=csv returns tidy rows (date,series,topic_id,topic_label,value) with the same formula-injection hardening as /api/v1/export.","tags":["analytics"],"parameters":[{"name":"series","in":"query","required":true,"description":"Index to read (whitelist of the numeric series the engine actually writes; anything else → 400).","schema":{"type":"string","enum":["echo_heterogeneity_index","agenda_density_index","contamination_score","narrative_mutations","weak_signal_growth","r0_narrativo","narrative_attractor","echo_origin_index","robust_changepoints","hawkes_branching_ratio","silence_ratio","contradiction_graph","contrarian_cluster","granger_leadlag_edges","signal_ladder","temporal_advantage"]}},{"name":"topic","in":"query","required":false,"description":"Restrict to one theme id (mutually exclusive with country).","schema":{"type":"string"}},{"name":"country","in":"query","required":false,"description":"ISO-2 source-country filter (mutually exclusive with topic). See endpoint description for the exact — honest — semantics.","schema":{"type":"string","pattern":"^[A-Za-z]{2}$"}},{"name":"from","in":"query","required":false,"description":"ISO-8601 lower bound on produced_at (inclusive).","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"ISO-8601 upper bound on produced_at (inclusive).","schema":{"type":"string","format":"date-time"}},{"name":"format","in":"query","required":false,"description":"json (default) or csv (tidy: date,series,topic_id,topic_label,value; cursor echoed as a trailing # comment and X-Next-Cursor header).","schema":{"type":"string","enum":["json","csv"],"default":"json"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–1000 (default 500). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":500}},{"name":"cursor","in":"query","required":false,"description":"Opaque keyset cursor from meta.next_cursor of the previous page. Stable under concurrent writes (keyset, not offset). Omit for the first page; meta.next_cursor = null means last page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Chronological data points. meta.next_cursor = null on the last page; meta.status = no_data_for_filter when the filter matches nothing (honest empty, never fabricated points).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TimeseriesPoint"}},"meta":{"type":"object","properties":{"series":{"type":"string"},"count":{"type":"integer"},"next_cursor":{"type":["string","null"]},"cursor_keys":{"type":"array","items":{"type":"string"}},"filters":{"type":"object","additionalProperties":true},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo"}}}},"required":["data","meta"]}},"text/csv":{"schema":{"type":"string","description":"Tidy CSV (date,series,topic_id,topic_label,value) with UTF-8 BOM and formula-injection hardening."}}}},"400":{"description":"series outside the whitelist, both topic and country given, malformed country/from/to, or invalid cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/asof":{"get":{"operationId":"getAsof","summary":"Point-in-time view: what did we know about an entity on date X","description":"Bitemporal as-of query over the claim corpus (the same event-time/transaction-time model as the engine’s temporal knowledge graph): returns the claims about an entity whose TRANSACTION time (articles.fetched_at — when WE ingested them) is <= the given date, each with both time axes (valid_time = articles.published_at, transaction_time = fetched_at) and full provenance. Later corrections never rewrite this view. meta.known_later_count quantifies the hindsight gap: claims about events on/before the date that we only learned afterwards. Entity matching is exact case-insensitive against engine-extracted actors — no fuzzy matching, because an invented match would be a fabrication. Empty results state their reason honestly in meta.status (no_data_for_entity / nothing_known_at_date).","tags":["analytics"],"parameters":[{"name":"entity","in":"query","required":true,"description":"Actor name as extracted by the engine (e.g. Gazprom, DRDO). Exact case-insensitive match.","schema":{"type":"string","maxLength":200}},{"name":"date","in":"query","required":true,"description":"ISO-8601 as-of date. A date without time means end of that day (inclusive).","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–500 (default 100). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque keyset cursor from meta.next_cursor of the previous page. Stable under concurrent writes (keyset, not offset). Omit for the first page; meta.next_cursor = null means last page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Claims known at the date, newest transaction time first. Keyset cursor on (transaction_time, claim_id).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AsofClaim"}},"meta":{"type":"object","properties":{"entity":{"type":"string"},"asof_date":{"type":"string"},"count":{"type":"integer"},"next_cursor":{"type":["string","null"]},"known_later_count":{"type":"integer","description":"Claims about events on/before the date, ingested only after it — the quantified hindsight gap."},"semantics":{"type":"object","additionalProperties":true},"status":{"type":"string","description":"Present only on honest-empty results: no_data_for_entity or nothing_known_at_date."},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo"}}}},"required":["data","meta"]}}}},"400":{"description":"Missing/oversized entity, malformed date, or invalid cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/screening":{"get":{"operationId":"getScreening","summary":"Registry & sanctions screening for one entity (honest perimeter)","description":"Screens an organization name against the primary public lists our daily registry refresh ingests — OFAC SDN (US public domain), the EU consolidated financial sanctions list and the UK OFSI consolidated list — plus GLEIF LEI open data (CC0) for legal identity and who-owns-whom consolidation links. Organizations only by construction: natural persons are filtered out at ingestion (no surveillance of individuals). Matching is EXACT on normalized names and on official aliases published by the lists themselves; 'possible' matches (name equality only after stripping corporate suffixes) are returned separately with a confidence below 1 and are never presented as confirmed. HONEST LIMITS, stated in every response (meta.disclaimer): meta.disclaimer names the lists actually searched and each list’s last-refresh date, and an empty result means «not found in those lists, as of those dates» — never a statement that an entity is not sanctioned anywhere. If the registry refresh has not run yet the response says so instead of returning an all-clear. This is not a comprehensive KYC/compliance screening service.","tags":["analytics"],"parameters":[{"name":"entity","in":"query","required":true,"description":"Organization name (e.g. Gazprombank). Matched exactly after normalization (lowercase, no diacritics, punctuation stripped).","schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Sanctions matches, possible matches, LEI identity and ownership links, with the ingested-list inventory and the mandatory honesty disclaimer in meta. meta.status = not_found_in_ingested_lists (or no_registry_ingested_yet) on honest-empty results.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ScreeningResult"},"meta":{"type":"object","properties":{"entity":{"type":"string"},"normalized":{"type":"string"},"lists_ingested":{"type":"array","items":{"type":"object","properties":{"list_name":{"type":"string"},"list_label":{"type":"string"},"entry_count":{"type":"integer"},"updated":{"type":"string","format":"date-time"}}}},"sanction_list_count":{"type":"integer"},"disclaimer":{"type":"string","description":"The mandatory honesty formula: which lists, updated when, and why absence is never an absolute all-clear."},"status":{"type":"string","description":"Present on honest-empty results: not_found_in_ingested_lists or no_registry_ingested_yet."},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo"}}}},"required":["data","meta"]}}}},"400":{"description":"Missing/oversized entity, or a name with no searchable word after normalization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/claims":{"get":{"operationId":"getClaims","summary":"Claim-level access with full provenance","description":"Engine-extracted claims with the article that backs each one (article_id, title, source_name, url, published_at) — only fields already in the database, no synthesis. Meta-claims about publication mechanics are excluded. Filters: entity (exact case-insensitive match on extracted actors), topic (the theme’s own claim set — same linkage the theme pages use), event_type (deterministic canonical event type derived from structured source substrates only — SEC 8-K items and form families, CISA KEV entries, openFDA recalls, USPTO grants; claims without a structured substrate honestly carry 'unclassified', never a plausible guess), from/to on the claim’s created_at (the same column the cursor uses). Keyset pagination on (created_at, id), newest first.","tags":["analytics"],"parameters":[{"name":"entity","in":"query","required":false,"description":"Actor name, exact case-insensitive match.","schema":{"type":"string","maxLength":200}},{"name":"topic","in":"query","required":false,"description":"Theme id: returns the claims belonging to that theme.","schema":{"type":"string"}},{"name":"event_type","in":"query","required":false,"description":"Canonical event type, lowercase snake_case (e.g. leadership_change, financial_results, material_agreement, product_recall, vulnerability_exploited, patent_grant, annual_report, quarterly_report). Deterministic mapping from structured filings/registries — no language model. 'unclassified' is itself a filterable, honestly-declared class.","schema":{"type":"string","pattern":"^[a-z0-9_]{1,40}$"}},{"name":"from","in":"query","required":false,"description":"ISO-8601 lower bound on the claim’s created_at.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"ISO-8601 upper bound on the claim’s created_at.","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","required":false,"description":"Maximum rows returned, 1–500 (default 100). Out-of-range values are clamped, not rejected.","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"cursor","in":"query","required":false,"description":"Opaque keyset cursor from meta.next_cursor of the previous page. Stable under concurrent writes (keyset, not offset). Omit for the first page; meta.next_cursor = null means last page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Claim rows with provenance, newest first. meta.status = no_data_for_filter on honest-empty results.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClaimWithProvenance"}},"meta":{"type":"object","properties":{"count":{"type":"integer"},"next_cursor":{"type":["string","null"]},"cursor_keys":{"type":"array","items":{"type":"string"}},"filters":{"type":"object","additionalProperties":true},"rate_limit":{"$ref":"#/components/schemas/RateLimitInfo"}}}},"required":["data","meta"]}}}},"400":{"description":"Oversized entity/topic, malformed from/to or event_type, or invalid cursor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid or revoked credentials. A request that presents an X-API-Key header which does not resolve to a valid token is rejected immediately (it never falls back to the session cookie).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Two limiters exist: (a) more than 20 INVALID API-key attempts within 15 minutes from one IP; (b) more than 120 authenticated requests per 60-second sliding window per token (per API token, or per user on session auth — declared in meta.rate_limit of every JSON response). A rate-limited 429 carries a Retry-After header in seconds; the invalid-key limiter answers 429 without it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Internal service error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API token generated in Settings → API tokens (format `ansg_…`, shown once at creation; only its SHA-256 hash is stored). Alternatively, every endpoint also accepts the same NextAuth session cookie your browser uses — sign in once with curl and reuse the cookie jar. If the X-API-Key header is present but invalid, the request gets 401 immediately (no cookie fallback)."}},"schemas":{"Error":{"type":"object","description":"Error envelope. `error` is a machine key (unauthorized, rate_limited, server_error), `message` a human-readable explanation.","properties":{"error":{"type":"string","examples":["unauthorized"]},"message":{"type":"string","examples":["Invalid or revoked API key."]}},"required":["error","message"]},"IndexRow":{"type":"object","properties":{"topic_id":{"type":"string"},"topic_label":{"type":"string","description":"Human-readable theme label (falls back to the raw id when no label exists)."},"value":{"type":"number","description":"Scalar value of the index for this theme."},"produced_at":{"type":"string","format":"date-time"}},"required":["topic_id","topic_label","value","produced_at"]},"AlertRow":{"type":"object","properties":{"id":{"type":"integer"},"topic_id":{"type":"string"},"category":{"type":"string","description":"Alert category key."},"risk_domain":{"type":"string","description":"Deterministic risk domain derived from the category (reputational | regulatory | market | none | unclassified). Fixed versioned mapping, no language model; unknown categories are honestly 'unclassified'."},"confidence":{"type":"number","description":"Classifier confidence, 0–1."},"contamination_score":{"type":"number","description":"Estimated contamination of the theme’s information flow, 0–1."},"rationale":{"type":"string","description":"Why the classifier raised this alert."},"created_at":{"type":"string","format":"date-time"},"topic_label":{"type":["string","null"]}},"required":["id","topic_id","category","risk_domain","confidence","contamination_score","rationale","created_at"]},"RateLimitInfo":{"type":"object","description":"The real, honest rate limit applied to authenticated requests, echoed in every JSON response.","properties":{"limit":{"type":"integer","examples":[120]},"window_seconds":{"type":"integer","examples":[60]},"scope":{"type":"string","examples":["per API token (per-instance, in-memory)"]}}},"TimeseriesPoint":{"type":"object","properties":{"topic_id":{"type":["string","null"]},"topic_label":{"type":["string","null"]},"series":{"type":"string"},"value":{"type":"number","description":"The numeric index value as persisted by the engine cycle — never interpolated."},"produced_at":{"type":"string","format":"date-time"}},"required":["series","value","produced_at"]},"AsofClaim":{"type":"object","properties":{"claim_id":{"type":"string"},"text":{"type":"string","description":"The claim as extracted by the engine."},"est":{"type":"string","description":"Epistemic status tag (V1–V5 scale)."},"claim_type":{"type":"string","description":"event | claim | frame | opinion."},"confidence":{"type":["number","null"]},"actors":{"type":"array","items":{"type":"string"}},"valid_time":{"type":["string","null"],"format":"date-time","description":"articles.published_at — when the fact was asserted in the world (event time)."},"transaction_time":{"type":["string","null"],"format":"date-time","description":"articles.fetched_at — when WE ingested it (what we actually knew, and when)."},"source_name":{"type":["string","null"]},"article_title":{"type":["string","null"]},"url":{"type":["string","null"]}},"required":["claim_id","text","est","claim_type"]},"ClaimWithProvenance":{"type":"object","properties":{"claim_id":{"type":"string"},"text":{"type":"string"},"est":{"type":"string"},"claim_type":{"type":"string"},"event_type":{"type":"string","description":"Deterministic canonical event type from structured substrates (SEC items/forms, KEV, recalls, patents); 'unclassified' when no structured substrate backs a classification."},"confidence":{"type":["number","null"]},"actors":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time","description":"When the engine extracted the claim (also the pagination key)."},"article_id":{"type":["string","null"]},"article_title":{"type":["string","null"]},"source_name":{"type":["string","null"]},"url":{"type":["string","null"],"description":"Verifiable link to the backing article."},"published_at":{"type":["string","null"],"format":"date-time"}},"required":["claim_id","text","est","claim_type","created_at"]},"ScreeningResult":{"type":"object","properties":{"sanctions":{"type":"array","description":"Confirmed matches: exact normalized-name equality with the list’s PRIMARY name. Short single-token acronyms and generic no-country institution names are never confirmed (homonymy guard): they degrade to possible_matches.","items":{"type":"object","properties":{"list_name":{"type":"string","enum":["ofac_sdn","eu_fsf","uk_ofsi"]},"list_label":{"type":"string"},"listed_entity":{"type":"string","description":"The name exactly as the list publishes it."},"matched_on":{"type":"string","const":"name"},"match_confidence":{"type":"number","const":1},"programs":{"type":"array","items":{"type":"string"}},"listed_on":{"type":["string","null"],"format":"date","description":"Listing date as declared by the list; null when the list does not publish one (OFAC’s CSV does not) — an honest null, never a guess."},"source_url":{"type":"string"}}}},"alias_matches":{"type":"array","description":"Exact equality with an OFFICIAL alias published by the list itself (confidence 0.9): the string is list-published, but aliases can collide across different entities — verify against the official source before acting.","items":{"type":"object","properties":{"list_name":{"type":"string"},"list_label":{"type":"string"},"listed_entity":{"type":"string"},"matched_on":{"type":"string","const":"alias"},"match_confidence":{"type":"number","const":0.9},"note":{"type":"string"},"programs":{"type":"array","items":{"type":"string"}},"listed_on":{"type":["string","null"],"format":"date"},"source_url":{"type":"string"}}}},"possible_matches":{"type":"array","description":"Two honest-uncertainty tiers, NEVER a confirmed listing: (a) name equality only after stripping corporate suffixes (computed by the daily screening run over the top corpus entities, confidence 0.6); (b) matches on short/generic ambiguous names degraded by the homonymy guard (confidence 0.5). Verify against the official source.","items":{"type":"object","properties":{"list_name":{"type":"string"},"listed_entity":{"type":"string"},"match_kind":{"type":"string","const":"possible"},"match_confidence":{"type":"number","description":"Below 1 by construction."},"note":{"type":"string"},"listed_on":{"type":["string","null"],"format":"date"},"source_url":{"type":"string"}}}},"lei":{"type":"array","description":"GLEIF LEI Level-1 identity rows whose normalized legal name matches exactly (our LEI set is filtered to corpus entities — declared cap, not the full golden copy).","items":{"type":"object","properties":{"lei":{"type":"string"},"legal_name":{"type":"string"},"jurisdiction":{"type":["string","null"]},"country":{"type":["string","null"]},"entity_status":{"type":["string","null"]}}}},"ownership":{"type":"object","description":"GLEIF Level-2 accounting-consolidation links (who-owns-whom). Partial by nature: only LEI-registered entities that report relationships appear.","properties":{"parents":{"type":"array","items":{"type":"object","additionalProperties":true}},"children":{"type":"array","items":{"type":"object","additionalProperties":true}},"semantics":{"type":"string"}}}}},"PredictionRow":{"type":"object","properties":{"id":{"type":"integer"},"question":{"type":"string","description":"The forecast question, with its resolution rule fixed in advance."},"probability":{"type":["number","null"],"description":"Estimated probability 0–1. null while the forecast is queued (pending/queued status): a default is never serialized as an estimate."},"status":{"type":"string","description":"estimated, pending or queued."},"delta_since":{"type":["number","null"],"description":"Probability change since the previous estimate."},"horizon_days":{"type":["integer","null"]},"produced_at":{"type":"string","format":"date-time"}},"required":["id","question","status","produced_at"]}}}}