{"openapi":"3.1.0","info":{"title":"Anteproof API","description":"Read-only access to the Anteproof forecast archive: live forecasts, question detail, and the full calibration/launch-gate audit trail. Anteproof is a product of Recoilix LLC.","version":"0.1.0"},"paths":{"/v1/account/config":{"get":{"summary":"Account Config","description":"What the browser needs before anyone signs in. Public by design;\nthe publishable key is meant to be published.","operationId":"account_config_v1_account_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Account Config V1 Account Config Get"}}}}}}},"/v1/me":{"get":{"summary":"Me","description":"The signed-in account. First call for a user creates it and starts\nthe reverse trial; the response says what the account can do now.","operationId":"me_v1_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Me V1 Me Get"}}}}}}},"/v1/me/watchlist":{"get":{"summary":"Watchlist","operationId":"watchlist_v1_me_watchlist_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Watchlist V1 Me Watchlist Get"}}}}}},"post":{"summary":"Watch","operationId":"watch_v1_me_watchlist_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Watch V1 Me Watchlist Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me/watchlist/import":{"post":{"summary":"Import Watchlist","description":"Load a whole dependency tree at once: the body is a CycloneDX JSON\nor SPDX JSON document (up to 5 MB). Every component with a\npkg:npm, pkg:pypi, pkg:cargo or pkg:github purl is normalised and\nmatched against the archive's entities; matches are added to the\nwatchlist up to the plan's cap, and the ones that did not fit are\nreported as over_cap rather than refused, so a Free account still\ngets its three and sees what Pro would unlock. Supported names\nwith no entity yet are remembered per account (watchlist_pending)\nand listed under unmatched. With ?dry_run=true nothing is written.\nA document that cannot be read is a 400 with the reason; the whole\nimport either applies or does not.","operationId":"import_watchlist_v1_me_watchlist_import_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Import Watchlist V1 Me Watchlist Import Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me/watchlist/pending":{"get":{"summary":"Watchlist Pending","description":"Packages from this account's SBOM imports that had no entity in\nthe archive at the time. They are the demand signal the universe\nseeder reads; once an entity exists the row keeps its id (shown\nwith ?include_resolved=true) and the next import watches it.","operationId":"watchlist_pending_v1_me_watchlist_pending_get","parameters":[{"name":"include_resolved","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Resolved"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Watchlist Pending V1 Me Watchlist Pending Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me/watchlist/{entity_id}":{"delete":{"summary":"Unwatch","operationId":"unwatch_v1_me_watchlist__entity_id__delete","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"integer","title":"Entity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Unwatch V1 Me Watchlist  Entity Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me/api-keys":{"get":{"summary":"Api Keys","operationId":"api_keys_v1_me_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Api Keys V1 Me Api Keys Get"}}}}}},"post":{"summary":"Create Api Key","description":"The one response that ever contains the key itself.","operationId":"create_api_key_v1_me_api_keys_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Api Key V1 Me Api Keys Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me/api-keys/{key_id}":{"delete":{"summary":"Revoke Api Key","operationId":"revoke_api_key_v1_me_api_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"integer","title":"Key Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Revoke Api Key V1 Me Api Keys  Key Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/me/alerts":{"get":{"summary":"Get Alerts","description":"This account's alert rules (Pro only). The defaults exist from\nthe moment an account is first Pro: a crossing rule at 0.35 below on\nthe maintenance families, a move rule at 0.15, and a resolution rule.","operationId":"get_alerts_v1_me_alerts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Alerts V1 Me Alerts Get"}}}}}},"put":{"summary":"Put Alerts","description":"Partial update: any of crossing / move / resolution with\nenabled, threshold (0-1, exclusive) and direction (crossing only),\nplus email_enabled for the account as a whole.","operationId":"put_alerts_v1_me_alerts_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertsBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Put Alerts V1 Me Alerts Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/checkout":{"post":{"summary":"Checkout","operationId":"checkout_v1_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Checkout V1 Billing Checkout Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/portal":{"get":{"summary":"Portal","operationId":"portal_v1_billing_portal_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Portal V1 Billing Portal Get"}}}}}}},"/v1/billing/webhooks/stripe":{"post":{"summary":"Stripe Webhook","description":"Signed with the endpoint's secret (Stripe-Signature, HMAC-SHA256\nover \"<t>.<body>\", 5-minute tolerance). Every event is recorded once\nby its id. customer.subscription.* events overwrite the account's\nsubscription state with the object they carry;\ncheckout.session.completed attaches the customer to the account and,\nif the subscription event has not arrived yet, fetches the\nsubscription so the account is never left half-linked.","operationId":"stripe_webhook_v1_billing_webhooks_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/support":{"post":{"summary":"Submit","description":"Store one support message and notify the founder. No sign-in\nneeded; a Clerk session, when present, attaches the account id.\nAnswers 202 with the message id (null when the honeypot caught a\nbot — the response is otherwise identical), 422 on a malformed\nmessage, 429 past five messages then one per two minutes per client,\n503 when this deployment has no accounts database.","operationId":"submit_v1_support_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportBody"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Submit V1 Support Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/entities":{"get":{"summary":"Search Entities","description":"Entity lookup for the watchlist: case-insensitive substring on the\ncanonical name, current (unmerged) entities only, exact match first.","operationId":"search_entities_v1_entities_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Search Entities V1 Entities Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/forecasts":{"get":{"summary":"List Forecasts","description":"The public forecast log: live (non-backtest) forecasts, newest\nfirst, with their question text and resolution when one exists.\nA forecast whose question was later voided stays in the log (the\nforecast is part of the record) and carries ``question_status`` =\n\"voided\" plus the recorded ``voided_reason`` so it is never mistaken\nfor an open call.\n``question`` is the exact wording that was forecast and timestamped;\n``title`` is a reader-friendly rendering of the same question (see\nforesight_api.titles) and falls back to the text.\n\n``entity_kind`` is ``entity.kind`` for the question's entity — the\nsame column ``/v1/entities/{id}`` already publishes — and is null\nwhen the question has no entity (a market question) or the entity row\nis gone. It is here because ``entity_id`` alone cannot tell a\ndependency question from one about a company: an SEC Form D filer has\na real ``entity_id`` exactly like tox-dev/tox does, and only the kind\nseparates them (\"company\" vs \"repo\"). A client that is about\ndependencies -- the homepage's two lists are -- filters on it; see\nSOFTWARE_ENTITY_KINDS in static/index.html.\n\nThis endpoint itself does NOT filter. The archive is append-only and\npublic by design, and an API that silently drops rows from the log is\na worse answer than a page that curates its own two lists: the record\nhas to stay complete for anyone checking it. The field is added so\nthe curation can happen where the curation claim is made.\n\n``resolved=true`` returns only forecasts whose question has an\noutcome, newest resolution first. That slice is what lets a reader\ncheck the record against what happened -- including the calls we got\nwrong, which are in it by construction rather than by the luck of a\nwindow ordered on issue date.","operationId":"list_forecasts_v1_forecasts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"resolved","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Resolved"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Forecasts V1 Forecasts Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/questions/{question_id}":{"get":{"summary":"Question Detail","description":"One question with its full public record: every live forecast ever\nissued for it (the append-only log, not just the latest) and its\nresolution if settled.","operationId":"question_detail_v1_questions__question_id__get","parameters":[{"name":"question_id","in":"path","required":true,"schema":{"type":"integer","title":"Question Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Question Detail V1 Questions  Question Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/entities/{entity_id}":{"get":{"summary":"Entity Detail","description":"One tracked entity's public record: identity, the day it was last\nvelocity-scored (every FIGURE in that block — ``composite``,\n``layer_scores`` and the ``stage`` label — is withheld, for two\nseparate reasons; see foresight_api.risk.VELOCITY_COMPOSITE_TRUSTED\nand VELOCITY_STAGE_TRUSTED), recent daily metric history (live\nview), and every non-pastcast question asked about it with its\nlatest live forecast. The data backbone for M6's entity pages.","operationId":"entity_detail_v1_entities__entity_id__get","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"integer","title":"Entity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Entity Detail V1 Entities  Entity Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/risk":{"get":{"summary":"Dependency Risk","description":"Dependency risk for a package or repository: the latest open\nmaintenance forecasts (commits_90d, release_90d), 90 days of\ncommit/release/star activity summarised, the day the entity was last\nvelocity-scored (the score itself and its stage label are both\nwithheld), the\nplain-language flags those numbers support, and the families' own\ncalibration record — one timestamped answer for an agent choosing a\ndependency. ``q`` is an npm/PyPI/crates.io package name, a GitHub\nowner/repo (or URL), or ``npm:`` / ``pypi:`` / ``crate:`` / ``repo:``\nplus a name. See foresight_api.risk for what each block means and\nhow a package is linked to its repository.\n\n``view`` (pilot findings, Priority 5): ``full`` (default, unchanged --\nevery diagnostic field, exactly this endpoint's historical shape) or\n``compact`` (foresight_api.risk_batch.compact_view -- what was\nresolved, coverage status, key evidence, forecasts, warnings, nothing\nelse). The default stays ``full`` on purpose: entity.html and\ncoverage_preview.py already depend on fields (signals, calibration_note,\nrepo_link) this endpoint has always returned by default, so making\ncompact the default would silently break both rather than opt anyone in.","operationId":"dependency_risk_v1_risk_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"title":"Q"}},{"name":"view","in":"query","required":false,"schema":{"type":"string","default":"full","title":"View"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dependency Risk V1 Risk Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/risk/batch":{"post":{"summary":"Risk Batch Endpoint","description":"A whole lockfile in one request (pilot findings, Priority 3):\ncompact, comparable per-package results in submitted order, with a\ntop-level status distinguishing full coverage, partial coverage,\nambiguity and no coverage (Priority 4) instead of one flat dict a\ncaller has to interpret differently for every failure mode. ``view``\nis ``summary`` (default -- foresight_api.risk_batch's compact fields)\nor ``detail`` (the same full per-package answer /v1/risk gives,\nkeyed by query). Ambiguous or unmatched entries never raise for the\nwhole batch -- one bad entry becomes one result, not a 4xx for\npackages that would have resolved fine.","operationId":"risk_batch_endpoint_v1_risk_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiskBatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Risk Batch Endpoint V1 Risk Batch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/coverage-preview":{"get":{"summary":"Coverage Preview Endpoint","description":"Staged coverage for a customer-supplied package list, before signup:\nfor each package, whether it is recognized, has a linked repository,\nhas recent activity data, has an open forecast, and has a validated\n(not withheld) track record -- with the reason spelled out at whichever\nstage a package falls short, rather than omitted. ``packages`` is a\ncomma-separated list, each entry in the same form /v1/risk accepts.\nUnmatched entries are reported, never silently dropped. See\nforesight_api.coverage_preview for the stage definitions.","operationId":"coverage_preview_endpoint_v1_coverage_preview_get","parameters":[{"name":"packages","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":2000,"title":"Packages"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Coverage Preview Endpoint V1 Coverage Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/risk/top":{"get":{"summary":"Top Risk","description":"The digest's \"Most at risk this week\" table as JSON: the ``limit``\nrepositories with the lowest open maintenance forecasts (commits_90d,\nrelease_90d) among source repositories of packages with at least\n``min_dependents`` dependent repositories (ecosyste.ms), one row per\nrepository with both family probabilities, ranked by the lower one.\nPublic and read-only; see foresight_api.risk.top_at_risk for the\nselection and the honest empty states (``open_questions`` says\nwhether anything is open at all).","operationId":"top_risk_v1_risk_top_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Limit"}},{"name":"min_dependents","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":1000,"title":"Min Dependents"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Top Risk V1 Risk Top Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/entities/{entity_id}/risk":{"get":{"summary":"Entity Risk","description":"The same dependency-risk answer for an entity by id.","operationId":"entity_risk_v1_entities__entity_id__risk_get","parameters":[{"name":"entity_id","in":"path","required":true,"schema":{"type":"integer","title":"Entity Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Entity Risk V1 Entities  Entity Id  Risk Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/calibration":{"get":{"summary":"Calibration","description":"The transparency record: latest calibration snapshot per domain\nplus EVERY launch-gate evaluation ever run, verdicts included —\nfailing runs are part of the audit trail, not something to hide.\n\nA run whose report names a family in\n`calibration.UNTRUSTED_RESOLUTION_FAMILIES` is published as a run and\nwithheld as a measurement. The row keeps when it ran, its domain,\nfamily, as-of date, horizon, forecaster version and verdict; every\nstatistic in its `report` is JSON `null` (never 0, never an omitted\nkey) and the row carries a `withheld` block with\n`reason: \"resolutions_untrusted\"` saying why. `reasons` goes with\nthem, as a whole list, because those strings quote the same figures in\nprose. `withheld` is `null` on every row still published, and rows for\ntrusted families — the `rollup_threshold` news-tone record included —\nare returned exactly as stored.\n\nThat the runs stay visible is deliberate and is the honest half of\nthe story: we ran our own backtest, found the GH Archive series under\nthose resolutions had collapsed, and withdrew the numbers. Deleting\nthe rows would hide the withdrawal too.\n\nWithheld per ROW, not per latest row: `proof._gate_withheld` reads\nonly the newest evaluation (`ORDER BY id DESC LIMIT 1`), and the\nwithdrawn runs are history. This matches /v1/risk, /v1/proof and the\ncalibration page exactly — one endpoint must not publish a number the\nnext one withholds.","operationId":"calibration_v1_calibration_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Calibration V1 Calibration Get"}}}}}}},"/v1/calibration/families":{"get":{"summary":"Calibration By Family","description":"The calibration page's data: the same resolved-forecast pool the\nlaunch gate judges, broken down by question family, plus the live\ntrack the gate does not judge yet.\n\nScope follows the gate exactly. The pastcast track is the current\nquestion-generation scheme and the forecaster version named by the\nmost recent gate evaluation — older regimes are retired, and mixing\nthem was measured to flatter the numbers (see\nforesight_backtest.report._resolved_pastcasts). The live track is\nevery real-time forecast that has resolved, all versions, because a\nreader deserves the whole real-time record, not a curated slice.\nOne row per question: the latest forecast, which is what resolution\nscored against.\n\nHorizon is the REAL horizon the gate scopes on — resolution date\nminus the as-of date (a live forecast's issue date) — bucketed, with\nevery bucket's row count returned so the page can offer only the\nscopes that exist. The default scope is the gate verdict's own\nhorizon (its report's `horizon_days`, bucketed): since the 30-day\nexperiment the archive holds pastcasts at more than one horizon and\npooling them was measured to flatter the headline (BSS 0.155 pooled\nagainst 0.107 at the gate's 7 days). `horizon=all` pools on purpose;\na verdict that names no horizon (older reports) scopes nothing.\n\nThe verdict follows the same scope. `regime.gate` is the latest\nverdict that judged THIS horizon, or null with `regime.gate_scope`\nsaying in words that no gate covers it — never a fallback to another\nhorizon's verdict, because a passing 7-day verdict served beside the\n90-day maintenance families reads as those families having passed a\ngate they have not. `regime.latest_gate` still carries the newest\nverdict overall, stamped with its own `domain` and `horizon_days`, so\nthe distinction survives outside the browser (the MCP tool has no\npage, no card and no selector).\n\nBoth verdict blocks are under the withheld contract, through\n`calibration.public_gate_row` — the same helper /v1/calibration's\n`gate_history` uses, not a second copy of the rule. A verdict scoped\nto a withheld family keeps its identity and its recorded `passes`,\nevery statistic in its `report` is `null`, and it carries a\n`withheld` block; `withheld` is `null` on every verdict still\npublished. This block used to be the row as stored, `report` blob and\nall, and it read as harmless only while the newest row happened to be\nthe trusted news-tone run: the first gate row scoped to a withheld\nfamily — which is what a backtest over the maintenance families\ncreates — would have published that family's `n` and Brier skill\nhere, from a route whose family table withholds exactly those numbers\nthree keys further down.\n\nEvery family resolved off a GH Archive activity series is WITHHELD,\nunconditionally: the windowed maintenance families (`commits_90d`,\n`release_90d`) and the legacy daily-threshold families that read the\nsame three series (`commits_hourly`, `release_published`,\n`stars_hourly` — keyed by their metric because they carry no\nprovenance family). Every question in that pool was resolved by\ncounting a GH Archive series whose capture collapsed, so the outcomes\nthose forecasts were scored against are themselves unreliable and no\nscore over the pool is sound — at any horizon, over any window, however\nhealthy the series looks now. Their `n` and every statistic beside it\nare JSON `null` (never 0, never omitted) and the family carries a\n`withheld` block with `reason: \"resolutions_untrusted\"` saying so;\n`withheld` is `null` on everything still published. Their rows are not\nfolded into a track total either, and a track left with nothing to\nscore goes null rather than reporting n=0. This matches /v1/risk and\n/v1/proof exactly: one endpoint must not withhold a number the next\none publishes. It ends when the pool is re-resolved against the\nGitHub API series, not when the source looks healthy again.\n\nUnknown query parameters are ignored on purpose: published proof\nlinks carry campaign tags (`utm_campaign=...`), and a 422 on those\nwould break every campaign-tagged link into this record. An invalid\nVALUE for `horizon` is still a 422.","operationId":"calibration_by_family_v1_calibration_families_get","parameters":[{"name":"horizon","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Horizon bucket to scope to: 7 (up to 7 days), 30, 90 or long (over 90); 'all' for every horizon. Omit for the horizon the latest gate verdict scopes on. `regime.gate` is the verdict for the horizon asked for, or null when none was run on it; `regime.latest_gate` is the newest verdict overall, whatever its horizon.","title":"Horizon"},"description":"Horizon bucket to scope to: 7 (up to 7 days), 30, 90 or long (over 90); 'all' for every horizon. Omit for the horizon the latest gate verdict scopes on. `regime.gate` is the verdict for the horizon asked for, or null when none was run on it; `regime.latest_gate` is the newest verdict overall, whatever its horizon."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Calibration By Family V1 Calibration Families Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/proof":{"get":{"summary":"Proof Record","description":"The record in one JSON, for the landing page's proof strip, the\nlaunch posts and the Monday report: the latest launch-gate verdict\n(n, Brier skill, the pool's families) and the maintenance families'\nown resolved record with a state — ``none``, ``early``, ``skill`` or\n``no_skill`` — and the plain-words note that state earns. Skill is\nnever claimed while the interval includes zero. See\nforesight_api.proof. Cached five minutes.","operationId":"proof_record_v1_proof_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Proof Record V1 Proof Get"}}}}}}},"/v1/chain/head":{"get":{"summary":"Chain Head","description":"The forecast log's current hash-chain head: what the daily\nAnteproof receipt anchors (packages/anchor). The statement is the same\none foresight_anchor.head.HEAD_SQL runs — one snapshot, so hash, id,\ncount and max issued_at describe the same chain state. The chain\nlinks by id (see foresight_core.repository.append_forecast), so the\nhead is the highest-id row's hash; backtest rows are links too and\nare counted.","operationId":"chain_head_v1_chain_head_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Chain Head V1 Chain Head Get"}}}}}}},"/v1/chain/rows":{"get":{"summary":"Chain Rows","description":"The chain itself, so a receipt's head can be re-derived with no\ntrust in us: every row's hashed fields (foresight_core.hashchain\n.HASH_FIELDS) plus prev_hash/row_hash as hex, in chain (id) order,\nkeyset-paginated. Timestamps are pre-rendered in the exact canonical\nform the hash covers (UTC ISO-8601 from foresight_core.hashchain\n.canonical_json) so a verifier hashes the strings verbatim. Backtest\nrows are included — they are links in the chain and omitting them\nwould make it impossible to recompute.","operationId":"chain_rows_v1_chain_rows_get","parameters":[{"name":"after_id","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"After Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":1000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Chain Rows V1 Chain Rows Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/anchor/latest":{"get":{"summary":"Anchor Latest","description":"The latest daily anchor: the receipt that commits the forecast\nlog's chain head to OpenTimestamps and an RFC 3161 TSA, plus where to\nfetch it and its proofs from. The receipt is written by a separate\njob (packages/anchor) into the public log repo; this box serves the\nfile when it mounts that checkout read-only and answers ``receipt:\nnull`` with the same pointer URLs when it does not — the repo is\nauthoritative either way. ``chain_head`` is LIVE, so it normally sits\nahead of the receipt's ``head_id``. See foresight_api.anchor.","operationId":"anchor_latest_v1_anchor_latest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Anchor Latest V1 Anchor Latest Get"}}}}}}},"/v1/stats":{"get":{"summary":"Stats","description":"Headline archive numbers for the public landing surface.\n\n``data_as_of`` is the newest LIVE forecast's own ``issued_at`` --\ndistinct from ``generated_at``, which is only when THIS RESPONSE was\nbuilt. A stalled forecaster and a healthy one answer identically on\n``generated_at``; ``data_as_of`` is what lets the ops watcher tell\nthem apart. ``null`` on an empty archive, never today's date.\n\n``is_backtest = false`` is not optional here, the same way it is not\noptional for ``live_forecasts`` two lines up: a pastcast sweep writes\nbacktest rows whose own ``issued_at`` can be newer than the live\nforecaster's last real run. Without the filter this field reports\nfresh from a sweep while the live forecaster is stalled -- the exact\nfailure it exists to catch (caught in review by e0dad417 before\ndeploy, not by this file's own test, which never seeded a backtest\nrow; see test_stats_data_as_of_ignores_backtest_rows).\n\nThis is \"newest live forecast issued\", NOT the import-freshness\nstamp from #141 3c (imported_at/data_through). A monitor on this\nfield detects a stalled forecaster, not a stalled push.","operationId":"stats_v1_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Stats V1 Stats Get"}}}}}}},"/v1/digest":{"get":{"summary":"Digest","description":"The period's newsletter body as Markdown — see foresight_api.digest\nfor what goes in and why. Delivery is out of scope here on purpose.","operationId":"digest_v1_digest_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"default":7,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AlertsBody":{"properties":{"crossing":{"anyOf":[{"$ref":"#/components/schemas/RuleUpdate"},{"type":"null"}]},"move":{"anyOf":[{"$ref":"#/components/schemas/RuleUpdate"},{"type":"null"}]},"resolution":{"anyOf":[{"$ref":"#/components/schemas/RuleUpdate"},{"type":"null"}]},"email_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Email Enabled"}},"type":"object","title":"AlertsBody"},"CheckoutBody":{"properties":{"interval":{"type":"string","pattern":"^(monthly|annual)$","title":"Interval","default":"annual"}},"type":"object","title":"CheckoutBody"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"KeyBody":{"properties":{"name":{"type":"string","maxLength":80,"minLength":1,"title":"Name","default":"default"}},"type":"object","title":"KeyBody"},"RiskBatchRequest":{"properties":{"packages":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Packages"},"view":{"type":"string","title":"View","default":"summary"}},"type":"object","required":["packages"],"title":"RiskBatchRequest"},"RuleUpdate":{"properties":{"enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Enabled"},"threshold":{"anyOf":[{"type":"number","exclusiveMaximum":1.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Threshold"},"direction":{"anyOf":[{"type":"string","pattern":"^(below|above)$"},{"type":"null"}],"title":"Direction"}},"type":"object","title":"RuleUpdate"},"SupportBody":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email"},"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"},"subject":{"type":"string","maxLength":200,"minLength":3,"title":"Subject"},"body":{"type":"string","maxLength":5000,"minLength":10,"title":"Body"},"page":{"anyOf":[{"type":"string","maxLength":400},{"type":"null"}],"title":"Page"},"website":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Website"}},"type":"object","required":["email","subject","body"],"title":"SupportBody"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WatchBody":{"properties":{"entity_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Entity Id"}},"type":"object","required":["entity_id"],"title":"WatchBody"}}}}