{"components":{"schemas":{"Allowance":{"description":"`GET /v1/play/allowance`.","properties":{"credits":{"description":"The account's available credits.","format":"int64","type":"integer"},"free_turns_left":{"description":"Free turns this account has left.","format":"int32","type":"integer"},"house_open":{"description":"Whether the house still pays for free turns today.","type":"boolean"},"turn_price_credits":{"description":"What one turn costs once the free turns are used.","format":"int64","type":"integer"}},"required":["free_turns_left","house_open","turn_price_credits","credits"],"type":"object"},"AnswerReq":{"description":"The answer body: the chosen option ids.","properties":{"picks":{"description":"Option ids from the request's `options[].id`, `min..=max` of them, no\nduplicates.","items":{"type":"string"},"type":"array"}},"required":["picks"],"type":"object"},"ApproveReq":{"properties":{"approve":{"description":"The proposal indexes the human approved. Only these are written; an empty\nor absent list is a typed refusal, never \"approve everything\".","example":[0,2],"items":{"minimum":0,"type":"integer"},"type":["array","null"]}},"type":"object"},"AttemptList":{"description":"`GET /v1/play/attempts`.","properties":{"items":{"items":{"$ref":"#/components/schemas/AttemptSummary"},"type":"array"}},"required":["items"],"type":"object"},"AttemptSummary":{"description":"One of your attempts in the list.","properties":{"challenge_id":{"type":"string"},"challenge_slug":{"type":"string"},"challenge_title":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"finished_at":{"format":"date-time","type":["string","null"]},"handle":{"type":["string","null"]},"id":{"type":"string"},"score":{"format":"int32","type":"integer"},"status":{"type":"string"},"support_total":{"format":"double","type":"number"},"turn":{"format":"int32","type":"integer"}},"required":["id","challenge_id","challenge_slug","challenge_title","status","turn","score","support_total","created_at"],"type":"object"},"AttemptView":{"description":"One attempt, owner only.","properties":{"challenge":{"$ref":"#/components/schemas/ChallengeBrief"},"created_at":{"format":"date-time","type":"string"},"credits":{"description":"Credits this attempt has settled.","format":"int32","type":"integer"},"finished_at":{"format":"date-time","type":["string","null"]},"funding":{"description":"`credits` once any turn was paid with credits, else `free`.","type":"string"},"handle":{"type":["string","null"]},"id":{"type":"string"},"max_chars":{"format":"int32","type":"integer"},"score":{"format":"int32","type":"integer"},"slug":{"description":"The challenge's slug (also in `challenge`).","type":"string"},"status":{"description":"`active`, `won`, `lost` or `abandoned`.","type":"string"},"support_total":{"format":"double","type":"number"},"table":{"items":{"$ref":"#/components/schemas/SeatView"},"type":"array"},"turn":{"format":"int32","type":"integer"},"turn_limit":{"format":"int32","type":"integer"},"turns":{"description":"Every stored turn, oldest first.","items":{"$ref":"#/components/schemas/TurnRecord"},"type":"array"},"turns_left":{"format":"int32","type":"integer"},"win_threshold":{"format":"double","type":"number"}},"required":["id","slug","turn_limit","win_threshold","max_chars","challenge","status","turn","turns_left","support_total","score","funding","credits","table","turns","created_at"],"type":"object"},"Attendee":{"properties":{"name":{"description":"Shown only to the account that made this preview. Timepoint's own models\nnever receive it, and no shareable form includes it.","type":["string","null"]},"notes":{"type":["string","null"]},"role":{"type":"string"}},"required":["role"],"type":"object"},"AttendeeIn":{"properties":{"display_name":{"description":"The name, when the calendar supplied one.","type":["string","null"]},"email":{"description":"The address, when the calendar supplied one.","type":["string","null"]},"raw":{"description":"A raw token (`\"Jane Doe <jane@co.com>\"`) — used when neither of the\nabove is given, so typed-in text and calendar data share one parser.","type":["string","null"]}},"type":"object"},"AttestForecastReq":{"description":"Attest request: a structured outcome, the user's free-text account, or both\n(at least one; the structured chip and the detail line compose).","properties":{"note":{"description":"What happened, in the user's words (nonempty, ≤500 chars). Optional when\n`outcome` rides the request; required without it (the original contract,\nunchanged).","example":"the council approved the port expansion on Friday","type":["string","null"]},"outcome":{"description":"One-click structured outcome: `confirmed` (it happened as forecast),\n`closed_differently` (it resolved another way), or `still_open` (no\nobservation yet — mints NOTHING, returns a typed no-op). Any other\nstring is a typed 422 `invalid_outcome`.","example":"confirmed","type":["string","null"]}},"type":"object"},"AuthoredProposals":{"additionalProperties":false,"description":"Hand-authored proposals in K1's own item types.","properties":{"extracted_entities":{"description":"Entity proposals (kind `person`, `org` or `role`; `entity_id` null —\n`/align` sets it). `id` must be new to the kickoff.","items":{"$ref":"#/components/schemas/ExtractedEntity"},"type":"array"},"proposed_edges":{"description":"Edges between entity proposals: `from`/`to` name entity ids in this\nrequest or already in the kickoff; `relation` from the E1 vocabulary.","items":{"$ref":"#/components/schemas/ProposedEdge"},"type":"array"},"scopes":{"description":"Territories (query scopes, never entities).","items":{"$ref":"#/components/schemas/Scope"},"type":"array"}},"type":"object"},"AwarenessReq":{"description":"The awareness switch request. `level` is required when enabling; on disable a\nprovided level updates the kept row's cadence. `research` is tri-state (see\nits field doc) so a level-only PUT can never wipe a live research source.","properties":{"enabled":{"description":"Target switch state: monitoring on (true) or off (false).","type":"boolean"},"level":{"description":"Cadence level — 1 daily · 2 six-hourly · 3 hourly. Required when enabling\n(typed 422 `level_required` otherwise); must be 1..=3 (typed 422\n`invalid_level`).","example":2,"format":"int32","maximum":3,"minimum":1,"type":["integer","null"]},"research":{"description":"Research source, TRI-STATE (G1): field ABSENT = leave `research_source`\nunchanged (the TOOLS level-select PUTs `{enabled, level}` without it);\nexplicit `null` = clear it; a string = set it — closed set, currently only\n`\"openrouter_web\"` (typed 422 `invalid_research_source` otherwise). The\ndouble-Option carries the distinction: outer None = absent, `Some(None)` =\nexplicit null, `Some(Some(s))` = set.","example":"openrouter_web","type":["string","null"]}},"required":["enabled"],"type":"object"},"Basis":{"description":"`docs/kickoff/kickoff.schema.json` `#/$defs/basis`. Deliberately only the two\nbases a kickoff may propose (PLAN.md policy 3: a kickoff never writes\n`ACCUMULATED`, so that variant does not exist here — an incoming `\"ACCUMULATED\"`\nvalue fails to deserialize instead of needing a runtime check).","enum":["GROUNDED","GENERATED"],"type":"string"},"BatchPreviewReq":{"properties":{"action":{"description":"`ack_flags`, `pass`, `request_changes`, `deliver` or `urgent`.","type":"string"},"items":{"description":"1 to 50 items of one client, no duplicates; `ack_flags` names runs only.","items":{"$ref":"#/components/schemas/PreviewItem"},"type":"array"}},"required":["action"],"type":"object"},"BatchRecordReq":{"properties":{"action":{"description":"`ack_flags`, `pass`, `request_changes`, `deliver` or `urgent`.","type":"string"},"items":{"description":"1 to 50 items of one client, no duplicates, each with its hash.","items":{"$ref":"#/components/schemas/RecordItem"},"type":"array"},"note":{"description":"The person's own note (at most 2000 characters, not a placeholder, not\none they recorded before). Required for `ack_flags` and\n`request_changes`.","type":["string","null"]},"phrase":{"description":"The batch's confirmation phrase (from the preview), when the sign-in is\nmore than five minutes old.","type":["string","null"]},"read":{"description":"Must be `true`: the person read every item.","type":["boolean","null"]}},"required":["action"],"type":"object"},"CastApprovalDecisionReq":{"properties":{"note":{"description":"Optional note recorded with the decision (send `{}` for none).","type":["string","null"]}},"type":"object"},"CastSel":{"properties":{"assertion_ids":{"items":{"format":"uuid","type":"string"},"type":"array"},"entity_id":{"format":"uuid","type":"string"}},"required":["entity_id"],"type":"object"},"ChallengeBrief":{"description":"The challenge as its player sees it. Never the hidden table.","properties":{"brief":{"type":"string"},"goal":{"type":"string"},"id":{"type":"string"},"likely_outcome":{"description":"`{label, p, as_of}`: the simulation's read, dated; never an accuracy claim.","type":["object","null"]},"max_chars":{"format":"int32","type":"integer"},"seat":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"turn_limit":{"format":"int32","type":"integer"},"win_threshold":{"format":"double","type":"number"}},"required":["id","slug","title","brief","goal","seat","turn_limit","win_threshold","max_chars"],"type":"object"},"Change":{"properties":{"after":{"format":"int64","type":"integer"},"basis":{"type":"string"},"before":{"format":"int64","type":"integer"},"item":{"type":"string"},"usd_per_credit_at_derivation":{"format":"double","type":"number"},"usd_per_credit_now":{"format":"double","type":"number"}},"required":["item","basis","before","after","usd_per_credit_at_derivation","usd_per_credit_now"],"type":"object"},"ChartReq":{"properties":{"regenerate":{"description":"Replace an existing chart or stored abstention (RED-UX A-2, Sean-ruled):\na fresh paid attempt that overwrites the artifact. Absent/false keeps\nthe attempted-once behavior.","type":"boolean"},"t":{"description":"The timepoint to chart (1..=t_count).","format":"int64","type":"integer"}},"required":["t"],"type":"object"},"ChatReq":{"properties":{"history":{"description":"Client-held history (the server stores no conversation state): at most\nthe last 10 turns, each at most 2000 characters, roles user|assistant.","items":{"$ref":"#/components/schemas/ChatTurn"},"type":"array"},"message":{"description":"The user's question about this run. Nonempty, at most 2000 characters.","example":"what happened in this case?","type":"string"}},"required":["message"],"type":"object"},"ChatTurn":{"description":"One client-held conversation turn.","properties":{"content":{"description":"The turn's text, at most 2000 characters.","type":"string"},"role":{"description":"`user` or `assistant` — anything else is a typed 422.","example":"user","type":"string"}},"required":["role","content"],"type":"object"},"CheckoutReq":{"properties":{"pack":{"type":"string"}},"required":["pack"],"type":"object"},"ClientReq":{"properties":{"consent_to_name":{"description":"Consent to be NAMED (marketing labels etc). Absent = leave as stored;\na brand-new client without it starts FALSE (the schema default).","type":["boolean","null"]},"display_name":{"type":["string","null"]},"note":{"type":["string","null"]},"owner_id":{"type":"string"}},"required":["owner_id"],"type":"object"},"CompileReq":{"description":"The Boss knobs a compile may set, with `POST /v1/runs`' own semantics.\nConstructible in the crate (`CompileReq { parent_run_id: Some(p),\n..Default::default() }`) so other code compiles a kickoff through\n[`compile`], the one entry point.","properties":{"cast_approval_id":{"description":"A usable C1 cast approval of the caller's account; needed when the\napproved cast is above the admin tier.","format":"uuid","type":["string","null"]},"dry_run":{"description":"Track E: `true` runs every compile and admission check and answers 200\nwith the run it would admit (`POST /v1/runs {dry_run}`'s preview plus\n`kickoff_id`); the kickoff stays as it is, no run is minted and nothing\nis reserved. A refusal is listed in `refusals`. Compile only; a\nrecompile refuses it.","type":["boolean","null"]},"models":{"description":"Model picks per role, exactly as `POST /v1/runs {models}` (admin only)."},"parent_run_id":{"description":"Lineage: the run this compile follows from. It must be a run that is\nnot withdrawn, in the SAME space this compile acts in (a workspace\nkickoff: a run of that workspace; a personal kickoff: your own personal\nrun). Unknown, another space's and withdrawn ids are one typed 422\n`invalid_parent_run`. Frozen as the run's `parent_run_id` and recorded on\nits `run.accepted` event as `lineage: {parent_run_id, relation: \"child\"}`.","format":"uuid","type":["string","null"]},"sim_params":{"additionalProperties":{"format":"double","type":"number"},"description":"Per-run simulation parameters, exactly as `POST /v1/runs {sim_params}`\n(bounded; `admin_only` keys need the `admin` capability).","propertyNames":{"type":"string"},"type":["object","null"]}},"type":"object"},"Confidence":{"description":"A confidence value in `[0, 1]`, or absent. `#/$defs/confidence`.\n\nPlain `Option<f64>` cannot express the schema's `minimum`/`maximum`, so this\nwrapper validates on the way in: `serde_json::from_str` rejects an out-of-range\nvalue with a `serde::de::Error`, the same failure mode as every other schema\nviolation in this module, rather than needing a second validation pass.","format":"double","type":["number","null"]},"ConfirmAttendee":{"properties":{"display_name":{"type":["string","null"]},"email":{"type":["string","null"]},"entity_id":{"description":"RE-POINT: attach to THIS existing person instead of resolving by name.\nAbsent → resolve-or-mint from the name.","format":"uuid","type":["string","null"]},"raw":{"type":["string","null"]}},"type":"object"},"ConfirmEvent":{"properties":{"all_day_on":{"description":"The bare date, when the event is ALL DAY. Never both.","type":["string","null"]},"id":{"description":"The calendar event's id — the provenance key.","type":"string"},"starts_at":{"description":"RFC 3339 start instant, when the event has one.","type":["string","null"]},"title":{"type":["string","null"]}},"required":["id"],"type":"object"},"ConfirmReq":{"properties":{"items":{"description":"Only these held items (default: every held item of that record).","items":{"type":"string"},"type":["array","null"]},"reprice_id":{"description":"The `ledger.reprices` record whose held moves to apply.","format":"int64","type":"integer"}},"required":["reprice_id"],"type":"object"},"ConsoleAssertionReq":{"description":"One assertion a PERSON is adding: typed by hand, or approved from a link.","properties":{"attribute":{"description":"The property name — kebab-case, ≤64 chars (the gate's own rule).","example":"current-role","type":"string"},"note":{"description":"Optional note recorded alongside the provenance, ≤280 chars.","type":["string","null"]},"source_url":{"description":"The link this value was approved FROM, when it came from one. Present\n⇒ the row's provenance names that document; absent ⇒ the row records\nthat the person typed it themselves. Never inferred.","type":["string","null"]},"value":{"description":"The value, as the person states it. Nonempty, ≤500 chars.","example":"Chair of the Federal Reserve","type":"string"}},"required":["attribute","value"],"type":"object"},"CreateCastApprovalReq":{"properties":{"max_cast":{"description":"The largest cast (N) the approval should allow. Must be above the admin\ntier (`deep_sim_cast_entity_max`'s row max, 20) and at most\n`cast_approval_max_cast` (default 50).","example":25,"format":"int64","type":"integer"},"reason":{"description":"Why this account needs a cast this large. Non-empty.","example":"Sector-wide scenario naming every regional utility","type":"string"},"valid_days":{"description":"Days the approval stays usable once approved; default and maximum from\n`cast_approval_valid_days` (default 30, max 365).","format":"int64","type":["integer","null"]}},"required":["max_cast","reason"],"type":"object"},"CreateDrilldownJobReq":{"additionalProperties":false,"properties":{"dry_run":{"description":"Track E: `true` runs every check (the administrator rule, the scope or\nroot, levels, depth and templates) and answers 200 with `{dry_run,\nwould_admit, job, caps_usd, research_enabled, research_note, refusals,\nreserves: \"nothing\"}`: the job and the pass caps it would freeze, and\n`job.first_step`: the searches its first research call would run\n(every query of its plan) and their Exa estimate. No job, pass or\nbudget row is written.","type":"boolean"},"kickoff_id":{"description":"Link this pass to a kickoff in the same space (optional; required with\n`kickoff_scope_id`).","format":"uuid","type":["string","null"]},"kickoff_scope_id":{"description":"One of that kickoff's scope proposals, by id — must already be\nAPPROVED. Mutually exclusive with `scope_name` and `root_entity_id`.","type":["string","null"]},"max_level":{"description":"Deepest level, 0..=4 (L0..L4), frozen on the job (D6). Default: the\n`research_max_level_default` sim param (seeded 2, stopping before\npeople) for a territory pass; one level past the root's start for a\nrooted one, but never past that default without asking — a role or\nperson root needs an explicit 3 or 4.","format":"int64","type":["integer","null"]},"min_level":{"description":"A rooted pass only (`root_entity_id`): the level to start at, 0..=4\n(L0..L4), frozen on the job. Default: the root's own start (org 2,\nrole 3, person 4). A later start must still research the root's kind:\nan org may start at 2 or 4 (4 alone is its profile, policy and\nposition pass, without its roles or people), a role at 3 or 4, a\nperson at 4. Any other value, one past `max_level`, or one on a\nterritory pass is 422. A dry run echoes the start it resolved as\n`job.min_level`.","format":"int64","type":["integer","null"]},"profile_queries":{"description":"A rooted pass only: the Exa query templates for an org's L4 `profile`\nfacts, frozen on the job (a deepen of it inherits them). 1 to 8\ntemplates of at most 200 characters, each naming the org as `{name}`;\n`{place}` (the scope's state, when it names one) is the only other\nplaceholder, and no other brace is allowed. Default: generic built-in\ntemplates. Anything else, or templates on a territory pass, is 422.","items":{"type":"string"},"type":["array","null"]},"root_entity_id":{"description":"Drill an existing entity (org, role or person) instead of a territory;\nits kind fixes the start level (`drilldown::start_level_for`).","format":"uuid","type":["string","null"]},"scope_name":{"description":"A territory name, when not naming a kickoff scope. Mutually exclusive\nwith `root_entity_id`.","type":["string","null"]},"search_depth":{"description":"Search depth in result pages, 1 (the first page of results) to 100,\nbounded by the `research_search_depth_default` sim param's min/max;\ndefault that param (seeded 1). Frozen on the job; each input's pages\nare weighted by its centrality, and spend scales with it (D6).","format":"int64","type":["integer","null"]}},"type":"object"},"CreateEdgeReq":{"description":"`POST /v1/edges` request body.","properties":{"attributes":{"description":"TT's closed per-relation attribute schema (`valid_from`/`valid_to`/`subject`,\ndepending on the relation). Absent means `{}`."},"basis":{"description":"GROUNDED (needs `source_urls`) or GENERATED — never ACCUMULATED (that\nbasis is written only by a run, through a different door).","example":"GROUNDED","type":"string"},"from_entity_id":{"format":"uuid","type":"string"},"observed_at":{"description":"When the evidence was seen. Defaults to now when omitted.","format":"date-time","type":["string","null"]},"relation_kind":{"description":"A TT `tt-relations/1.0` relation id, e.g. `holds-office`, `knows`.","example":"holds-office","type":"string"},"score":{"description":"Belief that the edge holds as stated, in (0, 1].","example":0.9,"format":"float","type":"number"},"source_urls":{"items":{"type":"string"},"type":"array"},"to_entity_id":{"format":"uuid","type":"string"}},"required":["from_entity_id","to_entity_id","relation_kind","basis","score"],"type":"object"},"CreateEntityReq":{"description":"The creation request: a kind and a human name — resolution is by name only.","properties":{"display_name":{"description":"The display name; trimmed, nonempty, ≤200 chars. Resolution slugifies this\nname — POSTing the same (kind, name) again resolves to the SAME entity.","example":"Jerome Powell","maxLength":200,"type":"string"},"kind":{"description":"person | org | market | role (the schema's four legal kinds).","example":"person","type":"string"}},"required":["kind","display_name"],"type":"object"},"CreateInviteReq":{"properties":{"sponsor_cap_cr":{"description":"A CEILING on credits moved from YOUR balance to the agent at redemption\n(default 0 — sponsor nothing). Nothing is held now: the transfer is\nchecked against your real balance at redeem time, and a short balance\nsponsors what exists and says so.","format":"int64","type":["integer","null"]},"trial_cr":{"description":"Free trial credits the redeeming agent is granted, `0..=100`\n(default 25). Granted from nothing — this is the house's welcome, not\nyour balance.","format":"int64","type":["integer","null"]},"ttl_days":{"description":"Days until the code expires, `1..=30` (default 7).","format":"int64","type":["integer","null"]}},"type":"object"},"CreateKickoffReq":{"properties":{"dry_run":{"description":"Track E: `true` runs every check, including the proposal checks, inside\na transaction that is rolled back, and answers 200 with `{dry_run,\nwould_admit, kickoff, refusals, reserves: \"nothing\"}`: the document\nthat would be created. Nothing is saved.","type":"boolean"},"extracted_entities":{"description":"Optional admin-authored proposals (Part E): the same items and checks\nas `POST /v1/kickoffs/{id}/proposals`. Any present requires the\nadministrator rule (`approvals::can_approve`), and the kickoff then\nstarts `extracted`.","items":{"$ref":"#/components/schemas/ExtractedEntity"},"type":"array"},"notes":{"description":"Raw notes, one entry each; stored with the caller as author.","items":{"type":"string"},"type":"array"},"proposed_edges":{"items":{"$ref":"#/components/schemas/ProposedEdge"},"type":"array"},"question":{"description":"The objective in the client's own words. Required before compiling.","type":["string","null"]},"scopes":{"items":{"$ref":"#/components/schemas/Scope"},"type":"array"},"title":{"description":"Short human label. Non-empty; no client names.","example":"Q3 go-to-market, synthetic state A","type":"string"}},"required":["title"],"type":"object"},"CreateRunReq":{"properties":{"applicant_entity_id":{"description":"find_grants only: the workspace entity that applies (the client whose\nproject the query describes). It must be an entity of the named\nworkspace (`422 applicant_not_in_workspace`).","format":"uuid","type":["string","null"]},"applicant_paths":{"description":"find_grants only: how the applicant may apply, `direct` (as itself)\nand/or `sponsored` (as a fiscally sponsored project). Default\n`[\"direct\"]`; anything else is `422 invalid_applicant_paths`.","example":["direct","sponsored"],"items":{"type":"string"},"type":["array","null"]},"cast":{"description":"Owner-scoped actors to include. Private Pro/Portal only; never with\n`publication_id`. At most `deep_sim_cast_entity_max` entities (default\n8; an admin may set up to 20; above 20 needs `cast_approval_id`) and\n`deep_sim_cast_fact_max` facts (3 per entity unless set).","items":{"$ref":"#/components/schemas/CastSel"},"type":["array","null"]},"cast_approval_id":{"description":"C1: an approved, unexpired, unrevoked cast approval of the caller's\naccount (`POST /v1/cast-approvals`). Raises this run's entity cap to its\nN (facts to 3 × N); required for a cast above 20. Frozen into the run as\n`cast_approval_id` + `cast_approval_max_cast`. deep_sim/portal_sim only.","format":"uuid","type":["string","null"]},"depth":{"description":"conference_sim only: `\"quick\"` (default), `\"standard\"`, or `\"deep\"` —\na named preset over deep_sim's existing `deep_sim_t_count`/\n`deep_sim_dialog_lines` sim_params, never a raw numeric dial. This\nkind is admin-only (an internal marketing-demo tool), so a non-`quick`\ndepth's admin-only sim_param overrides are never refused for it.","example":"standard","type":["string","null"]},"dry_run":{"description":"Track E: `true` runs every admission check (validation, cast and\nparams, the balance gate, the price and the credit balance) and answers\n200 with what the run would be, `{dry_run, would_admit, kind,\nresolved, caps, quote, balance, refusals, reserves: \"nothing\"}`. Nothing\nis reserved or written, and a refusal is listed in `refusals` with the\nstatus and body the real call would give.","type":["boolean","null"]},"geography":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/GrantGeography","description":"find_grants only: where to look, `{city?, county?, region?, state?,\nfederal?}` inside ONE US state (a second state is a separate run). The\npass searches local first: city, county, region, state, multi_state,\nthen federal (default true). Absent: the federal level only. An unknown\nlevel, a non-state, or a place without its state is `422\ninvalid_geography`."}]},"kind":{"description":"Run kind — `quick_sim`, `story_sim`, `news_scan`, `deep_sim`,\n`find_money`, `find_grants`, or `conference_sim`; anything else is a\ntyped 422.","example":"quick_sim","type":"string"},"models":{"description":"Private model picks (MR2): `{planning?, simulation?, dialog?, synthesis?,\nresearch_shallow?, research_deep?, interpretation?, synthesis_mode?:\nsingle|alternate|ab, synthesis_pair?: [a, b]}`; omitted roles take the\ndeployment defaults (`GET /v1/models`). `ab` and `synthesis_*` are deep_sim\nonly; `ab` is priced (see `model_roles`). Frozen into the run as\n`resolved_models` + `resolved_synthesis`. Never with `publication_id`. **Editor\ncapability** — an account without `admin` is refused 403 `forbidden`\n(T2.0): the credit price is flat per run, so an open model pick would let\nthe caller choose our cost. `cast` carries no such gate; it is priced per\nactor and owner-scoped."},"original_query":{"description":"Original user text before work context was appended. Pro only.","type":["string","null"]},"parent_run_id":{"description":"quick_sim: the what-if question — nonempty, at most 500 characters.\ndeep_sim: the deep question — nonempty, at most 2000 characters\n(CONTRACTS §11.11 B4.1). find_grants: the administrator's eligibility\nscope (applicant type, field, geography, award size) — nonempty, at\nmost 500 characters.\nOptional lineage: the run this one supersedes (re-run with today's world).","format":"uuid","type":["string","null"]},"publication_id":{"description":"Editor-owned public research project. Private account memory is excluded.","format":"uuid","type":["string","null"]},"query":{"example":"What if BeachSat launches a subscription tier next quarter?","type":["string","null"]},"scenario_requirements":{"description":"Explicit constraints retained independently of model extraction. Pro only.","items":{"$ref":"#/components/schemas/Requirement"},"type":["array","null"]},"sim_params":{"additionalProperties":{"format":"double","type":"number"},"description":"Per-run simulation parameters (G8): `{key: number}` over the\n`run.sim_params` keys. `deep_sim` takes the `deep_sim_*` keys;\n`portal_sim` takes the `portal_*` keys and the two cast caps. Any other\nkind, an unknown key, a non-number, an out-of-bounds value or a\nfractional int is a typed 422 `invalid_sim_params`; a key whose row is\n`admin_only` from an account without `admin` is 403 `forbidden`. Nothing\nis reserved on either refusal. The resolved set (defaults + overrides)\nis frozen into the run at admission; a larger shape prices higher\n(`deep_sim`: the plan-time quote; `portal_sim`: the admission hold).","example":{"deep_sim_t_count":8},"propertyNames":{"type":"string"},"type":["object","null"]},"url":{"description":"story_sim/conference_sim: the URL to fetch and simulate (CONTRACTS\n§11.8; conference_sim requires an Article-shaped page, never a tweet\nor a prediction market). Required for these kinds; http/https, at\nmost 2000 characters.","example":"https://en.wikipedia.org/wiki/OpenAI","type":["string","null"]}},"required":["kind"],"type":"object"},"DecideReq":{"description":"Decide request: the three-verb loop plus an optional margin note.","properties":{"choice":{"description":"`did` | `not_now` | `not_this`.","example":"did","type":"string"},"note":{"description":"Optional note (≤2000 chars). Persisted only on `did` (inside the minted\nmoment's payload, outside the content hash) — the 0012 schema has no\nnote column; other choices acknowledge + log it, stated.","type":["string","null"]}},"required":["choice"],"type":"object"},"DecisionReq":{"properties":{"agent_note":{"description":"Approve under a grant: what the agent is doing, 10 to 500 characters.","type":["string","null"]},"grant_id":{"description":"Approve only: an agent approving under a person's approval grant names\nit here (see `POST /v1/staged-actions/grants`). Ignored for a person who\nmay approve directly.","format":"uuid","type":["string","null"]},"grant_note":{"description":"Approve under a grant: the grant's note, restated exactly.","type":["string","null"]},"note":{"description":"Optional note recorded with the decision (send `{}` for none).","type":["string","null"]}},"type":"object"},"DeepenDrilldownJobReq":{"additionalProperties":false,"properties":{"max_level":{"description":"Deepest level, 0..=4 (L0..L4), frozen on each child job. Default: D3's\ndepth policy (one level past the root's start) CAPPED at the parent\njob's max level (D6) — going deeper than the parent's max (e.g. an\norg's people, L3, under an L2 pass) takes an explicit value here.","format":"int64","type":["integer","null"]},"min_level":{"description":"The level each child job starts at, 0..=4 (L0..L4), frozen on it.\nDefault: each root's own start (org 2, role 3, person 4). A later\nstart must still research the root's kind: an org at 4 is its\nprofile, policy and position pass, without its roles or people (an\norg may start at 2 or 4, a role at 3 or 4, a person at 4). A start\nthat does not fit a root, or is past `max_level`, is 422 for a single\nroot and that root's entry in `errors` for several. An org's L4\ndeepen and its default deepen are separate child jobs.","format":"int64","type":["integer","null"]},"profile_queries":{"description":"The Exa query templates for an org's L4 `profile` facts, frozen on\neach child job. 1 to 8 templates of at most 200 characters, each\nnaming the org as `{name}`; `{place}` (the scope's state, when it\nnames one) is the only other placeholder, and no other brace is\nallowed; anything else is 422. Default: the parent job's, else\ngeneric built-in templates.","items":{"type":"string"},"type":["array","null"]},"roots":{"description":"1 to `drilldown_depth::MAX_FANOUT` roots, each naming exactly one of\n`entity_id`/`proposal_id`. One root deepens it directly; several open\none child job per root, up to the fan-out cap (`truncated` in the\nresponse says when the list was cut).","items":{"$ref":"#/components/schemas/DeepenRootReq"},"type":"array"},"search_depth":{"description":"Search depth in result pages, 1–100 (bounded by the sim param);\ndefault the parent job's (D6).","format":"int64","type":["integer","null"]}},"required":["roots"],"type":"object"},"DeepenRootReq":{"properties":{"entity_id":{"description":"Deepen a live entity, by id.","format":"uuid","type":["string","null"]},"proposal_id":{"description":"Deepen a pending or approved entity proposal, by id.","format":"uuid","type":["string","null"]}},"type":"object"},"DirectoryExcluded":{"description":"The active identities the metric removed as Timepoint's own.","properties":{"clients":{"description":"Client logins (`entity.clients`), not also internal. A client's\n`member:` identities are not client logins.","format":"int64","type":"integer"},"internal":{"description":"Holding a verified address at Timepoint's own domain (`timepointai.com`).\nAn identity that is also a client login counts here, once.","format":"int64","type":"integer"}},"required":["internal","clients"],"type":"object"},"DirectoryMetric":{"description":"`GET /v1/admin/metrics/directory`: counts only.","properties":{"active_identities":{"description":"Distinct identities active in the window: not administrators, agent or\nservice identities, internal accounts or client logins. One person can\nhold more than one identity (GitHub, email, member), so this can count\na person more than once.","format":"int64","type":"integer"},"excluded":{"$ref":"#/components/schemas/DirectoryExcluded","description":"Active identities left out of `active_identities`, by reason."},"via_mcp":{"description":"How many of `active_identities` made an MCP tools/call in the window.","format":"int64","type":"integer"},"window_days":{"description":"The trailing window, in days (7).","format":"int32","type":"integer"}},"required":["window_days","active_identities","via_mcp","excluded"],"type":"object"},"DisplayNameReq":{"description":"A display-name correction. The path and id never move.","properties":{"display_name":{"description":"The corrected name; trimmed, nonempty, ≤200 chars.","example":"Jerome H. Powell","maxLength":200,"type":"string"}},"required":["display_name"],"type":"object"},"EmailReq":{"properties":{"email":{"type":"string"},"owner_id":{"type":"string"}},"required":["owner_id","email"],"type":"object"},"EngagementReq":{"properties":{"client_owner":{"type":"string"},"question_key":{"type":"string"},"side":{"type":"string"},"workspace_id":{"description":"W3 (gate G7): link this engagement to a workspace, so its delivered\nresult becomes visible to that workspace's providers and admin, not\nonly to `client_owner`. Optional and additive: omit it and the\nengagement behaves exactly as before workspaces existed. When given,\n`client_owner` must already hold a `client` row in that workspace —\nthe 0055 exclusivity trigger guarantees that is the ONLY workspace\nthey hold a client row in, so this never lets one engagement point at\na workspace the client does not belong to.","format":"uuid","type":["string","null"]}},"required":["client_owner","question_key","side"],"type":"object"},"Error":{"description":"Every refusal body: a stable `error` code, and for some a `reason` in words. A 429 adds `retry_after_secs`.","properties":{"error":{"description":"A stable code, such as not_found, bad_id, unavailable or rate_limited.","type":"string"},"reason":{"type":"string"},"retry_after_secs":{"type":"integer"}},"required":["error"],"type":"object"},"ExtractedEntity":{"additionalProperties":false,"properties":{"basis":{"$ref":"#/components/schemas/Basis"},"confidence":{"$ref":"#/components/schemas/Confidence"},"entity_id":{"format":"uuid","type":["string","null"]},"id":{"type":"string"},"kind":{"type":"string"},"name":{"type":"string"},"notes":{"type":["string","null"]},"sources":{"items":{"type":"string"},"type":"array"}},"required":["id","kind","name","basis","sources","confidence"],"type":"object"},"FileQaReq":{"properties":{"note":{"type":"string"},"pass":{"type":"boolean"},"sha256":{"description":"The hash of what the reader read: the file's current sha256, or for a\nfilm its current `review_sha256` (the film plus its poster and captions).","type":"string"}},"required":["pass","note","sha256"],"type":"object"},"FreemiumMetric":{"description":"`GET /v1/admin/metrics/freemium`: counts only, for today (UTC).","properties":{"day":{"description":"The UTC day counted, `YYYY-MM-DD`.","type":"string"},"new_accounts":{"description":"Accounts created today (`ledger.accounts`, one per identity at signup).","format":"int64","type":"integer"},"new_accounts_with_first_preview":{"description":"Of `new_accounts`, how many ran a first preview today: a `quick_sim`\nrun or a meeting preview, free or paid.","format":"int64","type":"integer"},"play_turns":{"$ref":"#/components/schemas/HouseToday","description":"Free Play turns the house paid for today vs its daily cap."},"previews":{"$ref":"#/components/schemas/HouseToday","description":"Free quick previews the house paid for today vs its daily cap."},"refunded_today":{"description":"Free claims refunded today because the work failed (both kinds).","format":"int64","type":"integer"}},"required":["day","new_accounts","new_accounts_with_first_preview","previews","play_turns","refunded_today"],"type":"object"},"GrantDecisionReq":{"description":"A decision's body: an optional note on approve, a required one on reject.","properties":{"note":{"description":"Why (at most 1000 characters). Required to reject.","type":["string","null"]}},"type":"object"},"GrantGeography":{"description":"Where a find_grants run looks: a place tree inside ONE US state (a second\nstate is a separate run), plus the federal level unless `federal` is false.","properties":{"city":{"description":"A city in `state`.","type":["string","null"]},"county":{"description":"A county in `state`.","type":["string","null"]},"federal":{"description":"Search the federal level too (default true).","type":["boolean","null"]},"region":{"description":"A named region of `state` (a metro area, a valley).","type":["string","null"]},"state":{"description":"One US state, by name or postal code (stored by name).","type":["string","null"]}},"type":"object"},"GrantReq":{"properties":{"note":{"description":"Your note to the agent, 10 to 500 characters. The agent must restate it\nexactly when it approves.","type":"string"},"staged_action_ids":{"description":"The staged actions an agent may approve: 1 to 20, each open and of a\ngrantable kind (`deliver_review`, `deliver_wrapup`, `deliver_file`).","items":{"format":"uuid","type":"string"},"type":"array"}},"required":["staged_action_ids","note"],"type":"object"},"GrantResumeReq":{"description":"The resume body.","properties":{"run_id":{"description":"The find_grants run waiting on its proposals.","format":"uuid","type":"string"}},"required":["run_id"],"type":"object"},"HandleReq":{"additionalProperties":false,"description":"`POST /v1/play/attempts/{id}/handle`.","properties":{"handle":{"description":"3 to 24 characters of letters, digits, `_` and `-`; unique on the board.","type":"string"}},"required":["handle"],"type":"object"},"Health":{"description":"Liveness plus the degraded-mode signals. Values are states, never secrets.","properties":{"account_headroom_usd":{"description":"A number only when a management key is configured and the provider answered; null is a labelled absence.","type":["number","null"]},"balance_gate":{"description":"The LLM admission gate's label.","enum":["ok","no_key","no_headroom","budget_exhausted","unknown"],"type":"string"},"calendar":{"enum":["on","off"],"type":"string"},"db":{"type":"boolean"},"ok":{"description":"The database answered.","type":"boolean"},"provider_supply":{"description":"`metered`: the balance preflight runs; `unlimited`: capacity is provisioned and nothing is asked.","enum":["metered","unlimited"],"type":"string"},"public_base":{"format":"uri","type":"string"},"signin_methods":{"description":"The sign-in methods that work now.","properties":{"email":{"type":"boolean"},"github":{"type":"boolean"},"google":{"type":"boolean"}},"required":["email","github","google"],"type":"object"},"signup":{"enum":["open","closed","allowlist"],"type":"string"},"trajectory":{"enum":["on","off"],"type":"string"},"version":{"type":"string"},"worker_role":{"enum":["shared","sealed"],"type":"string"},"worker_state":{"enum":["draining","reconnecting","standby","starting","stopped","off"],"type":"string"}},"required":["ok","version","db","balance_gate","provider_supply","signup"],"type":"object"},"HouseToday":{"description":"One kind's house use today.","properties":{"cap":{"description":"The house's daily cap for this kind.","format":"int32","type":"integer"},"used":{"description":"Free uses claimed today and not refunded.","format":"int32","type":"integer"}},"required":["used","cap"],"type":"object"},"HumanReadReq":{"properties":{"evidence_hash":{"description":"The evidence hash of the version the reader read, as `GET\n…/evidence` showed it. Required; must be the current hash.","type":["string","null"]},"note":{"type":"string"},"reader":{"description":"Retired: the reader is the signed-in caller. Accepted only when it\nnames the caller.","type":["string","null"]}},"required":["note"],"type":"object"},"ImportPreviewReq":{"description":"The link a person wants read for this dossier.","properties":{"url":{"description":"http/https, ≤2000 chars.","example":"https://en.wikipedia.org/wiki/Jerome_Powell","type":"string"}},"required":["url"],"type":"object"},"IntakeReq":{"properties":{"client_owner":{"type":"string"},"engagement_id":{"format":"uuid","type":"string"},"og_description":{"description":"What is being decided and when it resolves, ≤~200 chars — the social\ncard's description. NEVER the leading outcome.","type":["string","null"]},"og_title":{"description":"The question, plainly, ≤~70 chars — the social card's title. NEVER the\nanswer (tp-public-run/1.2, campaign rule).","type":["string","null"]},"reality_note":{"description":"When the SUBJECT is real: the sentence separating the real event from\nthe invented scenario. Prefixes the provenance string in the manifest.","type":["string","null"]},"resolution_criterion":{"description":"How the public entry resolves, stated up front — rendered on the post\n(tp-public-run/1.1). Optional: client work has no public resolution.","type":["string","null"]},"run_id":{"format":"uuid","type":"string"},"summary":{"description":"A summary, ≤ 160 characters (R1). Trimmed; an empty or longer value is\nrefused, never cut. Absent on first intake: `og_description` when it\nfits, else derived from the run's question.","type":["string","null"]},"title":{"description":"A short title, ≤ 60 characters, shown in every list (R1). Trimmed; an\nempty or longer value is refused, never cut. Absent on first intake:\n`og_title` when it fits, else derived from the run's question.","type":["string","null"]}},"required":["run_id","client_owner","engagement_id"],"type":"object"},"LinkReq":{"properties":{"workspace_id":{"format":"uuid","type":"string"}},"required":["workspace_id"],"type":"object"},"MatchReq":{"properties":{"attendees":{"items":{"$ref":"#/components/schemas/AttendeeIn"},"type":"array"}},"required":["attendees"],"type":"object"},"MediaUploadReq":{"properties":{"captions_for":{"description":"For a text/vtt captions file: the film (file id) it belongs to.","format":"uuid","type":["string","null"]},"content_type":{"description":"One of video/mp4, image/png, image/jpeg, text/vtt.","type":"string"},"folder":{"description":"Folder; default \"Films\".","type":["string","null"]},"name":{"description":"File name within the folder, 1-160 characters, no slashes, not hidden.","type":"string"},"poster_for":{"description":"For a png/jpeg poster: the film (file id) it belongs to.","format":"uuid","type":["string","null"]},"provenance":{"description":"For a film: its render's provenance record (`render.json`), a JSON object of at most 16 KB.","type":["object","null"]},"run_id":{"description":"For a film: the run it depicts (a run of this engagement whose QA a person passed).","format":"uuid","type":["string","null"]},"sha256":{"description":"SHA-256 of the exact bytes, hex.","type":"string"},"size_bytes":{"description":"Exact byte length: at most 500 MB for video/mp4, 10 MB otherwise.","format":"int64","type":"integer"},"title":{"type":["string","null"]}},"required":["name","content_type","size_bytes","sha256"],"type":"object"},"MeetingIn":{"properties":{"agenda":{"type":["string","null"]},"attendees":{"items":{"$ref":"#/components/schemas/Attendee"},"type":"array"},"title":{"type":"string"},"when":{"description":"When it happens (an ISO date-time or a plain date).","type":"string"}},"required":["title","when"],"type":"object"},"MeetingPreviewReq":{"description":"The meeting to preview and what you want out of it.","properties":{"calendar_event_id":{"type":["string","null"]},"invite_text":{"type":["string","null"]},"meeting":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/MeetingIn","description":"The meeting, spelled out. Exactly one of `meeting`, `calendar_event_id`\nand `invite_text`."}]},"want":{"description":"What you want out of it (at most 500 characters).","type":"string"}},"required":["want"],"type":"object"},"MemberReq":{"properties":{"name":{"type":"string"},"role":{"type":"string"}},"required":["name","role"],"type":"object"},"MergeReq":{"properties":{"from":{"description":"The duplicate row to retire.","type":"string"},"into":{"description":"The row that survives (usually the client's login identity).","type":"string"}},"required":["from","into"],"type":"object"},"OnboardingReq":{"properties":{"company":{"type":"string"},"goals":{"type":"string"},"role":{"type":"string"},"what":{"type":"string"},"work":{"type":"string"},"you":{"type":"string"}},"required":["you","role","work","company","what","goals"],"type":"object"},"OutcomeReq":{"properties":{"happened":{"description":"What happened: free text, a short code, or `true`/`false`. Text that\nsays it did not happen (\"no\", \"did not happen\", \"no_term_sheet\", …)\nsettles the forecast as not having occurred.","type":"object"},"id":{"description":"The meeting preview (`mp_…`) or pin (`pin_…`) to settle.","type":"string"},"occurred_at":{"description":"When it happened (RFC 3339), if known.","type":["string","null"]}},"required":["id","happened"],"type":"object"},"Override":{"description":"An owner's correction, stored on the profile: an event whose title\ncontains `title_contains` (case-insensitive) belongs in `category`. The\nowner typed or tapped it, which is why it may hold title text at all.","properties":{"category":{"type":"string"},"title_contains":{"type":"string"}},"required":["title_contains","category"],"type":"object"},"PinReq":{"properties":{"by":{"description":"The date to reach it by, `YYYY-MM-DD`, after today.","type":"string"},"outcome":{"description":"The outcome to reach (at most 500 characters).","type":"string"},"start":{"description":"Where you are starting from (optional, at most 500 characters).","type":["string","null"]}},"required":["outcome","by"],"type":"object"},"PlayMetric":{"properties":{"challenges_live":{"description":"Challenges live right now.","format":"int64","type":"integer"},"day":{"description":"The UTC day counted as \"today\", `YYYY-MM-DD`.","type":"string"},"last_7_days":{"$ref":"#/components/schemas/PlayWindow"},"per_turn_cost_cap_usd":{"description":"The per-turn cost cap the estimate multiplies by, in USD.","format":"double","type":"number"},"today":{"$ref":"#/components/schemas/PlayWindow"},"top_challenges":{"description":"Top 5 challenges by attempts started in the last 7 days.","items":{"$ref":"#/components/schemas/PlayTopChallenge"},"type":"array"}},"required":["day","challenges_live","per_turn_cost_cap_usd","today","last_7_days","top_challenges"],"type":"object"},"PlayTopChallenge":{"description":"One challenge in the top list.","properties":{"attempts":{"description":"Attempts started on it in the last 7 days.","format":"int64","type":"integer"},"slug":{"type":"string"},"title":{"type":"string"},"won":{"format":"int64","type":"integer"}},"required":["slug","title","attempts","won"],"type":"object"},"PlayWindow":{"description":"One window of the funnel.","properties":{"attempts_abandoned":{"format":"int64","type":"integer"},"attempts_lost":{"format":"int64","type":"integer"},"attempts_started":{"description":"Attempts started in the window.","format":"int64","type":"integer"},"attempts_won":{"description":"Attempts that finished in the window, by outcome.","format":"int64","type":"integer"},"distinct_players":{"description":"Distinct players who started an attempt or stored a turn in the window.","format":"int64","type":"integer"},"estimated_house_cost_usd_upper_bound":{"description":"UPPER BOUND on what the house paid: free turns x the per-turn cap.","format":"double","type":"number"},"failed_calls":{"description":"Judge calls that stored no turn, summed over attempts started in the\nwindow (`play.attempts.failed_calls`, both funding paths).","format":"int64","type":"integer"},"median_turns_per_finished_attempt":{"description":"Median turns reached by attempts finished in the window; null when none.","format":"double","type":["number","null"]},"model_calls":{"description":"House-funded model calls in the window (`freemium.house_days.calls`,\nevery free claim, refunded or not). Credit-funded calls are not here.","format":"int64","type":"integer"},"turns_credits":{"format":"int64","type":"integer"},"turns_free":{"description":"Turns stored in the window, by what paid for them.","format":"int64","type":"integer"},"win_rate":{"description":"won / (won + lost) over attempts finished in the window; null when none\nwas won or lost. Abandoned attempts are not counted either way.","format":"double","type":["number","null"]}},"required":["attempts_started","attempts_won","attempts_lost","attempts_abandoned","turns_free","turns_credits","distinct_players","model_calls","failed_calls","estimated_house_cost_usd_upper_bound"],"type":"object"},"PortfolioReq":{"description":"`POST /v1/trajectory/portfolio`.","properties":{"weeks":{"description":"The trailing window in weeks, 4 to 12; null or absent reads the\nprofile's `window_weeks`.","format":"int64","type":["integer","null"]}},"type":"object"},"PostResponseReq":{"properties":{"body":{"description":"Plain text, 1 to 2000 characters.","type":"string"},"item_id":{"description":"Run id (result) or wrap-up id (wrapup).","format":"uuid","type":"string"},"item_kind":{"description":"`result` or `wrapup`.","type":"string"},"kind":{"description":"`comment` (a note on the item) or `follow_up` (a question to be answered next).","type":"string"}},"required":["item_kind","item_id","kind","body"],"type":"object"},"PreliminaryReq":{"properties":{"evidence_hash":{"type":["string","null"]},"note":{"type":"string"},"pass":{"type":"boolean"}},"required":["pass","note"],"type":"object"},"PreviewItem":{"description":"One item named in a batch preview.","properties":{"id":{"type":"string"},"kind":{"description":"`run` (id = run id), `file` (id = file id) or `wrapup` (id = engagement id).","type":"string"}},"required":["kind","id"],"type":"object"},"PreviewReq":{"properties":{"reason":{"description":"Why, for the person who approves it. Nonempty, at most 2000 characters.","type":"string"},"research_at_risk":{"description":"`refuse` (default): the retraction cannot run while an approved research\nfact rests only on rows it would delete. `keep`: keep those rows.","type":["string","null"]},"targets":{"description":"The items to retract (at most 500).","items":{"$ref":"#/components/schemas/PreviewTarget"},"type":"array"},"workspace_id":{"description":"The workspace every target must belong to.","type":"string"}},"required":["workspace_id","targets","reason"],"type":"object"},"PreviewTarget":{"properties":{"id":{"description":"The item's id (a run id, kickoff id, wrap-up id or client file id).","type":"string"},"type":{"description":"`run`, `kickoff`, `wrapup` or `file`.","example":"run","type":"string"}},"required":["type","id"],"type":"object"},"PricingParams":{"properties":{"auto_reprice_enabled":{"type":"boolean"},"max_step_ratio":{"description":"A move above this ratio either way is held for an admin confirm.","format":"double","type":"number"},"min_sample_units":{"format":"int64","type":"integer"},"rederive_threshold":{"format":"double","type":"number"},"usage_window_days":{"format":"int64","type":"integer"}},"required":["auto_reprice_enabled","rederive_threshold","usage_window_days","min_sample_units","max_step_ratio"],"type":"object"},"ProfileReq":{"description":"`PUT /v1/trajectory/profile`. Every field is optional on update; the first\ncreate needs `birth_date` or `birth_year`. A field left out keeps what is\nstored (or the config default on create).","properties":{"bed":{"description":"Bed time, `HH:MM`, after `wake` (default `23:00`): the dial's sunset.","type":["string","null"]},"birth_date":{"description":"`YYYY-MM-DD`.","type":["string","null"]},"birth_year":{"description":"A year alone is stored as July 2 of that year, `birth_precision: year`.","format":"int32","type":["integer","null"]},"ideal_hours":{"description":"A saved ideal week, hours a week per category key; `null` clears it.","type":["object","null"]},"life_expectancy_years":{"description":"50 to 120 (default 85).","format":"double","type":["number","null"]},"manual_hours":{"additionalProperties":{"format":"double","type":"number"},"description":"Slider values, hours a week per category key (0 to 168). Replaces the\nstored map.","propertyNames":{"type":"string"},"type":["object","null"]},"overrides":{"description":"Title rules (at most 50). Replaces the stored list.","items":{"$ref":"#/components/schemas/Override"},"type":["array","null"]},"ratings":{"additionalProperties":{"$ref":"#/components/schemas/Rating"},"description":"Re-ratings per category key; a key left out uses the config seed.\nReplaces the stored map.","propertyNames":{"type":"string"},"type":["object","null"]},"sleep_hours_per_day":{"description":"0 to 16 (default 8).","format":"double","type":["number","null"]},"wake":{"description":"Wake time, `HH:MM` (default `07:00`): the dial's sunrise.","type":["string","null"]},"window_weeks":{"description":"Trailing calendar window in weeks, 4 to 12 (default 8).","format":"int64","type":["integer","null"]}},"type":"object"},"ProjectionReq":{"description":"`POST /v1/trajectory/projection`. Both fields are optional; each one asks\nfor its own part of the answer.","properties":{"at_age":{"description":"An age for the time machine (0 to your life expectancy): cumulative\nhours per row at that age.","format":"double","type":["number","null"]},"ideal_hours":{"description":"An ideal week to compare with the current one, hours a week per\ncategory key (0 to 168; unscheduled is what the others leave and cannot\nbe set). A key left out keeps its current hours.","type":["object","null"]}},"type":"object"},"ProposalDecisionReq":{"additionalProperties":false,"description":"One decision over a batch of proposals.","properties":{"decision":{"$ref":"#/components/schemas/ProposalVerdict"},"note":{"description":"Optional note recorded with the decision (at most 2000 characters).","type":["string","null"]},"proposal_ids":{"description":"The proposals to decide: kickoff proposal ids, or drill-down proposal\nuuids. 1 to 200, duplicates ignored.","items":{"type":"string"},"type":"array"}},"required":["decision","proposal_ids"],"type":"object"},"ProposalRule":{"additionalProperties":false,"description":"A filter over PENDING drill-down proposals, for `decide_by_rule`. Every\nset field narrows the match (AND, never OR); at least one must be set —\n[`checked`](Self::checked) refuses the empty rule, which would otherwise\nmatch every pending proposal in the job.","properties":{"grounded_only":{"description":"Only `GROUNDED` proposals match when true. Approve never applies to a\n`GENERATED` proposal regardless of this flag (module docs, \"Rule\nnever approves GENERATED\"); this narrows the *match*, not just the\napply step, so a rule scoped to `GENERATED` work is honestly empty\nrather than reporting every match refused.","type":"boolean"},"kind":{"description":"Only these proposal kinds match: `entity`, `edge`, `fact`, `merge`.","items":{"type":"string"},"type":["array","null"]},"min_confidence":{"description":"Only proposals with confidence at or above this match, in (0, 1]. A\nproposal with no recorded confidence never matches when this is set.","format":"double","type":["number","null"]},"no_lint_flags":{"description":"Only proposals with no K2 lint flags match when true.","type":"boolean"},"source_domains":{"description":"Only proposals whose EVERY source URL's host is in this list match\n(case-insensitive, exact host match, e.g. `[\"example.org\"]`). A\nproposal with no sources never matches when this is set.","items":{"type":"string"},"type":["array","null"]}},"type":"object"},"ProposalVerdict":{"enum":["approve","reject"],"type":"string"},"ProposedEdge":{"additionalProperties":false,"description":"A proposed relation between two `ExtractedEntity` proposals, or an\nalready-aligned `entity.entities` id. T1's kebab-case vocabulary\n(`docs/proposals/ENTITY-RELATIONS.md`); `basis`/`score`/`sources` are beta-side\ncolumns T1 deliberately leaves to the consumer. `attributes` carries relation\nattributes such as `holds-office`'s `valid_from`/`valid_to` tenure, as plain\nstrings (T1's closed attribute-type set is `date` and `text`, both strings).","properties":{"attributes":{"additionalProperties":{"type":"string"},"propertyNames":{"type":"string"},"type":"object"},"basis":{"$ref":"#/components/schemas/Basis"},"from":{"type":"string"},"id":{"type":"string"},"relation":{"$ref":"#/components/schemas/RelationId"},"score":{"$ref":"#/components/schemas/Score"},"sources":{"items":{"type":"string"},"type":"array"},"to":{"type":"string"}},"required":["id","relation","from","to","basis","score","sources","attributes"],"type":"object"},"ProvisionReq":{"properties":{"display_name":{"type":"string"},"email":{"description":"Optional sign-in address. Stored on the identity so email links (and a\nlater Google sign-in) reach this account.","type":["string","null"]},"note":{"type":["string","null"]}},"required":["display_name"],"type":"object"},"PublicFreePreviews":{"description":"How many free meeting previews a new account gets, and whether free previews are open today.","properties":{"open_today":{"type":"boolean"},"previews_per_account":{"minimum":0,"type":"integer"}},"required":["previews_per_account","open_today"],"type":"object"},"PublicHome":{"description":"The public home page document.","properties":{"claim_copy_enabled":{"type":"boolean"},"links":{"properties":{"buy_credits":{"type":"string"},"sign_in":{"type":"string"}},"type":"object"},"page":{"type":"string"},"promises":{"items":{"properties":{"cta":{"properties":{"href":{"type":"string"},"label":{"type":"string"}},"type":"object"},"headline":{"type":"string"},"id":{"type":"string"}},"type":"object"},"type":"array"},"proof":{"properties":{"featured_no_route":{"properties":{"href":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"verdict":{"type":"string"}},"type":["object","null"]},"featured_route":{"properties":{"href":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"},"verdict":{"type":"string"}},"type":["object","null"]},"gallery":{"items":{"properties":{"href":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"}},"type":"object"},"type":"array"},"news":{"items":{"properties":{"date":{"type":"string"},"id":{"type":"string"},"kind":{"type":"string"},"link":{"type":"string"},"summary":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"seals_count":{"type":"integer"}},"type":"object"}},"type":"object"},"PublicMoment":{"description":"A Gallery moment: a historical scene, its real and alternate endings.","properties":{"alternate_ending":{"properties":{"summary":{"type":"string"},"verdict":{"type":"string"},"walk_id":{"type":"string"}},"type":"object"},"kind":{"type":"string"},"note":{"type":"string"},"play":{"properties":{"available":{"type":"boolean"},"setup_id":{"type":"string"}},"type":"object"},"public":{"type":"boolean"},"real_ending":{"properties":{"summary":{"type":"string"},"verdict":{"type":"string"},"walk_id":{"type":"string"}},"type":"object"},"scene":{"type":"string"},"seats":{"items":{"properties":{"figure":{"type":"string"},"role":{"type":"string"}},"type":"object"},"type":"array"},"share":{"properties":{"og_image":{"type":"string"},"url":{"type":"string"}},"type":"object"},"slug":{"type":"string"},"sources":{"items":{"properties":{"title":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"title":{"type":"string"},"visual":{"type":"string"},"when":{"type":"string"},"where":{"type":"string"}},"type":"object"},"PublicNewsItem":{"description":"One published item: a route, no-route, moment or sealed forecast.","properties":{"date":{"format":"date","type":"string"},"id":{"type":"string"},"kind":{"enum":["route","no_route","moment","seal"],"type":"string"},"link":{"description":"A path on https://timepointai.com.","type":"string"},"summary":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"}},"type":"object"},"PublicNewsList":{"description":"Recent published items, newest first (at most 50).","properties":{"items":{"items":{"$ref":"#/components/schemas/PublicNewsItem"},"type":"array"},"next":{"description":"Always null: no pagination.","type":["string","null"]}},"required":["items"],"type":"object"},"PublicNoRoute":{"description":"A published walk-back that found no route, with the nearest reachable outcome.","properties":{"candidates_tried":{"items":{"properties":{"closest_replay":{"type":"string"},"distance":{"type":"string"},"n":{"type":"integer"},"summary":{"type":"string"}},"type":"object"},"type":"array"},"check":{"properties":{"held":{"type":"integer"},"method":{"type":"string"},"replays":{"type":"integer"}},"type":"object"},"cost_usd":{"type":["number","null"]},"domain_feasibility":{"type":"string"},"endpoint":{"properties":{"by":{"type":"string"},"outcome":{"type":"string"}},"type":"object"},"id":{"type":"string"},"kind":{"type":"string"},"nearest_reachable":{"properties":{"by":{"type":"string"},"outcome":{"type":"string"},"why":{"type":"string"}},"type":"object"},"public":{"type":"boolean"},"published_at":{"format":"date-time","type":"string"},"share":{"properties":{"og_image":{"type":"string"},"url":{"type":"string"}},"type":"object"},"start":{"properties":{"summary":{"type":"string"}},"type":"object"},"title":{"type":"string"},"verdict":{"type":"string"}},"type":"object"},"PublicPlayBoardRow":{"description":"One leaderboard row: a player's opt-in handle and best finished attempt.","properties":{"handle":{"type":"string"},"rank":{"type":"integer"},"score":{"type":"integer"},"status":{"enum":["won","lost"],"type":"string"},"turns":{"type":"integer"}},"type":"object"},"PublicPlayChallenge":{"description":"A live public Play challenge with its leaderboard.","properties":{"attempts":{"type":"integer"},"best_score":{"type":["integer","null"]},"brief":{"type":"string"},"goal":{"type":"string"},"id":{"type":"string"},"leaderboard":{"items":{"properties":{"handle":{"type":"string"},"rank":{"type":"integer"},"score":{"type":"integer"},"status":{"enum":["won","lost"],"type":"string"},"turns":{"type":"integer"}},"type":"object"},"type":"array"},"likely_outcome":{"description":"The simulation's dated read of the likely ending. Never an accuracy claim.","properties":{"as_of":{"type":"string"},"label":{"type":"string"},"p":{"type":"number"}},"type":["object","null"]},"link":{"description":"A path on https://timepointai.com.","type":"string"},"seat":{"type":"string"},"slug":{"type":"string"},"source":{"properties":{"kind":{"enum":["news","run","preview","moment","authored"],"type":"string"},"link":{"type":["string","null"]}},"type":"object"},"table":{"items":{"properties":{"name":{"type":"string"},"role":{"type":"string"},"stance_label":{"enum":["opposed","leans against","undecided","leans for","supportive"],"type":"string"}},"type":"object"},"type":"array"},"title":{"type":"string"},"turn_limit":{"type":"integer"},"win_threshold":{"description":"The share of the table's support that wins.","maximum":1,"minimum":0,"type":"number"},"wins":{"type":"integer"}},"type":"object"},"PublicPlayChallengeList":{"description":"The live public challenges, newest first (at most 50).","properties":{"items":{"items":{"$ref":"#/components/schemas/PublicPlayChallengeSummary"},"type":"array"},"next":{"description":"Always null: no pagination.","type":["string","null"]}},"required":["items"],"type":"object"},"PublicPlayChallengeSummary":{"description":"A live public Play challenge. Win it by talking the table to `win_threshold` support within `turn_limit` turns (POST /v1/play/attempts, or the MCP tool play_start).","properties":{"attempts":{"type":"integer"},"best_score":{"type":["integer","null"]},"brief":{"type":"string"},"goal":{"type":"string"},"id":{"type":"string"},"likely_outcome":{"description":"The simulation's dated read of the likely ending. Never an accuracy claim.","properties":{"as_of":{"type":"string"},"label":{"type":"string"},"p":{"type":"number"}},"type":["object","null"]},"link":{"description":"A path on https://timepointai.com.","type":"string"},"seat":{"type":"string"},"slug":{"type":"string"},"source":{"properties":{"kind":{"enum":["news","run","preview","moment","authored"],"type":"string"},"link":{"type":["string","null"]}},"type":"object"},"table":{"items":{"properties":{"name":{"type":"string"},"role":{"type":"string"},"stance_label":{"enum":["opposed","leans against","undecided","leans for","supportive"],"type":"string"}},"type":"object"},"type":"array"},"title":{"type":"string"},"turn_limit":{"type":"integer"},"win_threshold":{"description":"The share of the table's support that wins.","maximum":1,"minimum":0,"type":"number"},"wins":{"type":"integer"}},"type":"object"},"PublicPlayLeaderboard":{"description":"Best finished attempt per player with a handle, best first (at most 25).","properties":{"items":{"items":{"$ref":"#/components/schemas/PublicPlayBoardRow"},"type":"array"}},"required":["items"],"type":"object"},"PublicRoute":{"description":"A published walk-back that found a route: dated milestones, each checked by a blind replay.","properties":{"check":{"properties":{"held":{"type":"integer"},"method":{"type":"string"},"replays":{"type":"integer"}},"type":"object"},"cost_usd":{"type":["number","null"]},"endpoint":{"properties":{"by":{"type":["string","null"]},"outcome":{"type":"string"}},"type":"object"},"fictional":{"type":"boolean"},"id":{"type":"string"},"kind":{"type":"string"},"milestones":{"items":{"properties":{"by":{"type":"string"},"check":{"properties":{"blind":{"type":"boolean"},"held":{"type":"boolean"},"judged":{"type":"boolean"},"replays":{"type":"integer"}},"type":"object"},"milestone":{"type":"string"},"n":{"type":"integer"}},"type":"object"},"type":"array"},"public":{"type":"boolean"},"published_at":{"format":"date-time","type":"string"},"sealed":{"properties":{"seal_id":{"type":["string","null"]}},"type":"object"},"share":{"properties":{"og_image":{"type":"string"},"url":{"type":"string"}},"type":"object"},"start":{"properties":{"summary":{"type":"string"}},"type":"object"},"title":{"type":"string"},"verdict":{"type":"string"}},"type":"object"},"PublicSeal":{"description":"One sealed forecast. `settlement_status` says whether the outcome is known; `status` is only the OpenTimestamps proof's state. Settlement fields are null while the seal is open. A field the record lacks is omitted.","properties":{"calendars":{"items":{"type":"string"},"type":"array"},"criteria":{"type":["string","null"]},"hash":{"type":"string"},"hit":{"description":"True when the sealed call was on the right side (p > 0.5 and yes, or p < 0.5 and no). Null while open, void, without a probability, or at exactly p = 0.5.","type":["boolean","null"]},"id":{"type":"string"},"label":{"type":"string"},"models":{"properties":{"dialog":{"type":["string","null"]},"planning":{"type":["string","null"]},"simulation":{"type":["string","null"]},"synthesis":{"type":["string","null"]}},"type":"object"},"models_source":{"enum":["frozen_at_admission","step_events",null],"type":["string","null"]},"outcome":{"type":["string","null"]},"probability":{"maximum":1,"minimum":0,"type":["number","null"]},"proof_status":{"type":"string"},"proof_urls":{"description":"OpenTimestamps proofs: paths on this API's host.","items":{"type":"string"},"type":"array"},"resolution":{"description":"The settled answer; null while open.","enum":["yes","no","void",null],"type":["string","null"]},"resolved_at":{"format":"date-time","type":["string","null"]},"resolves_by":{"format":"date","type":["string","null"]},"sealed_at":{"format":"date-time","type":"string"},"settled":{"format":"date","type":["string","null"]},"settlement_sources":{"items":{"format":"uri","type":"string"},"type":"array"},"settlement_status":{"description":"Whether the outcome is known: `open` until settled.","enum":["open","settled","void"],"type":"string"},"source_url":{"format":"uri","type":["string","null"]},"status":{"description":"The OpenTimestamps proof's state (`pending` until the proof is anchored). Not the outcome: read `settlement_status`.","type":"string"},"url":{"format":"uri","type":"string"},"visibility":{"type":"string"},"window_end":{"format":"date","type":["string","null"]}},"required":["id","hash","status","url","settlement_status","resolution","hit","resolved_at","resolves_by","criteria","settlement_sources"],"type":"object"},"PublicSealList":{"description":"The public sealed forecasts.","properties":{"items":{"items":{"$ref":"#/components/schemas/PublicSeal"},"type":"array"},"next":{"description":"Always null: no pagination.","type":["string","null"]}},"required":["items"],"type":"object"},"PublicSealMedia":{"description":"A seal's generated frames and simulated charts, each labelled as such.","properties":{"charts":{"items":{"properties":{"chart":{"properties":{"kind":{"type":"string"},"series":{"items":{"properties":{"basis":{"type":"string"},"name":{"type":"string"},"points":{"items":{"properties":{"x":{"type":"string"},"y":{"type":"number"}},"type":"object"},"type":"array"}},"type":"object"},"type":"array"},"source_note":{"type":"string"},"title":{"type":"string"},"x_label":{"type":"string"},"y_label":{"type":"string"},"y_unit":{"type":"string"}},"type":"object"},"label":{"type":"string"},"t":{"type":"integer"}},"type":"object"},"type":"array"},"frames":{"items":{"properties":{"alt":{"type":"string"},"label":{"type":"string"},"t":{"type":"integer"},"url":{"type":"string"}},"type":"object"},"type":"array"},"label":{"type":"string"},"seal_id":{"type":"string"}},"type":"object"},"PublishReq":{"properties":{"model_slug":{"description":"The model a public board pins (cheap, structured outputs, never `:free`).\nRequired for a public or unlisted challenge that has none yet.","type":["string","null"]},"reason":{"description":"Why, for the person who approves it.","type":["string","null"]}},"type":"object"},"QaReq":{"properties":{"note":{"type":"string"},"pass":{"type":"boolean"},"reader":{"description":"Retired: the reader is the signed-in caller (PLAN.md policy 9).\nAccepted only when it names the caller.","type":["string","null"]},"snapshot_hash":{"description":"The snapshot hash of the version the reader read, as shown with it.\nRequired; must be that version's hash.","type":["string","null"]},"version":{"description":"The version the reader read. Must be the current one: a QA note is\nevidence about one document, never about \"whatever is there now\".","format":"int32","type":"integer"}},"required":["pass","note","version"],"type":"object"},"Rating":{"description":"The value lens's inputs for one category (Life Choices'\n`calculateEventValue`). A seed in the config, or the owner's re-rating.","properties":{"cost_max":{"format":"double","type":"number"},"cost_min":{"format":"double","type":"number"},"events_per_year":{"format":"double","type":"number"},"hours_max":{"format":"double","type":"number"},"hours_min":{"format":"double","type":"number"},"love_mult":{"format":"double","type":"number"},"people_mult":{"format":"double","type":"number"},"value_max":{"format":"double","type":"number"},"value_min":{"format":"double","type":"number"},"vortex_mult":{"format":"double","type":"number"}},"required":["value_min","value_max","cost_min","cost_max","hours_min","hours_max","people_mult","love_mult","vortex_mult","events_per_year"],"type":"object"},"RecordItem":{"description":"One item named in a batch attestation, with the hash the reader read.","properties":{"hash":{"description":"The hash the reader read, as the preview showed it: a run's evidence\nhash, a file's `review_sha256`, a wrap-up's current snapshot hash.\nMust be current.","type":["string","null"]},"id":{"type":"string"},"kind":{"description":"`run` (id = run id), `file` (id = file id) or `wrapup` (id = engagement id).","type":"string"}},"required":["kind","id"],"type":"object"},"RecordReviewReq":{"additionalProperties":false,"description":"One independent review. The reviewer is never named here: it is the\ncaller's own login.","properties":{"reasons":{"description":"1 to 20 non-empty reasons, each at most 2000 characters (trimmed).","items":{"type":"string"},"type":"array"},"stage":{"description":"`pre_spend` for a kickoff; `plan` or `terminal` for a run.","type":"string"},"subject_sha256":{"description":"Optional: `sha256:<64 hex>`, the hash of what the reviewer read (a plan\nor report hash).","type":["string","null"]},"verdict":{"description":"`approve` or `reject`.","type":"string"}},"required":["stage","verdict","reasons"],"type":"object"},"RecordSettingsReq":{"properties":{"show_unfinished":{"description":"List the runs that were not delivered (did not finish, still running,\nor finished and not delivered) on the client's record. On by default.","type":"boolean"}},"required":["show_unfinished"],"type":"object"},"RegistryModel":{"properties":{"catalog_removed_at":{"format":"date-time","type":["string","null"]},"catalog_seen_at":{"format":"date-time","type":["string","null"]},"completion_usd_per_token":{"format":"double","type":["number","null"]},"context_length":{"format":"int64","type":["integer","null"]},"display_name":{"type":["string","null"]},"image_usd":{"format":"double","type":["number","null"]},"in_catalog":{"type":"boolean"},"input_modalities":{"items":{"type":"string"},"type":"array"},"last_probe":{"format":"date-time","type":["string","null"]},"live":{"type":"boolean"},"output_modalities":{"items":{"type":"string"},"type":"array"},"prompt_usd_per_token":{"format":"double","type":["number","null"]},"request_usd":{"format":"double","type":["number","null"]},"slug":{"type":"string"},"source":{"type":["string","null"]},"supported_parameters":{"items":{"type":"string"},"type":"array"},"tier":{"type":"string"},"tier_source":{"type":"string"}},"required":["slug","tier","tier_source","live","input_modalities","output_modalities","supported_parameters","in_catalog"],"type":"object"},"RelationId":{"description":"A relation id in T1's kebab-case vocabulary (`docs/proposals/ENTITY-RELATIONS.md`\n2.3): lowercase ascii words joined by single hyphens. Kept as a validated string,\nnot a closed enum, because the vocabulary is not yet pinned into beta (T2/T3);\nD2/K3 check the value against the pinned vocabulary at write/compile time.","type":"string"},"RemoveMemberReq":{"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"},"RenderReq":{"properties":{"regenerate":{"description":"Replace an existing render (RED-UX A-2, Sean-ruled): charges the full\nprice again and overwrites the artifact. Absent/false keeps the\ngenerated-once behavior.","type":"boolean"},"t":{"description":"The timepoint to render (1..=t_count).","format":"int64","type":"integer"}},"required":["t"],"type":"object"},"ReplicateReq":{"description":"`POST /v1/runs/{id}/replicate`'s body. Send `{}` for the defaults.","properties":{"allow_differences":{"description":"`true`: admit the replicate even when it would differ from the source\nrun, and report each difference. Default: any difference refuses (409\n`replicate_not_identical`) and nothing is written.","type":["boolean","null"]},"cast_approval_id":{"description":"A usable C1 cast approval of YOUR account, needed when the source's\nfrozen cast cap is above the admin tier.","format":"uuid","type":["string","null"]},"dry_run":{"description":"`true`: run every check (the identity check included) and answer 200\nwith what would be admitted. Nothing is written, not even the\nreplicate's kickoff, and nothing is reserved.","type":["boolean","null"]}},"type":"object"},"ReplyView":{"properties":{"line":{"type":"string"},"name":{"type":"string"},"persona_id":{"type":"string"}},"required":["persona_id","name","line"],"type":"object"},"RepriceOutcome":{"properties":{"changes":{"items":{"$ref":"#/components/schemas/Change"},"type":"array"},"derivation_id":{"format":"int64","type":"integer"},"from_version":{"format":"int32","type":"integer"},"held":{"description":"Moves held by the step cap (not applied), each with its target.","items":{"$ref":"#/components/schemas/Change"},"type":"array"},"reprice_id":{"format":"int64","type":"integer"},"skipped":{"items":{"$ref":"#/components/schemas/Skip"},"type":"array"},"to_version":{"description":"`None` when nothing moved (no version written).","format":"int32","type":["integer","null"]}},"required":["reprice_id","derivation_id","from_version","changes","skipped","held"],"type":"object"},"Requirement":{"additionalProperties":false,"description":"Uniform wire shape supported by the provider's strict object schema.\n`number` means count for Count and elapsed days for MinimumDuration.","properties":{"clause":{"type":"string"},"date":{"format":"date","type":["string","null"]},"from_t":{"format":"int64","type":["integer","null"]},"kind":{"$ref":"#/components/schemas/RequirementKind"},"number":{"format":"int64","type":["integer","null"]},"to_t":{"format":"int64","type":["integer","null"]}},"required":["clause","kind"],"type":"object"},"RequirementKind":{"enum":["end_date","count","milestone","minimum_duration","unresolved"],"type":"string"},"RetractEdgeReq":{"description":"`POST /v1/edges/{id}/retract` request body. Attributes are never restated\nhere — a retraction withdraws the predecessor's exact claim.","properties":{"basis":{"type":"string"},"observed_at":{"format":"date-time","type":["string","null"]},"score":{"format":"float","type":"number"},"source_urls":{"items":{"type":"string"},"type":"array"}},"required":["basis","score"],"type":"object"},"RevivedModel":{"properties":{"last_probe":{"format":"date-time","type":["string","null"]},"live":{"type":"boolean"},"slug":{"type":"string"}},"required":["slug","live"],"type":"object"},"RolesUpdate":{"additionalProperties":false,"description":"`PUT /v1/admin/model-roles` body. Every field is optional.","properties":{"roles":{"additionalProperties":{"type":["string","null"]},"description":"role → slug, or null to inherit again.","propertyNames":{"type":"string"},"type":["object","null"]},"synthesis":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/SynthesisUpdate"}]}},"type":"object"},"RuleDecisionReq":{"additionalProperties":false,"description":"A `decide_by_rule` request (module docs): every PENDING proposal `rule`\nmatches gets `decision`, independently — `preview: true` reports it\nwithout writing anything.","properties":{"decision":{"$ref":"#/components/schemas/ProposalVerdict"},"note":{"description":"Recorded on every proposal this call decides (at most 2000\ncharacters); never the per-item note a GENERATED approval needs —\nrule approval excludes GENERATED outright (module docs).","type":["string","null"]},"preview":{"description":"`true`: report exactly which proposals match and what would happen\nto each, and write nothing. `false`: apply it.","type":"boolean"},"rule":{"$ref":"#/components/schemas/ProposalRule"}},"required":["rule","decision","preview"],"type":"object"},"Scope":{"additionalProperties":false,"description":"A territory/scope discovered during kickoff (WORKGRAPH D1's L0 drill-down root),\ne.g. \"Synthetic State A\". Deliberately NOT an entity: T1's `market` entity kind is\na priced tradable, not a sales territory (`docs/proposals/ENTITY-RELATIONS.md`\nsection 11, open question 2 — its recommended default, pending Sean's\nconfirmation, is that a territory stays a query scope this wave). No `entity_id`.","properties":{"basis":{"$ref":"#/components/schemas/Basis"},"confidence":{"$ref":"#/components/schemas/Confidence"},"id":{"type":"string"},"name":{"type":"string"},"notes":{"type":["string","null"]},"sources":{"items":{"type":"string"},"type":"array"}},"required":["id","name","basis","sources","confidence"],"type":"object"},"Score":{"description":"A proposed-edge score in `(0, 1]`: belief the edge holds as stated (T1 draft,\n`docs/proposals/ENTITY-RELATIONS.md` 2.6 — zero is absence, not a low score, so the\nlower bound is exclusive, unlike [`Confidence`]'s inclusive `[0, 1]`).","format":"double","type":"number"},"SeatView":{"description":"One person at the table and how far they back the goal now.","properties":{"name":{"type":"string"},"persona_id":{"type":"string"},"role":{"type":"string"},"support":{"description":"0 to 1.","format":"double","type":"number"}},"required":["persona_id","name","role","support"],"type":"object"},"SendLoginReq":{"properties":{"owner_id":{"type":"string"}},"required":["owner_id"],"type":"object"},"SetPassCapsReq":{"additionalProperties":false,"properties":{"exa_usd":{"description":"The pass's new Exa cap in USD, within `research_pass_cap_exa_usd`'s\nbounds. Omit to keep it.","format":"double","type":["number","null"]},"openrouter_usd":{"description":"The pass's new OpenRouter cap in USD, within\n`research_pass_cap_openrouter_usd`'s bounds. Omit to keep it.","format":"double","type":["number","null"]}},"type":"object"},"SettleReq":{"properties":{"outcome":{"description":"One line, at most 240 characters, starting with the word that matches\n`resolution`: `Yes:`, `No:` or `Void:`.","example":"No: the central bank held its policy rate (statement, Oct 30, 2026).","type":"string"},"reason":{"description":"Why, for the person who approves it. Defaults to a sentence naming the source.","type":["string","null"]},"resolution":{"description":"How the question resolved: `yes`, `no` or `void`. Required. The public\nseal serves it as `resolution` and derives `hit` from it.","example":"no","type":"string"},"settled":{"description":"The date of the result, `YYYY-MM-DD` (the date where it was published).","example":"2026-10-30","type":"string"},"source_url":{"description":"The primary source of the result: an https URL. Public once settled.","example":"https://example.gov/statement","type":"string"}},"required":["settled","outcome","resolution","source_url"],"type":"object"},"ShareReq":{"properties":{"ttl_days":{"format":"int64","type":["integer","null"]}},"type":"object"},"Skip":{"properties":{"item":{"type":"string"},"reason":{"type":"string"}},"required":["item","reason"],"type":"object"},"StageDeckReq":{"properties":{"audit":{"description":"The audit the skill wrote beside the deck (`timepoint-deck-audit/1`): `deck` (its sha256,\nfile name, pages, length and audience modes, generation time and identity, release,\nengagement, client and scope) and `claims` (each with `key`, `slides` and `source`).","type":"object"},"content_base64":{"description":"The deck's bytes, base64 (standard alphabet). At most 10 MB decoded.","type":"string"},"name":{"description":"The file name the skill wrote: `client-<id prefix>-<date>-<length>-<audience>.pptx`.\n1-160 characters, ends in `.pptx`, no slashes, not hidden, never the client's name.","type":"string"},"title":{"description":"What the client reads in the list (optional, at most 200 characters).","type":["string","null"]}},"required":["name","content_base64","audit"],"type":"object"},"StageReq":{"properties":{"content_base64":{"description":"The file's bytes, base64 (standard alphabet).","type":"string"},"content_type":{"description":"One of text/markdown, text/csv, application/json, text/plain, application/pdf.","type":"string"},"folder":{"description":"Subfolder path segment, e.g. \"briefs\" (empty for the root folder).","type":"string"},"name":{"description":"File name within the folder, 1-160 characters, no slashes, not hidden.","type":"string"},"title":{"type":["string","null"]}},"required":["name","content_type","content_base64"],"type":"object"},"StartReq":{"additionalProperties":false,"description":"`POST /v1/play/attempts`: exactly one of `challenge_id`, `run_id`,\n`preview_id`.","properties":{"challenge_id":{"description":"A challenge id (`ch_…`).","type":["string","null"]},"preview_id":{"description":"One of your meeting previews (`mp_…`) with a challenge compiled from it.","type":["string","null"]},"run_id":{"description":"A run with a playable challenge compiled from it.","format":"uuid","type":["string","null"]},"seat":{"description":"The seat to take. A v1 challenge has one seat; when given it must match.","type":["string","null"]}},"type":"object"},"SupersedeEdgeReq":{"description":"`POST /v1/edges/{id}/supersede` request body.","properties":{"attributes":{"description":"Revised attributes. Omitted = keep the predecessor's own (a score-only\ncorrection never silently blanks a tenure nobody meant to touch)."},"basis":{"type":"string"},"observed_at":{"format":"date-time","type":["string","null"]},"score":{"format":"float","type":"number"},"source_urls":{"items":{"type":"string"},"type":"array"}},"required":["basis","score"],"type":"object"},"SynthesisUpdate":{"additionalProperties":false,"properties":{"ab_extra_base_fraction":{"description":"The A/B surcharge as a fraction of `deep_sim_base`, in (0, 4].","format":"double","type":["number","null"]},"mode":{"description":"`single` or `alternate` (`ab` is a per-run admin choice, never a default).","type":["string","null"]},"pair":{"description":"`[model_a, model_b]`, or null to clear (only while mode is single).","items":{"type":"string"},"type":["array","null"]}},"type":"object"},"TableDelta":{"properties":{"delta":{"description":"The change this turn made.","format":"double","type":"number"},"persona_id":{"type":"string"},"support":{"description":"Support after this turn, 0 to 1.","format":"double","type":"number"}},"required":["persona_id","support","delta"],"type":"object"},"TrajectorySimulateReq":{"description":"The simulation to start.","properties":{"by":{"description":"`walk_back`: the date to reach it by, `YYYY-MM-DD`, after today.","type":["string","null"]},"goal":{"description":"`walk_back`: the outcome to reach (at most 300 characters). A deep\nquery carries it as the pinned goal when given.","type":["string","null"]},"horizon_years":{"description":"The horizon the question names (1 to 45 years; default 5).","format":"int32","minimum":0,"type":["integer","null"]},"ideal_hours":{"additionalProperties":{"format":"double","type":"number"},"description":"An ideal week, `{category: hours a week}`; its differences from the\ncurrent week ride into a deep query. Optional.","propertyNames":{"type":"string"},"type":["object","null"]},"mode":{"description":"`quick` (a `quick_sim`, 2 credits), `deep` (a `deep_sim`; the plan\nbrings the exact quote and nothing is reserved until you confirm) or\n`walk_back` (a pin walked back from `goal` by `by`).","type":"string"}},"required":["mode"],"type":"object"},"TurnRecord":{"description":"One stored turn, as its owner reads it back.","properties":{"credits_charged":{"format":"int32","type":"integer"},"funding":{"description":"`free` or `credits`.","type":"string"},"replies":{"items":{"$ref":"#/components/schemas/ReplyView"},"type":"array"},"say":{"type":"string"},"support_after":{"format":"double","type":"number"},"table":{"items":{"$ref":"#/components/schemas/TableDelta"},"type":"array"},"turn":{"format":"int32","type":"integer"}},"required":["turn","say","replies","table","support_after","funding","credits_charged"],"type":"object"},"TurnReq":{"additionalProperties":false,"description":"`POST /v1/play/attempts/{id}/turns`.","properties":{"say":{"description":"What the player says to the table (at most the challenge's `max_chars`).","type":"string"}},"required":["say"],"type":"object"},"TurnView":{"description":"What one turn did.","properties":{"credits_charged":{"format":"int64","type":"integer"},"funding":{"description":"`free` or `credits`.","type":"string"},"replies":{"items":{"$ref":"#/components/schemas/ReplyView"},"type":"array"},"score":{"format":"int32","type":"integer"},"status":{"description":"`active`, `won` or `lost`.","type":"string"},"support_total":{"format":"double","type":"number"},"table":{"items":{"$ref":"#/components/schemas/TableDelta"},"type":"array"},"turn":{"format":"int32","type":"integer"},"turns_left":{"format":"int32","type":"integer"}},"required":["turn","replies","table","support_total","score","status","turns_left","funding","credits_charged"],"type":"object"},"VerdictBody":{"additionalProperties":false,"description":"What a caller may assert. `deny_unknown_fields` is the 8.2 gate: a\n`filter_version` (or anything else) offered here is refused, not dropped.","properties":{"moment_id":{"description":"The moment being assessed — must be the caller's own.","type":"string"},"note":{"description":"Why, in words a person can act on. Required, non-empty.","type":"string"},"supersedes":{"description":"The verdict this one supersedes IN THE VIEW. The predecessor stays.","format":"uuid","type":["string","null"]},"verdict":{"description":"supported | unsupported | contradicted (three-valued on purpose).","type":"string"}},"required":["moment_id","verdict","note"],"type":"object"},"WorkspaceReq":{"properties":{"display_name":{"type":"string"}},"required":["display_name"],"type":"object"}},"securitySchemes":{"bearer":{"bearerFormat":"tpb_<hex>","description":"Dev bearer token minted by `pro-api mint-token`; only its sha256 is stored. Required on every /v1 route.","scheme":"bearer","type":"http"}}},"info":{"description":"Timepoint Beta v0: quick_sim runs over a real credit ledger, SSE run logs, ConsoleDoc/CockpitDoc projections, and the TT bundle. Where a handler serves a dynamically-assembled `serde_json::Value`, the schema is honestly untyped (any JSON) and the shape is stated in the operation description — no invented typed shapes. Workspace-scoped operations act in the workspace named by the `x-timepoint-workspace` request header, and in the caller's personal space when it is absent. Every response from an operation that acts in a space echoes that space in the `x-timepoint-space` response header: `personal` or `workspace:<uuid>`. Operations that do not act in a space carry no echo, and neither does a refusal of a workspace that did not resolve (malformed, unknown, or not one the caller is a member of: 404 workspace_not_found). M4: a handful of create/spend operations — named individually in their own `Idempotency-Key` parameter, currently create_run, create_kickoff, compile_kickoff, replicate_run, create_drilldown_job, stage_action, intake_review, open_engagement and provision_client — accept that request header (a caller-chosen key, at most 200 characters). Sending one makes a retry safe: the same caller + key + method/path/body within 24h replays the first response verbatim (response header `idempotent-replay: true`) instead of re-running the operation; the same key with a different request answers 422 `idempotency_key_reused`; a concurrent duplicate answers 409 `idempotency_in_progress` (retryable). Omitting the header is ordinary, unchanged behavior. Every /v1 operation not listed above has no such key — a retry is a fresh call. Public reads: every /public/v1 operation (seals, news, home, routes, no-routes, moments, Play challenges and leaderboards) and /health need no token. The /public/v1 reads share one budget of 60 requests a minute per client IP with the other public routes (OAuth and sign-in discovery); /health is not rate limited. Connecting: MCP. The MCP endpoint is POST /mcp (streamable HTTP, JSON-RPC 2.0) with `Authorization: Bearer <token>`: a tpb_ token, or the OAuth token an assistant's connector obtains (discovery at /.well-known/oauth-protected-resource/mcp). Every MCP call needs a token; tools/list shows only what the token may call. A connector token lists walk_back, forecast, deep_sim, confirm_plan, get_run, get_run_cockpit, get_run_portal, get_run_plan, list_my_runs, list_models, preview_meeting, get_meeting_preview, pin_outcome, get_pin, record_outcome, balance, checkout, list_seals, list_published, play_challenges, play_leaderboard, play_allowance, play_start, play_turn and play_state (a directory connector omits balance, checkout and confirm_plan). Four of them read public data that also needs no token over HTTP: list_seals (GET /public/v1/seals), list_published (GET /public/v1/news), play_challenges (GET /public/v1/play/challenges) and play_leaderboard (GET /public/v1/play/challenges/{slug}/leaderboard). /mcp/manifest.json lists the HTTP operations as tools under their operation ids.","license":{"name":"Proprietary"},"title":"Timepoint Beta API","version":"0.1.0"},"openapi":"3.1.0","paths":{"/health":{"get":{"description":"Liveness probe plus the honest degraded-mode signals: `ok`/`db` (database reachability), `version`, `balance_gate` (the LLM admission gate's label: ok / no_key / no_headroom / budget_exhausted / unknown), and `account_headroom_usd` (a number only when a management key is configured AND the credits endpoint answered; null is a LABELED absence, never a fabricated 0).","operationId":"health","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}},"description":"Health document `{ok, version, db, balance_gate, account_headroom_usd, calendar, trajectory, signup, worker_role, worker_state, public_base, indexable, signin_methods}`; `signin_methods` is `{email, github, google}` booleans for the sign-in methods that work now — served even when degraded; the fields carry the degradation. `calendar` and `trajectory` are on/off, `signup` is closed/allowlist, `worker_role` is shared/sealed, `worker_state` is draining/reconnecting/standby/starting/stopped/off. None of them name secrets."}},"security":[],"summary":"Liveness + honest degraded-mode signals. No auth.","tags":["health"]}},"/public/v1/free-previews":{"get":{"description":"How many free meeting previews a new account gets, and whether they are open today.","operationId":"public_free_previews","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicFreePreviews"}}},"description":"The numbers."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Free meeting previews per account","tags":["public"]}},"/public/v1/home":{"get":{"description":"The documents the public home page renders.","operationId":"public_home","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicHome"}}},"description":"The home document."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Public home page document","tags":["public"]}},"/public/v1/moments/{slug}":{"get":{"description":"One published Gallery moment.","operationId":"public_get_moment","parameters":[{"description":"A moment slug.","in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicMoment"}}},"description":"The moment."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Read a Gallery moment","tags":["public"]}},"/public/v1/news":{"get":{"description":"Routes, no-routes, moments and sealed forecasts, newest first (at most 50). The MCP tool list_published serves the same list with an absolute `url` per item.","operationId":"public_news","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicNewsList"}}},"description":"The items."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Recent published items","tags":["public"]}},"/public/v1/no-routes/{id}":{"get":{"description":"A published walk-back that found no route.","operationId":"public_get_no_route","parameters":[{"description":"A no-route id (`nr_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicNoRoute"}}},"description":"The no-route."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Read a published no-route","tags":["public"]}},"/public/v1/play/challenges":{"get":{"description":"The live public challenges. Start one with POST /v1/play/attempts (`challenge_id`) or the MCP tool play_start (`slug`). Cached for a minute.","operationId":"public_list_play_challenges","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlayChallengeList"}}},"description":"The challenges."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"List live Play challenges","tags":["public"]}},"/public/v1/play/challenges/{slug}":{"get":{"description":"One live public challenge with its leaderboard. Cached for a minute.","operationId":"public_get_play_challenge","parameters":[{"description":"A challenge slug.","in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlayChallenge"}}},"description":"The challenge."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Read a live Play challenge","tags":["public"]}},"/public/v1/play/challenges/{slug}/leaderboard":{"get":{"description":"Best finished attempt per player who chose a handle. Cached for a minute.","operationId":"public_get_play_leaderboard","parameters":[{"description":"A challenge slug.","in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPlayLeaderboard"}}},"description":"The leaderboard."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Read a Play leaderboard","tags":["public"]}},"/public/v1/routes/{id}":{"get":{"description":"A published walk-back that found a route.","operationId":"public_get_route","parameters":[{"description":"A route id (`rt_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicRoute"}}},"description":"The route."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Read a published route","tags":["public"]}},"/public/v1/seals":{"get":{"description":"Every public seal: hash, timestamp proofs, probability and settlement. The MCP tool list_seals serves the same list.","operationId":"public_list_seals","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSealList"}}},"description":"The seals."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"List sealed forecasts","tags":["public"]}},"/public/v1/seals/{id}":{"get":{"description":"One public seal.","operationId":"public_get_seal","parameters":[{"description":"A seal id (`seal_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSeal"}}},"description":"The seal."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Read one sealed forecast","tags":["public"]}},"/public/v1/seals/{id}/frames/{file}":{"get":{"description":"One generated frame as PNG (`<t>.png`, from the media document's `url`).","operationId":"public_get_seal_frame","parameters":[{"description":"A seal id (`seal_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"`<t>.png`.","in":"path","name":"file","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"image/png":{"schema":{"format":"binary","type":"string"}}},"description":"The image."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"A seal's generated frame image","tags":["public"]}},"/public/v1/seals/{id}/media":{"get":{"description":"Generated frames and simulated charts for a seal, each labelled as generated.","operationId":"public_get_seal_media","parameters":[{"description":"A seal id (`seal_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSealMedia"}}},"description":"The media document."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"A seal's generated frames and charts","tags":["public"]}},"/public/v1/seals/{id}/proof/{file}":{"get":{"description":"A detached OpenTimestamps proof, served unchanged so `ots verify` can check it against the seal's hash.","operationId":"public_get_seal_proof","parameters":[{"description":"A seal id (`seal_…`).","in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"`a.ots` or `b.ots` (one per calendar).","in":"path","name":"file","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/octet-stream":{"schema":{"format":"binary","type":"string"}}},"description":"The proof bytes."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`bad_id`: a malformed id or slug."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`not_found`: nothing public by that id."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`rate_limited`: 60 requests a minute per client IP, a budget shared with the other public routes beyond /public/v1."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"`unavailable`: the store could not be read."}},"security":[],"summary":"Download a seal's OpenTimestamps proof","tags":["public"]}},"/v1/account/context":{"get":{"operationId":"get_context","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"You + home company pointers, or complete:false."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"tags":["account"]}},"/v1/account/onboarding":{"post":{"operationId":"post_onboarding","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Pointers set, you are under daily watch."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"A required field was empty or too long."}},"security":[{"bearer":[]}],"tags":["account"]}},"/v1/actions/pending":{"get":{"description":"The caller's single pending gentle-action card (CONTRACTS §11.11 B3.5): `{action: {id, run_id, text, basis, branches: 1, window, window_end, entity_path, created_at} | null}` — at most ONE, newest first; `null` is the honest nothing-pending state.","operationId":"get_pending_action","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{action: card | null}` for the caller."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable}`."}},"security":[{"bearer":[]}],"summary":"The caller's one pending card.","tags":["actions"]}},"/v1/actions/{id}/decide":{"post":{"description":"Decide a pending gentle-action card (CONTRACTS §11.11 B3.5). OWNER-ONLY and PENDING-ONLY. A card that is not yours answers exactly as an id that does not exist does — 404, same body, same wording — so this route cannot be used to find out which card ids are real. 409 is the caller's OWN card in the wrong state. `did` mints a `user_attested` GROUNDED moment (real tt-core envelope, participants = the entity's registry path, t=1 — the schema floor; provenance source is the attestation marker) + a participation edge, records `moment_id` on the card, then attempts a NORMAL classification (a closed LLM gate leaves the moment honestly untagged, never fails the attestation). `not_now` extends the window 3 days and returns to pending after 24h (worker sweep). `not_this` is terminal — the training signal is the state row.","operationId":"decide_action","parameters":[{"description":"Action id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecideReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The decision outcome: `{action_id, choice, state, decided_at, window_end?}`; for `did` also `{moment_id, content_hash, provenance_hash, entity_path, classification}` where classification ∈ tagged | abstained | untagged | `unavailable: …`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no such card FOR THIS CALLER: an unknown id and another account's id are one answer."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_pending, state}` — the card was already decided."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_choice | note_too_long}`."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable | action_unmintable}`."}},"security":[{"bearer":[]}],"summary":"Decide the card: owner-only, pending-only, choice-typed.","tags":["actions"]}},"/v1/admin/attestations":{"post":{"description":"Admin only, and only a person's own sign-in (email link or Google login) — an agent or API token gets `person_sign_in_required`: agents never attest (PLAN.md policy 9: one person's re-authenticated attestation over named items, each with its hash). Records one batch: an action (`ack_flags`, `pass`, `request_changes`, `deliver`; `urgent` = acknowledge every open flag, pass and deliver, owner only) over 1 to 50 items of ONE client, each with the hash the reader read (must be current). `read` must be true. `note` (the person's own, at most 2000 characters, not a placeholder, never one they recorded before) is required for `ack_flags` and `request_changes`. Re-authentication: a sign-in at most 300 seconds old, or `phrase` = the preview's confirmation phrase (case- and whitespace-insensitive). Every item runs through its single-item route's own checks — the run's own gate, its scenario contract and the pre-QA lint still bind every pass and delivery, in urgent mode too — in ONE transaction, each in a savepoint; any refusal rolls everything back and names every failing item. Writes one `run.attestation_batches` row (with a factual note naming every item and the first 12 characters of its hash) and each item's own rows linked by `batch_id` (and `urgent`), each with a note composed for that item. Never approves staged actions, cast additions or research proposals.","operationId":"record_attestation_batch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRecordReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{batch_id, mode, action, client_owner, step_up: \"fresh_sign_in\"|\"phrase\", factual_note, items: [{kind, id, hash, result}]}`; `result` is what the single-item route returns (urgent: `{qa, delivery}`; an item already delivered: `already_delivered: true`, no rows)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required | owner_required | step_up_required, reason}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: batch_refused, reason, items: [{kind, id, error, reason}]}` — every failing item with the single-item route's code (`not_found`, `hash_mismatch`, `gate_blocked`, `gate_report_missing`, `scenario_not_ready`, `prequa_lint_blocked`, `work_not_ready`, `not_qa_passed`, `review_content_changed`, `already_delivered`, `withdrawn`, `no_open_flags`, `file_not_reviewed`, `delivered`, `poster_required`, `companion_follows_film`, `brief_citation_changed`, `film_run_changed`, `not_passed`, `hash_changed`, `stale_version`, `no_wrapup`, `client_scope_changed`, …); nothing was written."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: batch_invalid | batch_mixed_clients | read_required | note_required | placeholder_note | note_reused, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/attestations/preview":{"post":{"description":"Admin only (any admin caller; it records nothing, but it runs the pre-QA check of a draft or returned run that has none for its current version, R3). Names a batch — an action (`ack_flags`, `pass`, `request_changes`, `deliver`, `urgent`) over 1 to 50 items of ONE client (`run` = run id, `file` = file id, `wrapup` = engagement id; `ack_flags` is runs only) — and answers, per item, its short title, state, grade (runs), open flags (runs), the hash a reader attests (a run's evidence hash, a file's `review_sha256`, a wrap-up's current snapshot hash) and whether the action would succeed now for a person signed in now: each item is run through its single-item route's own checks inside a transaction that is rolled back (`problem` is that route's `{error, reason}`). Also the confirmation `phrase` (an intent check, not a secret), the caller's `step_up` state (a sign-in at most 300 seconds old re-authenticates; otherwise type the phrase), `note_required` (ack_flags and request_changes) and `owner` (the caller may use urgent mode: an administrator signed in as a person, not an agent). A token may preview; only a person records (`record_attestation_batch`).","operationId":"preview_attestation_batch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchPreviewReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{action, mode: \"bulk\"|\"urgent\", client_owner, client_name, items: [{kind, id, title, state, grade, open_flags, hash, ok, problem: null|{error, reason}}], ready, refused, phrase, step_up: {fresh, signed_in_at, fresh_until, window_seconds}, note_required, owner}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: batch_invalid | batch_mixed_clients, reason}` — no items, more than 50, a duplicate, an unknown kind or action, an action not valid for a kind, or items of more than one client."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/clients":{"get":{"description":"Admin only. Every non-merged client record: owner id, display name (falling back to the note, then \"Unnamed client\"), note, consent to be named, and its sign-in email addresses.","operationId":"list_clients","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{owner_id, display_name, note, consent_to_name, consent_changed_at, emails: [{email, verified_at}]}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only. Create or update a client record by `owner_id`: an absent field leaves the stored value unchanged, and `consent_to_name` (to be NAMED — marketing labels etc) starts FALSE for a brand-new client and is only ever raised by an explicit write here. `note` and `display_name` pass the entity-language lint (PLAN.md policy 2); a rejection is a 422, a milder flag is returned in `language_flags` and the write still lands.","operationId":"upsert_client","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{owner_id, consent_to_name, language_flags}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input | entity_language_rejected, reason}` — an empty/oversized field, or the lint rejected `note`/`display_name`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/clients/email":{"post":{"description":"Admin only. Attach a sign-in address to an existing client. Never moves an address away from another account that already holds it.","operationId":"attach_client_email","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{owner_id, email}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_client}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input, reason}` — a malformed email, or it already signs in to another account."}},"security":[{"bearer":[]}],"summary":"Attach a sign-in address to an existing client. Never moves an address that\nanother account already holds.","tags":["client_services"]}},"/v1/admin/clients/merge":{"post":{"description":"Admin only. Fold a duplicate client row (`from`) into another (`into`): their engagements, reviews and wrap-ups move to `into`, consent to be named becomes the stricter of the two, and `from`'s note is carried over. `from` is marked merged (kept for the audit trail, hidden from `list_clients`) — never deleted. A person's browser sign-in merges directly; any token, an operator's included, gets 403 `stage_required` and stages `merge_clients` instead.","operationId":"merge_clients","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{from, into, engagements, reviews, wrapups}` — counts of rows moved."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — `from` or `into` does not exist."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input, reason}` — `from` and `into` are the same, or one of them was already merged."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/clients/merge` — fold a duplicate client row into another.\nEngagements, reviews and wrap-ups move to `into`; `from` is marked merged\n(kept for the audit trail, hidden from the client list). Consent to be\nnamed is the stricter of the two; `from`'s note is carried over. Each moved\nreview gets a `client_merged` history row.","tags":["client_services"]}},"/v1/admin/clients/provision":{"post":{"description":"Admin only. Create a brand-new client identity (`client:<uuid>`) with a fresh API token and no starter credits (D3: a client logs in to READ what was delivered to it — the console, `list_delivered_results`, the read-delivered MCP scope — never to start a paid simulation on its own token). Optionally attaches a sign-in email. `note`/`display_name` pass the entity-language lint like `upsert_client`. The response carries the token once and is sent `Cache-Control: no-store`.","operationId":"provision_client","parameters":[{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response, TOKEN INCLUDED (header idempotent-replay: true), instead of minting a second client; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior — each call mints a new client.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{owner_id, display_name, token, email, sign_in_url}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input, reason}` — an empty/oversized field, a malformed email, or that email already signs in to another account."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/clients/send-login":{"post":{"description":"Admin only. Mail a sign-in link to every address on file for a client.","operationId":"send_client_login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendLoginReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{sent: [email, ...]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input, reason}` — no email on file, mail sending is not configured, the address may not be mailed, too many links this hour, or the send failed."}},"security":[{"bearer":[]}],"summary":"Mail a sign-in link to every address on file for a client.","tags":["client_services"]}},"/v1/admin/clients/{owner}/engagements/{id}/grant-book":{"get":{"description":"Admin only. The grant book exactly as the named identity reads it (`get_my_engagement_grant_book` built for them): the record preview's grant book.","operationId":"get_client_engagement_grant_book","parameters":[{"description":"The client identity","in":"path","name":"owner","required":true,"schema":{"type":"string"}},{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The book, as `get_my_engagement_grant_book` answers it."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: unknown, or not that identity's."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/clients/{owner}/engagements/{id}/grant-book` — the client\nview built for a named identity (admin): the record preview's grant book.","tags":["client_services"]}},"/v1/admin/clients/{owner}/engagements/{id}/record":{"get":{"description":"Admin only. The engagement record exactly as the named identity reads it (`get_engagement_record` built for them): the service desk's preview of a client's page.","operationId":"get_client_engagement_record","parameters":[{"description":"The client identity","in":"path","name":"owner","required":true,"schema":{"type":"string"}},{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The record, as `get_engagement_record` answers it."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: unknown, or not that identity's."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/clients/{owner}/engagements/{id}/record` — the same\ndocument for a named identity (admin only): what that client sees.","tags":["client_services"]}},"/v1/admin/clients/{owner}/world-model":{"get":{"operationId":"get_client_world_model","parameters":[{"description":"entities (default), facts, or relationships; iterate each to inventory the entire model.","in":"query","name":"part","required":false,"schema":{"type":"string"}},{"in":"query","name":"kind","required":false,"schema":{"type":"string"}},{"in":"query","name":"q","required":false,"schema":{"type":"string"}},{"in":"query","name":"cursor","required":false,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"limit","required":false,"schema":{"minimum":0,"type":"integer"}},{"in":"query","name":"updated_since","required":false,"schema":{"format":"date-time","type":"string"}},{"description":"in or all; client projections always use in. Only explicit scope decisions omit research.","in":"query","name":"engagement_scope","required":false,"schema":{"type":"string"}},{"in":"query","name":"include_generated","required":false,"schema":{"type":"boolean"}},{"in":"path","name":"owner","required":true,"schema":{"type":"string"}},{"in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Admin preview of exactly this client's world model. Same builder, filters, inventory and pagination as get_world_model; no provider drafts."},"404":{"description":"workspace_not_found"}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements":{"get":{"description":"Admin only. Every engagement, newest first: id, client, question, side, whether it is live, and its linked workspace (if any).","operationId":"list_engagements","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{id, client_owner, question_key, side, live, workspace_id}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only. Open an engagement claiming one side of a question for a client — THE CONFLICT CHECK: a second live intake on the same `question_key` (normalized to lowercase, collapsed whitespace) is a 409, race-safe because the database's unique index is the arbiter, not the code. `workspace_id` (W3, gate G7) links the engagement to a workspace up front so its delivered result reaches that workspace's members too; `client_owner` must already be a `client` member of that workspace.","operationId":"open_engagement","parameters":[{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response (header idempotent-replay: true) instead of re-running; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngagementReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{id, question_key, side, workspace_id}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_client}` — no client record for `client_owner`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: engagement_conflict, reason}` — this side of this question already has a live engagement."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input | workspace_client_mismatch, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/close":{"post":{"description":"Admin only. Close a live engagement (`live = false`): its question is no longer claimed, so a new engagement may intake it. Closing changes no review, and delivered results stay readable. After a close, the engagement's reviews can no longer be taken in or retitled (`intake_review`), QA'd (`preliminary_qa`, `qa_review`) or delivered (`deliver_review`): those answer 404 `no_engagement` (410 `withdrawn` for a withdrawn review), and `deliver_all` delivers none of them, listing each under `skipped`. Its wrap-ups and files can still be QA'd and delivered. Close once the engagement's reviews are delivered.","operationId":"close_engagement","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{closed: true}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}` — unknown, or already closed."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/decks":{"post":{"description":"Admin only. Stage a client deck: a PowerPoint (.pptx) report the client-deck skill built from this engagement's delivered work, with the audit it wrote beside it. The audit must be `timepoint-deck-audit/1` and name these exact bytes (`deck.sha256`), this file name, this engagement and its client. Every claim needs a source (a run artifact, the wrap-up, a file or a service record), and every run, wrap-up, file and grant proposal it cites, and every run in the deck's scope, must belong to this engagement. The deck is filed under `Report decks` and is STAGED: a person passes its QA (`qa_file`, their own sign-in) and delivers it (`deliver_file`) before the client can download it; delivery also needs every run in its scope delivered. Staging the same name again replaces a staged or withdrawn deck, never a delivered one.","operationId":"stage_deck","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageDeckReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, state: \"staged\", folder, name, size_bytes, sha256, pages, claims}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: delivered | retracted | name_taken, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid | unsourced_claim | foreign_source | audit_mismatch, reason}`: a bad name, title, encoding or file; a claim with no usable source; a cited record outside this engagement; an audit for other bytes, another file, engagement or client."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/deliver-all":{"post":{"description":"Admin only, a person's own sign-in. Delivers every review of this engagement that is not withdrawn or already delivered and whose run is complete with a document, passing each in the same call where no pass is on record (as `deliver_review` does). Answers `{engagement_id, delivered: [run_id], skipped: [{run_id, state, reason}]}`; a review that cannot be delivered is skipped with its reason, never a refusal of the whole call.","operationId":"deliver_all","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, delivered, skipped}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | stage_required, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."}},"security":[{"bearer":[]}],"summary":"DELIVER ALL. Every review of one engagement that can be delivered, in one\ncall, by a person: the two checks, per item (PLAN.md, 2026-10-13).","tags":["client_services"]}},"/v1/admin/engagements/{id}/entities":{"get":{"description":"Admin only (an agent token may read it). The entities of every review of the engagement that is not withdrawn, merged by name (case-insensitive) and kind: the origins seen, how many runs list it, whether any run's space holds it, how many runs require it or never show it, and its summed appearances. Read-only.","operationId":"get_engagement_entities","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, runs, entities: [{name, kind, origins, runs, in_registry, required_in, appearances: {moments, dialog}, never_appears_in}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found, reason}`: no such engagement."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable, reason}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/engagements/{id}/entities` — R4 roll-up over every\nnon-withdrawn review of the engagement. Read-only.","tags":["client_services"]}},"/v1/admin/engagements/{id}/files":{"get":{"description":"Admin only. Every file staged, delivered or withdrawn for an engagement, with its state. Stored media (`storage: \"object\"`) also carries `bound_run_id`, `bound_evidence_hash`, `provenance`, `duration_seconds`, a film's `poster_file_id` and `captions_file_id`, a companion's `poster_for`/`captions_for` and `detached` (replaced). `review_sha256` is the hash a QA must name: the file's `sha256`, or for a film the composite of the film and its current poster and captions; `review.current` compares against it.","operationId":"list_engagement_files","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, files: [{file_id, engagement_id, folder, name, title, content_type, size_bytes, sha256, delivered_at, state, uploaded_by, updated_at, storage, duration_seconds, poster_file_id, captions_file_id, poster_for, captions_for, bound_run_id, bound_evidence_hash, provenance, detached, review_sha256, review}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only: a person or an agent's admin token (bearer or `mcp:operator`) may upload. Stage a document for an engagement (or replace it while still staged/withdrawn): base64 content, up to 5 MB, one of text/markdown, text/csv, application/json, text/plain or application/pdf. A text format must be valid UTF-8; a PDF must start with the PDF magic bytes. Staging the same `folder`/`name` again replaces the content UNLESS it is already delivered (withdraw it first). It stays staged until a person delivers it with `deliver_file` (a token stages that delivery with `stage_action`).","operationId":"stage_file","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, state: \"staged\", size_bytes, sha256}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: delivered | name_taken, reason}` — this folder/name is already delivered (withdraw it first), or holds a stored media file."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid, reason}` — a bad name/folder/content type, invalid base64, oversized, not valid UTF-8, or not a PDF."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/grant-book":{"get":{"description":"Admin only. The engagement's grant book: one row per grant its searches found (the record's find_grants runs: attached to it, or top-level in its linked workspace), de-duplicated by funder and program across searches (`grant_id` is the latest find's proposal id, `also_found_in` the others'), with its decision (approved, rejected, ineligible, waiting), its quick read (the slate's outcome tag: win, loss or unknown, with `award_p` and the top outcome), its deep simulation (status delivered, not_delivered, in_progress or did_not_finish; top outcome, estimate, `result_word` positive, negative, mixed or unresolved, and `the_move`), the next step in a sentence and a `status_word`: found, not eligible, set aside, read: likely win, read: likely loss, simulating, simulated: won or simulated: lost. Every id and result, delivered or not.","operationId":"get_engagement_grant_book","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, question, counts: {searches, found, eligible, approved, quick_reads, simulated, won, lost}, rows: [{grant_id, funder, program, title, website, award_min_usd, award_max_usd, deadline, found_at_level, applicant_path, eligibility, eligibility_quote, decision, quick_read: {outcome, award_p, top_outcome, run_id} | null, simulation: {run_id, status, top_outcome, estimate, result_word, the_move, delivered_at} | null, next_step, status_word, searches, also_found_in, times_found, first_found_at}]}`"},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/engagements/{id}/grant-book` — the working view (admin).","tags":["client_services"]}},"/v1/admin/engagements/{id}/link":{"post":{"description":"Admin only. Link an engagement opened without `workspace_id` to a workspace after the fact (client platform D2), so its delivered runs become readable by that workspace's members. One-time: linking the same workspace again is a no-op 200 (`already: true`); a different target is a 409. Refused when any of the engagement's reviewed runs lives outside the target workspace.","operationId":"link_engagement_workspace","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{id, workspace_id, linked, reviews}` (or `{..., linked: false, already: true}` when already linked to this workspace)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement | no_workspace}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: engagement_already_linked | runs_outside_workspace, reason}`."}},"security":[{"bearer":[]}],"summary":"LINK an existing engagement to a workspace (client platform D2). An\nengagement opened without `workspace_id` keeps delivery client_owner-only;\nlinking it makes its delivered runs readable by the workspace's members\n([`crate::runs::delivered_review_sql`]) — how a client identity provisioned\nAFTER the work was delivered gets to read it. One-time (a different target\nis 409; the same target is idempotent), admin-only, refused when any\nreviewed run lives outside the workspace, and written to review history.","tags":["client_services"]}},"/v1/admin/engagements/{id}/media/uploads":{"post":{"description":"Admin only: a person or an agent's admin token (bearer or `mcp:operator`) may upload. Start uploading a film (video/mp4, up to 500 MB), a poster (image/png or image/jpeg, up to 10 MB, `poster_for` a film) or captions (text/vtt, up to 10 MB, `captions_for` a film) into an engagement's files. Answers a presigned `put_url` valid for 15 minutes: PUT the exact bytes to it with exactly `put_headers` (the store refuses any other length, type or checksum), then call `complete_media_upload`. A film may name the `run_id` it depicts (a run of this engagement whose QA a person passed): it is bound to that run's approved evidence hash, and delivering it later refuses if the run is not delivered at that hash. `provenance` (the render's record, a JSON object up to 16 KB; hashes as `sha256:<hex>`) must agree with the upload: `run_id`, `approved_evidence_hash` (the run's) and `mp4_sha256` (this file's), is never a sample render (`sample: true`), and gives the film its `duration_seconds`; other keys are kept as they are. Nothing is public; nothing is stored until complete.","operationId":"create_media_upload","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaUploadReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"`{upload_id, put_url, put_headers: {content-type, x-amz-checksum-sha256}, expires_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: run_not_in_engagement | run_not_reviewed | name_taken | delivered}` (`delivered`: the film a poster or captions file is for is delivered; withdraw it first)."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid | provenance_mismatch | sample_render, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: storage_unconfigured}`: no media bucket on this deployment."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/media/uploads/{upload_id}/complete":{"post":{"description":"Admin only: a person or an agent's admin token may complete. Finish an upload: the server asks storage for the object and checks its size and SHA-256 against what `create_media_upload` declared, then stages the file (`storage: \"object\"`, `uploaded_by` the caller) for a person's QA and delivery. A poster or captions file replaces the film's previous one (kept, detached), and the film then needs a new QA read. Idempotent: completing a completed upload answers the same file.","operationId":"complete_media_upload","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Upload id from create_media_upload","in":"path","name":"upload_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, state: \"staged\", sha256, size_bytes, content_type, storage: \"object\", bound_run_id, bound_evidence_hash, poster_for, captions_for, duration_seconds}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: no such upload in this engagement."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: upload_incomplete | checksum_mismatch | name_taken | delivered, reason}`."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: upload_expired}`: the PUT expired and nothing landed; start again."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: storage_unconfigured | storage_unavailable}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/record-settings":{"post":{"description":"Admin only. Whether the engagement's record (`get_engagement_record`) lists the runs that were not delivered: ones that did not finish, are still running, or finished and were not delivered. On by default; delivered runs are always listed. Records who changed it and when.","operationId":"set_engagement_record_settings","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordSettingsReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, show_unfinished, updated_by, updated_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/engagements/{id}/record-settings` — the engagement's\nrecord switch (admin only).","tags":["client_services"]}},"/v1/admin/engagements/{id}/research-scope":{"get":{"operationId":"get_engagement_research_scope","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Explicit decisions and their notes, newest first. Admin only."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/status":{"get":{"description":"Everything open on one engagement, in one call. Administrators only. `{engagement, reviews: {counts: {draft, qa_passed, qa_failed, delivered}, by_state: {<state>: [{run_id, kind, run_state, phase, reviewed_at, created_at}]}}, live_runs: [{run_id, kind, state, phase, created_at}], live_research_jobs: [{job_id, status, scope_name, kickoff_id, created_at}], open_decisions: [{decision_id, run_id, kind, prompt, expires_at}], pending_proposals: {drilldown: [{job_id, pending}], drilldown_total, kickoffs: [{kickoff_id, title, status}]}, spend: {usd: {exa, openrouter, openrouter_research, openrouter_runs}, runs_estimated_usd, credits: {held, settled}}}`. The engagement's runs are its reviewed runs plus, when it is linked to a workspace, every run admitted in that workspace; research jobs, proposals, kickoffs and research spend come from the linked workspace (none when unlinked). Research spend counts settled actuals plus every reservation not settled, as the pass ledger does.","operationId":"engagement_status","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The engagement's status."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown engagement."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: status_unavailable}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/wrapup":{"get":{"description":"Admin only. Read one version of an engagement's wrap-up (default: the current version). `format=json` (default) returns structured data; `format=html`/`format=md` render the document instead (`view=client` previews exactly what the client will read: no spend, no QA notes, no reviewer identities). `version_state` is `draft`, `qa_passed`, `qa_failed`, `delivered` or `superseded`.","operationId":"get_wrapup","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"A specific version (default: the current one).","in":"query","name":"version","required":false,"schema":{"format":"int32","type":"integer"}},{"description":"`json` (default), `html` or `md`.","in":"query","name":"format","required":false,"schema":{"type":"string"}},{"description":"`client` renders exactly what the client reads.","in":"query","name":"view","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"JSON by default (`{wrapup_id, engagement_id, state, current_version, version, version_state, snapshot_hash, composed_at, approved_version, approved_hash, review_note, reviewed_by, reviewed_at, delivered_version, delivered_hash, delivered_at, snapshot}`); `text/html` or `text/markdown` when `format` asks for it."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_wrapup}` — nothing composed yet, or that version does not exist."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid, reason}` — bad `view` or `format`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only. Compose a new wrap-up snapshot (`wrapup/0.1`) for an engagement: every DELIVERED run projected from its cockpit doc, research rolled up through the CRM export's own builder when the engagement is linked to a workspace, and admin-only spend. The hash is over content, not compose time — re-composing unchanged inputs returns the SAME version (`created: false`); a real change starts a new draft version, resetting QA.","operationId":"compose_wrapup","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{wrapup_id, engagement_id, version, created, snapshot_hash, snapshot}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/wrapup/deliver":{"post":{"description":"HAND-OFF, for a wrap-up. Admin only. Only a `qa_passed` wrap-up whose approved hash still matches the current version's hash may be delivered. Pins `delivered_version` — composing again afterwards starts the next draft without taking the delivered one away from the client. Delivering the version that is ALREADY delivered is not a conflict: it answers 200 with the same body and `already_delivered: true`, and writes no second history row. A changed (redrafted) version still refuses `not_passed` like any undelivered draft.","operationId":"deliver_wrapup","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{wrapup_id, version, state: \"delivered\", snapshot_hash}`, plus `already_delivered: true` on a repeat delivery of the same version."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_wrapup}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_passed | hash_changed, reason}` — not `qa_passed`, or the version has moved since delivery."}},"security":[{"bearer":[]}],"summary":"HAND-OFF: only a qa_passed wrap-up whose approved hash is the current\nversion's hash. Pins `delivered_version` — what the client reads.","tags":["client_services"]}},"/v1/admin/engagements/{id}/wrapup/history":{"get":{"description":"Admin only. The append-only history of an engagement's wrap-up: every compose, QA verdict and delivery, per version, oldest first.","operationId":"get_wrapup_history","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{wrapup_id, engagement_id, items: [...]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_wrapup}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/engagements/{id}/wrapup/qa":{"post":{"description":"THE GATE, for a wrap-up. Admin only, and only a person's own sign-in (email link or Google login) — an agent or API token gets `person_sign_in_required` (PLAN.md policy 9). `version` and `snapshot_hash` must name the CURRENT version and its hash exactly (a QA note is evidence about ONE document, never \"whatever is there now\"); `note` is required, not a placeholder, and not reused verbatim from another wrap-up's QA. Refused once the current version is already delivered.","operationId":"qa_wrapup","parameters":[{"description":"Engagement id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QaReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{wrapup_id, version, state: \"qa_passed\"|\"qa_failed\", snapshot_hash}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_wrapup}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: stale_version | delivered | hash_mismatch, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: reader_not_caller | hash_required | placeholder_note | note_reused, reason}`."}},"security":[{"bearer":[]}],"summary":"THE GATE, for a wrap-up: a person's own read of one named version (its\nnumber and hash), with a note that says what they checked. Agent and API\ntokens cannot record it (PLAN.md policy 9).","tags":["client_services"]}},"/v1/admin/files/{id}/audit":{"get":{"description":"Admin only. A staged, delivered or withdrawn deck's audit, as the client-deck skill wrote it (every claim with its pages, words and source), and the deck's metadata. Never shown to a client.","operationId":"get_deck_audit","parameters":[{"description":"File id of a deck","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, engagement_id, name, sha256, deck, audit}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: unknown, not a deck, or retracted."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/files/{id}/deliver":{"post":{"description":"Admin only. Deliver a staged file: the engagement's client (or a member of its linked workspace) can then read it with `get_file`/`list_my_files`. Idempotent — delivering an already-delivered file keeps its original `delivered_at` and clears any withdrawal. A delivered file is otherwise immutable: withdraw it and stage a new one to change it. A film delivers its poster and captions with it, needs a person's pass of its current `review_sha256` and a poster, and, when bound to a run, that run delivered to this engagement at the approved evidence hash it was bound to. A poster or captions file is never delivered on its own.","operationId":"deliver_file","parameters":[{"description":"File id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, state: \"delivered\", delivered_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: file_not_reviewed | brief_citation_changed | poster_required | film_run_changed | companion_follows_film, reason}`: no person's pass of these exact bytes, a cited run changed since the brief was composed, a film without a poster, a film whose run is not delivered at the hash it was bound to, or a poster or captions file delivered on its own."}},"security":[{"bearer":[]}],"summary":"Deliver one file. Track H: the same gate as a run — a person's own pass of\nthis exact file (its sha256), and for a composed brief, every cited run\ndelivered at the hash the brief cites. Delivering a delivered file again\nchanges nothing.","tags":["client_services"]}},"/v1/admin/files/{id}/link":{"get":{"operationId":"get_engagement_file_link","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Admin only: five-minute download capability for a staged or delivered file, revocable with its issuing MCP key."},"404":{"description":"not_found"}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/files/{id}/qa":{"post":{"description":"Admin, signed in as a person (email link or Google), never a token; not available over MCP. Record your own read of one staged file's exact bytes: `pass`, a note that says what you checked, and the `sha256` you read (must be current). A pass lets the file be delivered. For a film, send its `review_sha256` (the film with its current poster and captions; `list_engagement_files`): the read covers all three, and a pass needs a poster. A poster or captions file is reviewed with its film, not alone.","operationId":"qa_file","parameters":[{"description":"File id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileQaReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, review, sha256}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: hash_mismatch | delivered | poster_required | companion_follows_film}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: placeholder_note | note_reused | invalid}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/files/{id}/qa` — a person's own read of one file's exact\nbytes (policy 9): their sign-in, the sha256 they read, and a note that\nsays what they checked. A pass lets the file be delivered.","tags":["client_services"]}},"/v1/admin/files/{id}/stream":{"get":{"description":"Admin only. Watch or open a stored media file in any state (a reviewer previews a staged film before passing it): a 302 to a presigned URL valid for 5 minutes, or with `format=json` the same URL as `{url, expires_at}`. Never cached.","operationId":"stream_engagement_file","parameters":[{"description":"File id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"`json` answers `{url, expires_at}` with 200 instead of a 302 (for a page\nthat authenticates with a bearer header, which `<video src>` cannot send).","in":"query","name":"format","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"With `format=json`: `{url, expires_at}`."},"302":{"description":"Location: the presigned URL."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: no stored media file with this id."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: storage_unconfigured}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/files/{id}/text":{"get":{"description":"Admin only. One engagement file's text, whatever its state (staged, delivered or withdrawn), so a person can read it before passing it: its metadata, `state`, `sha256`, `review_sha256` (the hash a QA names) and, for a db-stored text format (markdown, CSV, JSON, plain text), its full `text` up to 2 MB. PDFs expose embedded text when extraction succeeds, labeled `text_source: extracted`; no OCR is claimed. Oversized text and stored media have null text. A retracted file is gone: 404.","operationId":"get_engagement_file","parameters":[{"description":"File id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, engagement_id, name, content_type, state, sha256, review_sha256, size_bytes, text, folder, title, storage, …}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown or retracted."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/files/{id}/text` — a staged (or delivered, or withdrawn)\nfile's text, so a person reads it in the page before passing it.","tags":["client_services"]}},"/v1/admin/files/{id}/withdraw":{"post":{"description":"Admin only. Withdraw a delivered file: the client can no longer read it. Idempotent. Stage a replacement (same folder/name) and deliver it to supersede. A film withdraws its poster and captions with it and keeps its stored object, so it can be delivered again; a poster or captions file is withdrawn with its film, not alone.","operationId":"withdraw_file","parameters":[{"description":"File id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{file_id, state: \"withdrawn\"}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: companion_follows_film}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/grant-proposals":{"get":{"description":"Admin only. One workspace's grant proposals (find_grants), newest first: `{items: [{id, run_id, workspace_id, funder_name, program, title, source_url, deadline, award_min_usd, award_max_usd, eligibility, excerpt, state, decided_by, decided_at, note, created_at, found_at_level, applicant_path, eligibility_verdict, eligibility_quote}]}` (at most 500). Each one is a proposal until a person approves it (PLAN.md policy 5). Eligibility is a hard filter: a solicitation whose stated applicant types exclude the applicant is listed with `state: ineligible`, `eligibility_verdict: ineligible` and the clause that says so in `eligibility_quote`, and can never be approved. `found_at_level` is the level of the search that found it (city, county, region, state, multi_state, federal, unknown). Proposals of a withdrawn run are not listed. Not exposed over MCP: the queue serves the person who decides it.","operationId":"list_grant_proposals","parameters":[{"description":"The workspace whose proposals to list (required).","in":"query","name":"workspace_id","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Only this run's proposals.","in":"query","name":"run_id","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"`proposed`, `approved`, `rejected` or `ineligible`.","in":"query","name":"state","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [...]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_required | invalid, reason}` — no `workspace_id`, or a `state` other than proposed, approved, rejected, ineligible."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: grants_unavailable}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/grant-proposals` — one workspace's grant proposals, newest\nfirst.","tags":["client_services"]}},"/v1/admin/grant-proposals/approve-all":{"post":{"description":"Admin only, and a person's own sign-in. Approves every proposal of the run that is still waiting (`proposed`; a solicitation recorded ineligible is never waiting) exactly as approving each one would, in one transaction, then resumes the run the way deciding its last proposal does. Answers `{run_id, approved: [ids], resumed}`; a run with nothing waiting answers `approved: []`.","operationId":"approve_all_grant_proposals","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantResumeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, approved, resumed}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: grants_unavailable}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/grant-proposals/approve-all {run_id}`: every proposal of\nthe run still waiting (state `proposed`, which an ineligible one never is)\nis approved as [`approve_grant_proposal`] approves one, in one\ntransaction, and deciding the last one resumes the run. Two checks\n(PLAN.md, 2026-10-13): a person, and proposals that exist for this run.","tags":["client_services"]}},"/v1/admin/grant-proposals/resume":{"post":{"description":"Admin only, and a person's own sign-in (never an agent or API token; not exposed over MCP). Resumes a find_grants run waiting on its proposals with the ones approved so far: each approved opportunity whose deadline has not passed is simulated, soonest deadline first. Proposals still undecided stay proposals (approving one later still records it, but it is not simulated by this run). A run also resumes by itself when its last undecided proposal is decided. Answers `{run_id, resumed: true, approved}`.","operationId":"resume_grant_proposals","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantResumeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Resumed."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no find_grants run with that id."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: nothing_approved}` — no approved proposal yet; `{error: already_decided}` — the run already resumed (or its wait expired); `{error: run_terminal}`."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: withdrawn}` — the run was withdrawn."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid, reason}` — no `run_id`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: grants_unavailable}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/grant-proposals/resume`","tags":["client_services"]}},"/v1/admin/grant-proposals/{id}/approve":{"post":{"description":"Admin only, and a person's own sign-in (never an agent or API token; not exposed over MCP). Approves one grant proposal (PLAN.md policy 5): resolves or mints the funder org in the proposal's workspace and records ONE GROUNDED `grant-opportunity` assertion on it, its source the opportunity's URL, its run the sourcing run, its value `{opp_id (sha256 of the URL), program, title, source_url, deadline, award_min_usd, award_max_usd, eligibility}`. Deciding the run's last undecided proposal resumes the run. Answers `{id, state: approved, entity_id, assertion: applied | unchanged, resumed}`.","operationId":"approve_grant_proposal","parameters":[{"description":"Grant proposal id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Approved."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_decided, state}`, or `{error: ineligible}` for a solicitation recorded ineligible (never decided)."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: withdrawn}` — the run was withdrawn."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: entity_rejected | invalid, reason}` — the record refused the funder or the assertion; nothing was written."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: grants_unavailable}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/grant-proposals/{id}/approve`","tags":["client_services"]}},"/v1/admin/grant-proposals/{id}/reject":{"post":{"description":"Admin only, and a person's own sign-in (never an agent or API token; not exposed over MCP). Rejects one grant proposal with a required `note`; nothing is recorded about the funder. Deciding the run's last undecided proposal resumes the run. Answers `{id, state: rejected, resumed}`.","operationId":"reject_grant_proposal","parameters":[{"description":"Grant proposal id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Rejected."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_decided, state}`, or `{error: ineligible}` for a solicitation recorded ineligible (never decided)."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: withdrawn}` — the run was withdrawn."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: note_required | invalid, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: grants_unavailable}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/grant-proposals/{id}/reject`","tags":["client_services"]}},"/v1/admin/metrics/directory":{"get":{"description":"Admin only. Distinct identities outside Timepoint active in the trailing 7 days (`created_at >= now() - 7 days`), and how many of them used MCP. One person can hold more than one identity (GitHub, email, member), so `active_identities` can count a person more than once. Active: an MCP tools/call (`identity.mcp_calls`, any outcome but `refused`), a meeting preview, a pin, or a run it created (root or child, any kind but `monitor`, the watch sweep's, and `onboarding`, a lineage row, which also leaves out a person's own calendar import; a pick's deep-simulation relaunch counts the original author). Counted: identities holding `user`, not `agent`, other than the house identity, not an administrator (effective `admin`: stored `admin` plus a verified `TP_ADMIN_EMAILS` address), not internal (a verified address at `timepointai.com`, exactly that domain; an unverified one does not exclude) and not a client login (`entity.clients`; a client's `member:` identities count). `excluded` counts the active identities removed as `internal` or as `clients` (one that is both counts once, as internal); administrators, agents and the house are not reported. `via_mcp` is the subset of the counted identities with an MCP call in the window. A read: counts only, never a name.","operationId":"get_directory_metrics","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectoryMetric"}}},"description":"`{window_days: 7, active_identities, via_mcp, excluded: {internal, clients}}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`, the `PUT /v1/admin/model-roles` refusal."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: metrics_unavailable}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/metrics/freemium":{"get":{"description":"Admin only. Today's free-preview funnel (UTC day): `new_accounts` (accounts created today), `new_accounts_with_first_preview` (of those, how many ran a quick preview or a meeting preview, free or paid), the free uses the house paid for today against each daily cap (`previews`, `play_turns`: `{used, cap}`; a refunded claim is not counted) and `refunded_today` (free claims given back because the work failed). Caps come from `TP_FREE_PREVIEWS_PER_DAY` and `TP_FREE_PLAY_TURNS_PER_DAY`. The audit behind it is `freemium.claims`. A read: counts only, never a name.","operationId":"get_freemium_metrics","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FreemiumMetric"}}},"description":"`{day, new_accounts, new_accounts_with_first_preview, previews: {used, cap}, play_turns: {used, cap}, refunded_today}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`, the `PUT /v1/admin/model-roles` refusal."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: metrics_unavailable}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/metrics/play":{"get":{"description":"Admin only. The Play funnel for today (UTC day) and the last 7 UTC days (today included): `challenges_live`; per window the attempts started and finished (`attempts_won`, `attempts_lost`, `attempts_abandoned`), turns stored by funding (`turns_free`, `turns_credits`), `distinct_players`, `win_rate` (won / (won + lost), null when none), `median_turns_per_finished_attempt`, `model_calls` (house-funded calls from `freemium.house_days.calls`, refunds included) and `failed_calls` (judge calls that stored no turn, `play.attempts.failed_calls`, both funding paths), and `estimated_house_cost_usd_upper_bound` (free turns x `per_turn_cost_cap_usd`, an upper bound, not a bill); plus the `top_challenges` by attempts in the last 7 days (slug and title only). A read: counts only, never an owner id, handle or table text.","operationId":"get_play_metrics","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlayMetric"}}},"description":"`{day, challenges_live, per_turn_cost_cap_usd, today, last_7_days, top_challenges}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: metrics_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Play funnel metrics (admin)","tags":["admin"]}},"/v1/admin/model-roles":{"get":{"description":"Admin only (MR2, gate G9). Per role: its current default, the stored slug (null = inherit), what it inherits from, where it is used, and the eligible registry models (live, text output, in the OpenRouter catalog or configured here). Also the deployment synthesis mode (single | alternate), its model pair, the A/B surcharge fraction and the alternation counter; and every eligible model with its registry prices. MR3's Boss model pickers read this.","operationId":"get_model_roles","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{roles:[{role, default, effective, source (stored | inherited:<parent> | env), configured, inherits, call_sites, eligible}], synthesis:{mode, pair, ab_extra_base_fraction, alternate_seq, modes, default_modes, ab_pricing_rule}, models:[{slug, display_name, tier, context_length, prompt_usd_per_token, completion_usd_per_token, in_catalog, configured, structured_outputs}]}`"},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["admin"]},"put":{"description":"Admin only (MR2, gate G9). Set per-role default models (a slug, or null to inherit again), the deployment synthesis mode (single | alternate), its model pair and the A/B surcharge fraction. A slug not yet registered triggers an on-demand OpenRouter catalog refresh (prices and parameters recorded; ledger prices untouched). Every named slug must then be registered, live, eligible (tp_llm::require_slug) and, when the catalog lists its parameters, list structured_outputs: beta sends a strict json_schema with provider.require_parameters (422 `model_lacks_structured_outputs`). A free model (`:free` or `openrouter/free`) is refused before anything else (422 `model_is_free`): free endpoints may log or train on prompts. Applies to runs admitted from now on; admitted runs keep their frozen models. Each change is logged append-only.","operationId":"put_model_roles","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RolesUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The updated admin view (same shape as GET)."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_model_roles, reason}`; nothing changed."},"503":{"content":{"application/json":{"schema":{}}},"description":"The on-demand catalog refresh failed upstream: `{error: catalog_unavailable, reason}`; nothing changed."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/models":{"get":{"description":"Every `llm.model_registry` row with its catalog fields and prices (admin only) — the data MR3's Boss model pickers and MR2's per-run role resolution read from.","operationId":"list_model_registry","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/RegistryModel"},"type":"array"}}},"description":"All registry rows, ordered by slug."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/models/refresh":{"post":{"description":"Admin only. Fetches OpenRouter's live `/api/v1/models` catalog RIGHT NOW and upserts `llm.model_registry` in one transaction: new slugs are added, changed prices/fields update in place (never re-tiering an operator-set row), and slugs absent from the catalog are marked `in_catalog=false` (never touching `live`, which stays a real-call probe result). No scheduler calls this — every refresh is this admin action. V2: the refresh then re-prices `ledger.price_list` against the held derivation (`/v1/admin/pricing`): a new all-items version only when model cost moved, the first refresh derives instead, and every attempt is recorded. Only when `auto_reprice_enabled` is 1 (it ships 0: `PUT /v1/admin/pricing/params`); a move above `max_step_ratio` is held for `POST /v1/admin/pricing/confirm`.","operationId":"refresh_model_catalog","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{seen, added, updated, removed, catalog_hash, pricing}`; `pricing` is `{status: disabled | derived | repriced | error, ...}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."},"502":{"content":{"application/json":{"schema":{}}},"description":"The OpenRouter fetch or parse failed; nothing was written. `{error: catalog_upstream_error, reason}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/models/{slug}/revive":{"post":{"description":"Admin only. Sets `live = true` and `last_probe = now()` on one registered `llm.model_registry` row, undoing a dead-slug mark (a provider 404) without a hand UPDATE. Makes no provider call; the next real call is the probe. Catalog refresh never touches `live`, so this is the only revive.","operationId":"revive_model","parameters":[{"description":"Registry slug, its `/` percent-encoded as `%2F` (e.g. `anthropic%2Fclaude-haiku-5.5`).","in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevivedModel"}}},"description":"The revived row `{slug, live, last_probe}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"Slug not registered: `{error: unknown_model, reason}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/play/challenges":{"get":{"description":"Admin. Play challenges, newest first (at most 200), filtered by `status`. The admin view includes `table_def` (personas, concerns, red lines), which no public route serves.","operationId":"list_play_challenges","parameters":[{"description":"`draft`, `live`, `retired` or `all` (default).","in":"query","name":"status","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [...]}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_challenge}`: unknown status."}},"security":[{"bearer":[]}],"summary":"List every Play challenge (admin)","tags":["admin"]},"post":{"description":"Admin. Compile a Play challenge from a simulation: exactly one of `run_id`, `news_id` (the story run behind a news card) or `preview_id`. One structured model call (the planning role) drafts the table: personas with concerns and red lines, the seat, the goal and the brief. The draft must pass the engine's checks (weighted starting support at most 0.40, no win reachable in one turn, 2 to 4 concerns per persona) and the naming rule: only a deceased public figure that a live Gallery moment vouches for is named; everyone else is a role, and no other person's name reaches the model or the challenge. A refused draft gets one more call. The challenge is stored as a `draft`; it goes live only when a person approves `POST /v1/admin/play/challenges/{id}/publish`.","operationId":"compile_play_challenge","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompileReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The draft challenge (admin view, with `table_def`) and `compile: {model_slug, cost_usd, attempts, version}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: no such run or preview."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_challenge, reason}`: not exactly one source, or the run has no cockpit_doc."},"502":{"content":{"application/json":{"schema":{}}},"description":"`{error: compile_failed, reason}`: the model's draft was refused twice."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: compiler_unavailable}`: no model key, no planning model, the supply gate is closed, or the call failed."}},"security":[{"bearer":[]}],"summary":"Compile a Play challenge (admin)","tags":["admin"]}},"/v1/admin/play/challenges/{id}/publish":{"post":{"description":"Admin. Ask for a draft challenge to go live. This never publishes: it stages a `publish_challenge` action (as `POST /v1/staged-actions` would) that a person approves in /service with their own browser sign-in (email link or Google). An agent, an approval grant, an operator's token and any other token cannot approve it (403 `person_sign_in_required`). Staging runs the publish checks and rolls them back: the challenge must still be a draft, pass every compile check under the naming rule derived again from its source, and a public board must pin `model_slug`. Staging again while one is open returns the open one (200).","operationId":"publish_play_challenge","parameters":[{"description":"Challenge id, `ch_` and 32 hex digits","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"An open request to publish this challenge already exists; it is returned unchanged."},"201":{"content":{"application/json":{"schema":{}}},"description":"The staged `publish_challenge` action, status `requested`."},"400":{"content":{"application/json":{"schema":{}}},"description":"`{error: bad_id}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_refused, refusal}`: the refusal is `not_found`, `not_draft` or `invalid_challenge`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_staged_action, reason}`: a bad model_slug."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_actions_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Stage publishing a Play challenge (admin)","tags":["admin"]}},"/v1/admin/pricing":{"get":{"description":"Admin only. The derived-pricing record: the process params, the served price list, the held derivation, and the latest 50 derivations and re-prices (append-only; before and after credits for every re-price).","operationId":"get_pricing","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{params, price_list: {version, items}, held_derivation_id, derivations: [...], reprices: [...]}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/pricing/confirm":{"post":{"description":"Admin only. Apply the moves a re-price HELD because they exceeded `max_step_ratio` (optionally only `items`) as a new price_list version copying every item, recorded in the re-price log with trigger `admin_confirm`. Refused 409 when the price list, the held derivation or the registry prices changed since that record: re-run the re-price and confirm the new record.","operationId":"confirm_pricing","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepriceOutcome"}}},"description":"The confirm's re-price outcome."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no such re-price record."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: pricing_conflict, reason}` — nothing held, or stale."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_pricing_request}` — an item that record did not hold."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/pricing/derive":{"post":{"description":"Admin only. Re-derive the implied model cost per credit from today's credits, recorded usage and registry prices, and hold it for re-pricing. Changes no credits; the derivation is appended to the record.","operationId":"derive_pricing","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{derivation_id, derivation}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/pricing/params":{"put":{"description":"Admin only. Set pricing process params: `{key: value}` over the known keys (`auto_reprice_enabled` 0/1 or a boolean, `rederive_threshold`, `usage_window_days`, `min_sample_units`, `max_step_ratio`), each bounded by its row. All or nothing; every change is logged append-only with the actor.","operationId":"put_pricing_params","requestBody":{"content":{"application/json":{"example":{"auto_reprice_enabled":1},"schema":{}}},"description":"`{key: value}`","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingParams"}}},"description":"The params after the change."},"403":{"content":{"application/json":{"schema":{}}},"description":"Caller lacks `admin`: `{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_pricing_request, reason}` — unknown key, not a number, a fraction for a whole-number key, or out of bounds. Nothing changed."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/queue":{"get":{"description":"Admin only. One queue of everything that needs a person, across clients (R2): run reviews (`run`), client-work runs nobody has attached yet (`unattached_run`; the default view shows only the caller's own, other creators' are `not_mine`, including failed runs, with `created_by`, `mine`, `run_state` and `has_doc`; only the creator can attach), wrap-ups (`wrapup`, id = engagement id), client files and films (`file`, `film`; a film's poster and captions follow it and are not listed), staged actions waiting for approval (`staged_action`, with `can_approve` and `approve_refusal` for this caller and credential, and the approval grant fields `grantable`, `can_grant`, `can_approve_with_grant` and `grant`), pending research proposals (`research`, one row per drill-down job, with `pending` and `workspace_id`), grant proposals waiting for a person's decision (`grant_proposals`, id = the find_grants run, one row per run with at least one proposal still `proposed`, `next_action: decide`, with `run_id`, `workspace_id`, `awaiting` (how many are still proposed), `run_state` and `phase`; `engagement_id` and `client_owner` name the engagement linked to the run's workspace: the only one, else the only live one, else null, and the client is that engagement's, else the one client all of the workspace's engagements serve, else null) and pending cast additions (`cast_addition`). Every row has a short `title` (at most 60 characters, never empty), `summary`, `state`, `grade` (`A`/`B`/`C`/`F` for runs; `F` for a staged brief whose cited runs changed; else null) with `grade_reason`, `open_flags`, `next_action`, the `batch_actions` valid for it now, `hash` (a run's evidence hash, a file's review hash, a wrap-up's current snapshot hash) and its age. Run rows also expose the full `query`, execution `run_state` and `has_doc` separately from review state and quality; a review row also has `engagement_live`. Rows whose `next_action` is `none` are left out unless `include_done=true`. The default view also leaves out rows nobody will act on, each marked with its `hidden_reason`: `house_run` (an unattached run made by the house identity, a news scan's child, or a public research project's run; listed with `kind=unattached_run`), `stale_run` (any other unattached run that failed or was made more than 7 days ago; listed with `kind=unattached_run`), `not_mine` (any other unattached run made by someone other than the caller, who alone can attach it; listed with `kind=unattached_run`), `closed_engagement` (a review of a closed engagement, which can no longer be QA'd or delivered, and that engagement's files and wrap-up while they only wait for a read; a file or wrap-up ready to deliver stays listed; the waiting ones are listed under their own `kind`) and `demo_client` (a demo client's row; listed under that client's `client` filter). `include_hidden=true` lists them all. `counts` and `clients` cover every row `include_done` admits, hidden or not, before the `client`/`kind`/`needs` filters; `counts.hidden` says how many of them the default view leaves out (`total`, `by_reason`, `by_next_action`). Ordered by what needs a person now: approve, then deliver, then read, reread, ack_flags and decide, then the rest; newest first within each. Reading the queue runs a missing pre-QA check for a review at `draft` or `qa_failed` whose run is complete, never for any other review.","operationId":"get_review_queue","parameters":[{"description":"Only rows serving this client (its `owner_id`).","in":"query","name":"client","required":false,"schema":{"type":"string"}},{"description":"Only rows of this kind: `run`, `unattached_run`, `wrapup`, `file`,\n`film`, `staged_action`, `research`, `grant_proposals` or `cast_addition`.","in":"query","name":"kind","required":false,"schema":{"type":"string"}},{"description":"Only rows whose `next_action` is this: `ack_flags`, `read`, `deliver`,\n`reread`, `fix`, `revise`, `approve`, `decide`, `attach` or `none`.","in":"query","name":"needs","required":false,"schema":{"type":"string"}},{"description":"`true` also lists rows that need nothing now (`next_action: none`, e.g.\ndelivered work). Default `false`.","in":"query","name":"include_done","required":false,"schema":{"type":"boolean"}},{"description":"`true` also lists the rows the default view leaves out (`hidden_reason`:\n`house_run`, `stale_run`, `not_mine`, `closed_engagement`, `demo_client`). Default\n`false`.","in":"query","name":"include_hidden","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{key, kind, id, client_owner, client_name, engagement_id, engagement, title, summary, state, grade, grade_reason, open_flags, next_action, batch_actions, hash, created_at, updated_at, age_seconds, hidden_reason}], counts: {total, by_kind, by_next_action, by_client, hidden: {total, by_reason, by_next_action}}, clients: [{owner_id, display_name}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid, reason}` — an unknown `kind` or `needs`, an `include_done` or `include_hidden` that is not true or false, or an oversized `client`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/retractions/preview":{"post":{"description":"Admin (agent tokens included). Preview a retraction of simulations (runs), kickoffs, wrap-ups and client files in one workspace. Writes nothing. Returns, per target, whether it was found in the workspace, its state, whether the client can see it now, `blocked` (`not_found`, `not_in_workspace`, `already_withdrawn`, `live`, `published`) and `rows`, the rows per table it would delete, redact, revoke or keep; `research_safety`, approved research facts whose only support is a row it would delete; `dependents`, other items that quote a target and must be retracted with it; `child_runs` (shown only); `totals`; `retractable`; each target's current `label` (not hashed, never stored); and `preview_hash`. To retract, stage `retract_items` with `POST /v1/staged-actions` and target `{workspace_id, targets, preview_hash, research_at_risk}`; a person approves it in /service. Research (entities, edges, GROUNDED and GENERATED facts, drill-down jobs and proposals) is never touched.","operationId":"preview_retraction","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{workspace_id, research_at_risk, targets: [{type, id, found, state, client_visible, retractable, blocked, rows, label}], dependents, child_runs, research_safety, totals, retractable, preview_hash}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_workspace}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_retraction, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: retractions_unavailable, retryable: true}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/reviews":{"get":{"description":"Admin only. Every review (a run attached to a client via intake), newest first: the review row plus the run's creator and state, its engagement's question key (`question`) and side, the client's display name, whether it has a cockpit doc, whether its kind is gate-required and the gate's verdict, and a count of policy-9 person reads recorded against it. `title` is the SHORT title (≤ 60 characters: the one written at intake, else derived from the run's question, `title_derived: true`), `summary` likewise (≤ 160), and `query` the run's whole question. Every non-withdrawn review carries its grade (R3: `grade` A/B/C/F with `grade_reason`, `open_flags`, `lint_current`, `preliminary` `{verdict, by, at, current}` or null, `evidence_hash`) and what it needs next (`next_action`, `batch_actions`), computed exactly as `get_review_queue` computes them. Reading the list runs a missing pre-QA check for a review at `draft` or `qa_failed` whose run is complete, never for any other review. Filter with `state` (`draft`, `qa_passed`, `qa_failed`, `delivered`).","operationId":"list_reviews","parameters":[{"description":"Filter to one review state: `draft`, `qa_passed`, `qa_failed` or `delivered`.","in":"query","name":"state","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{run_id, client_owner, client_name, engagement_id, state, title, summary, title_derived, query, question, side, originator, run_state, has_doc, run_kind, gate_required, gate_verdict, person_reads, grade, grade_reason, open_flags, lint_current, preliminary, evidence_hash, next_action, batch_actions, …}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only. Attach a prepared run (created by the calling admin) to a client under a live engagement, starting it at `draft` — or, if it is already attached to THIS client and engagement, update its intake fields (title, summary, og title/description, resolution criterion, reality note; only the fields sent change). The engagement must exist, be live, and belong to `client_owner`; a workspace-linked engagement additionally requires the run to be in that workspace. Once the review reached `delivered` only `title` and/or `summary` are accepted (under the same authorizations: the calling admin must have created the run): each value that differs is written and one `retitled` history row names what changed (`short title set`, `summary set`); an identical repeat changes nothing and writes no row. Any other field on a delivered run answers 409 `already_delivered`. Refused when the run is already assigned to a different client or engagement. `title` (≤ 60 characters, shown in every list) and `summary` (≤ 160) are trimmed and never cut: an empty or longer value is a 422. Without them a first intake uses `og_title` / `og_description` when they fit, else lists derive both from the run's question. When the run is complete and the review is at `draft` or `qa_failed`, intake runs the pre-QA check of the run as it stands (track F) in the same transaction; a check that cannot run never loses the intake.","operationId":"intake_review","parameters":[{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response (header idempotent-replay: true) instead of re-running; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, state}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_engagement | not_found}` — no live engagement for this client, or the run does not exist / is not the caller's."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_outside_workspace | already_assigned | already_delivered, reason}` — `already_delivered` when a delivered run is sent anything but `title`/`summary`."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: withdrawn, reason}` — the run was withdrawn by a retraction."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input, reason}` — `title` is empty or over 60 characters, or `summary` is empty or over 160 characters."}},"security":[{"bearer":[]}],"summary":"INTAKE: attach a prepared run to a client under a live engagement. The\nengagement must exist, be live, and belong to that client — preparing\nwork outside a claimed question is exactly what the conflict rule forbids.","tags":["client_services"]}},"/v1/admin/reviews/{run_id}/deliver":{"post":{"description":"HAND-OFF. Admin only, a person's own sign-in. Two checks (2026-10-13): the caller is a person, and the run is complete with a document. A review not yet passed is passed in the same call (history rows `qa_passed` with the note \"passed at delivery\", then `delivered`); the approved evidence hash becomes the run as served now. The run's own gate report, the scenario contract and the pre-QA lint are information on the card, never a block. At `delivered` the client's read of this run opens. Delivering an already-`delivered` review is not a conflict: it answers 200 with the same body and `already_delivered: true`, and writes no second history row.","operationId":"deliver_review","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, state: \"delivered\"}`, plus `already_delivered: true` on a repeat delivery."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | no_engagement}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_qa_passed | work_not_ready | gate_report_missing | gate_blocked | scenario_not_ready | review_content_changed, reason}` — anything not `qa_passed` and not already `delivered`."}},"security":[{"bearer":[]}],"summary":"HAND-OFF. Refuses anything not at qa_passed — the gate cannot be walked\naround, only through. At `delivered` the client's read opens.","tags":["client_services"]}},"/v1/admin/reviews/{run_id}/entities":{"get":{"description":"Admin only (an agent token may read it). The run's entities: its roster (origin `selected`, `planner_added` or `planner`), selected cast members that never reached the roster (`selected`), and actors the planner proposed (`proposed`, with `proposal_state`). Each says whether a live entity in the run's own space holds it (`in_registry`), whether the run requires it, how many moments and dialogue lines it appears in, and `never_appears` (required, in no moment and not named in the text: the pre-QA check's `required_missing` rule). Read-only; it never writes a check.","operationId":"get_review_entities","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, evidence_base, entities: [{name, kind, id, path, origin, proposal_state, in_registry, required, appearances: {moments, dialog}, never_appears}], counts: {entities, selected, planner_added, planner, proposed, required_missing}}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found, reason}`: no such run."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: withdrawn, reason}`: the run was withdrawn."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable, reason}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/reviews/{run_id}/entities` — R4: who the run could use and\nwho appeared. Read-only: it never writes a lint.","tags":["client_services"]}},"/v1/admin/reviews/{run_id}/evidence":{"get":{"description":"Admin only. The evidence hash a reader attests to when they QA or human-read this item: the review's current evidence hash next to the hash the last passing QA approved, whether they match, and the run's own quality-check gate (`{required, verdict}`). Send `evidence_hash` back with `qa_review`, `record_human_read` or `preliminary_qa` to name the version read.","operationId":"get_review_evidence","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, state, evidence_hash, approved_evidence_hash, matches_approved, gate}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no review for this run."}},"security":[{"bearer":[]}],"summary":"`GET /v1/admin/reviews/{run_id}/evidence` — the hash a reader attests to:\nthe evidence hash of the run as it is served now, next to the hash the\nlast passing QA approved. The review UI shows it and sends it back.","tags":["client_services"]}},"/v1/admin/reviews/{run_id}/history":{"get":{"description":"Admin only. The full append-only history of one review: every state transition and attestation (draft, human reads, preliminary and gate QA verdicts, delivery, merges, workspace links), oldest first.","operationId":"get_review_history","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [...]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/admin/reviews/{run_id}/human-read":{"post":{"description":"Admin only, and only a person's own sign-in (email link or Google login) — an agent or API token gets `person_sign_in_required`; use `preliminary_qa` instead (PLAN.md policy 9). Records that the signed-in person read this one item, whatever its state (a `delivered` review may still be read again) — append-only history, the review itself is unchanged. `evidence_hash` must name the CURRENT hash (`get_review_evidence`), and `note` must say something about this item, not a placeholder and not reused verbatim from another item's read.","operationId":"record_human_read","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HumanReadReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, recorded: \"human_read\", reader, evidence_hash}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no review for this run."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: hash_mismatch, reason}` — the item changed since `evidence_hash` was read."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: reader_not_caller | note_required | hash_required | placeholder_note | note_reused, reason}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/reviews/{run_id}/human-read` — record that the signed-in\nperson read this one item, whatever its state (a delivered review cannot\npass through the QA gate again). Append-only history; the review itself\nis unchanged. Needs a person's own sign-in, the hash read, and a real note.","tags":["client_services"]}},"/v1/admin/reviews/{run_id}/preliminary-qa":{"post":{"description":"Admin only — any admin caller, including an agent or API token. Records a preliminary pass/fail read against the current evidence hash; it NEVER changes the review's state (only a person's `qa_review` can pass or fail a review) and is never treated as the human read. Refused once the review is `delivered`. Same evidence-hash and note rules as `record_human_read`.","operationId":"preliminary_qa","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreliminaryReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, recorded: \"qa_preliminary\", verdict, state, evidence_hash}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | no_engagement}` — no review for this run, or its engagement is closed."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_delivered | work_not_ready | hash_mismatch, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: note_required | placeholder_note | hash_required | note_reused, reason}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/reviews/{run_id}/preliminary-qa` — an agent's (or a\nperson's) preliminary read of one item. Any admin caller, including agent\nand API tokens. It records a note and a pass/fail verdict against the\ncurrent hash, and NEVER changes the review's state: only a person's\n`…/qa` can pass or fail a review, and a preliminary QA is never a human\nread.","tags":["client_services"]}},"/v1/admin/reviews/{run_id}/qa":{"post":{"description":"THE GATE. Admin only, and only a person's own sign-in (email link or Google login) — an agent or API token gets `person_sign_in_required`; it records a preliminary QA instead (PLAN.md policy 9). A `pass` needs a readable, complete result AND (for a `deep_sim`/`portal_sim` run) its own quality-check report to have passed, plus any scenario contract on the plan; a `fail` needs only a readable result. `evidence_hash` must name the CURRENT hash, and `note` is the human read's required evidence — not a placeholder, not reused verbatim from another item's read. Refused once `delivered`.","operationId":"qa_review","parameters":[{"description":"Run id (the review's id)","in":"path","name":"run_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QaReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, state: \"qa_passed\"|\"qa_failed\", evidence_hash}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | no_engagement}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_delivered | work_not_ready | gate_report_missing | gate_blocked | scenario_not_ready | hash_mismatch, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: reader_not_caller | note_required | hash_required | placeholder_note | note_reused, reason}`."}},"security":[{"bearer":[]}],"summary":"THE GATE. A person's own read against the boundary list: their sign-in\n(never an agent or API token), the hash of the version they read, and a\nnote that says what they checked. A run whose own quality check blocked\nit cannot pass. Records who and when.","tags":["client_services"]}},"/v1/admin/runs/{id}/picks/{option_id}/rerun":{"post":{"description":"Admin only, and a person's own sign-in (never an agent or API token; not exposed over MCP). Relaunches the deep simulation of one pick of a delivered find_grants or find_money run whose last deep simulation failed (or was withdrawn): a new `deep_sim` run with the failed one's question word for word, its workspace, its owner and applicant, and the same `parent_run_id`, admitted as the pick's own was (no reservation; its plan card is the spend consent). The parent records the relaunch as a `run.child` event `{child_run_id, opp_id, kind: deep_sim, rerun_of, by}`, so the grant book shows the newest. Answers 202 `{run_id, parent_run_id, option_id, rerun_of, kind: deep_sim, state: created, query}`.","operationId":"rerun_pick_deep_sim","parameters":[{"description":"The find_grants or find_money run","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"The pick: its `opp_id`, as the run's pick_opportunities options and artifact `picks` carry it","in":"path","name":"option_id","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Relaunched."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no run with that id; `{error: pick_not_found}` — the run's delivered picks do not name this option, or no deep simulation of it was started."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_a_search, kind}` — the run is not a find_grants or find_money run; `{error: deep_sim_not_failed, state, child_run_id}` — the pick's last deep simulation has not failed (it is created, running or complete)."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: withdrawn}` — the run was withdrawn."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: grants_unavailable | deep_sim_unpriced}`."}},"security":[{"bearer":[]}],"summary":"`POST /v1/admin/runs/{id}/picks/{option_id}/rerun`","tags":["client_services"]}},"/v1/admin/seals/{id}/settle":{"post":{"description":"Admin. Ask for a public sealed forecast to be settled, with no deploy. This never writes the settlement: it stages a `settle_seal` action (as `POST /v1/staged-actions` would) that a person approves in /service with their own browser sign-in (email link or Google). An agent, an approval grant and an operator's token cannot approve it (403 `person_sign_in_required`). Staging runs the settlement's checks and rolls them back: `preview.would_return` is the public seal record as it would read. On approval the settlement is recorded (final; it is never edited) and `/public/v1/seals/{id}` serves it at once, with `settlement_status`, `resolution`, `hit` and `resolved_at`. `resolution` (`yes`, `no` or `void`) is required and must match the outcome's first word. Staging again while one is open for this seal returns the open one (200).","operationId":"settle_seal","parameters":[{"description":"Seal id, `seal_` and 12 hex digits","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettleReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"An open request to settle this seal already exists; it is returned unchanged."},"201":{"content":{"application/json":{"schema":{}}},"description":"The staged `settle_seal` action, status `requested`."},"400":{"content":{"application/json":{"schema":{}}},"description":"`{error: bad_id}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_refused, refusal}`: the refusal is `unknown_seal`, `already_settled` or `invalid_settlement` (a date before the seal or in the future)."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_staged_action, reason}`: a bad date, outcome or source_url."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_actions_unavailable}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/admin/work":{"get":{"description":"Admin only. The caller's own completed simulations (`quick_sim`, `deep_sim`, `portal_sim`, `story_sim`) not yet attached to any review — the pool `intake_review` draws from. `title` is a short title derived from the run's question (≤ 60 characters); `query` is the whole question.","operationId":"list_work","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{id, title, query, state, created_at, has_doc}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/billing/checkout":{"post":{"operationId":"checkout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Create a Checkout Session for a credit pack; the client redirects to url. `{session_id, url}`."},"503":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"`{error: billing_test_mode}`: this deployment holds a Stripe test key and does not allow test checkout; or `{error: stripe_unconfigured}`: no Stripe key."}},"security":[{"bearer":[]}],"summary":"Create a Checkout Session for a credit pack; the client redirects to `url`.","tags":["billing"]}},"/v1/billing/confirm":{"post":{"operationId":"confirm","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"{granted, credits, balance} — idempotent"},"409":{"description":"session exists but is not paid yet"}},"security":[{"bearer":[]}],"summary":"Server-side verification + idempotent grant. Safe to replay.","tags":["billing"]}},"/v1/budget":{"get":{"description":"Every provider budget layer the LLM gate consults, before anything fails, in the gate's own order (`order`: account, key, daily_budget): `{supply: metered | unlimited, gate, refusing_layer, order, account: {state: ok | no_headroom | unknown | not_configured, headroom_usd, floor_usd, spendable_usd}, key: {state: ok | no_headroom | no_key | unknown | unlimited, limit_remaining_usd, floor_usd, spendable_usd}, daily_budget: {state: ok | exhausted | unknown, spent_today_usd, budget_usd, remaining_usd}, note, credits: {account: present | none, balance, held, available}}`. Every layer is observed (none is skipped because an earlier one refuses); `gate` is the gate's own answer over them and `refusing_layer` the first layer that refuses, or null. `spendable_usd` is what a layer allows before the gate refuses (amount minus floor); a key `unlimited` has no limit of its own and the account governs. Under unlimited supply the layers are null and nothing is checked over the network. With `cap_usd` the response adds `step: {cap_usd, covered, short_layers, unknown_layers}`: a layer covers the step when its spendable amount is at least the cap; an unknown amount is never covered (`covered: null`); one short layer makes `covered: false`. `credits` is the caller's own ledger. Numbers and states only; no key is ever returned. Admin only.","operationId":"get_budget_gates","parameters":[{"description":"Optional: what one planned step may cost at most, in USD (greater than\n0, at most 1000). The response then carries `step`.","in":"query","name":"cap_usd","required":false,"schema":{"format":"double","type":"number"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The gate report, `credits`, and `step` when `cap_usd` is given."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_cap_usd}`: not a number, not above 0, or above 1000."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: budget_unavailable}`."}},"security":[{"bearer":[]}],"tags":["budget"]}},"/v1/calendar/confirm":{"post":{"description":"Write EXACTLY the attendees a human confirmed. Body: `{event: {id, title?, starts_at?, all_day_on?}, attendees: [{email?, display_name?, raw?, entity_id?}]}`. An attendee with `entity_id` is attached to that existing person (re-point); without one it is resolved or minted by name through `entities::resolve_or_mint`. Skipped attendees are simply absent from the list, and an EMPTY list writes nothing at all (`written: false`, not even a lineage row). Each confirmed person receives two GROUNDED assertions through the registry's own gate — `email` (which makes them matchable by address next time) and `calendar-invite` (the event id, title and time) — with `source = google_calendar:<event id>`, so the provenance names the calendar event itself. The human confirmation is recorded as lineage: the assertions ride an `onboarding` run row created by the confirming account. Idempotent: re-confirming the same event writes nothing new. Returns `{written, import_id, confirmed: [...], skipped: [...]}`.","operationId":"calendar_confirm","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{written, import_id, event_id, confirmed, skipped, note}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request | unknown_entity | entity_refused, reason}`."}},"security":[{"bearer":[]}],"summary":"Write the human-confirmed attendees into the registry.","tags":["calendar"]}},"/v1/calendar/connect":{"post":{"description":"Mint a single-use Google consent link for THIS account. Returns `{authorize_url, expires_at, scope, redirect_uri}`; the browser navigates to `authorize_url`, Google returns to the callback, and the refresh token is stored server-side, ENCRYPTED AT REST under a key this deployment holds outside the database — it is never returned to the browser and never rides a URL. The requested scope is exactly one: `calendar.events.readonly`. When the deployment holds no Google OAuth client, or no encryption key for the stored credential, the answer is a typed 503 `calendar_not_configured` naming the unset variables — there is never a link that leads nowhere, and a deployment that cannot store the credential safely never asks for one.","operationId":"calendar_connect","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{authorize_url, expires_at, scope, redirect_uri}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: calendar_not_configured, reason}` — the reason names the missing environment variables."}},"security":[{"bearer":[]}],"summary":"Begin connecting a Google Calendar: mint a single-use consent link.","tags":["calendar"]}},"/v1/calendar/connection":{"delete":{"description":"Disconnect this account's Google Calendar. The stored refresh token is revoked AT GOOGLE and then FORGOTTEN (the row's ciphertext and nonce are both nulled — a revoked connection never keeps a dormant standing capability). Idempotent: disconnecting when nothing is connected answers 200 with `{was_connected: false}` and still deletes any event copies and calendar summaries left behind. If the stored credential cannot be decrypted here it is still forgotten, and the response says `revoked_at_google: false` with the address to finish the withdrawal — forgetting a permission we cannot open beats keeping it. Entities and assertions already imported, and meeting previews already run, are NOT deleted — they are part of the record, and the response says so. The event copies saved for meeting previews (titles, descriptions, attendee names) and Trajectory's calendar summaries (aggregate snapshots of hours per category) ARE deleted with the permission, in the same transaction: if either delete fails the call fails and the calendar stays connected, so a retry finishes the job. Category rules the person saved in Trajectory stay with their profile (the note says so) until they forget their Trajectory.","operationId":"calendar_disconnect","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{was_connected, revoked_at_google, note}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"Disconnect the calendar: revoke at Google, then forget the token.","tags":["calendar"]}},"/v1/calendar/events":{"get":{"description":"This account's upcoming primary-calendar events over a forward window (`days` 1..=30, default 7; `max` 1..=50, default 50), recurrences expanded into dated instances and ordered by start time. ALWAYS 200 with an envelope `{connected, state, reason, calendar_label, window, events}`; `state` is one of `ok` | `not_configured` (this deployment holds no Google OAuth client, or no key for the stored credential) | `not_connected` | `reauthorize` (the grant died) | `scope_missing` (connected, but the calendar permission was withheld — the UI must offer re-authorize, NOT show an empty list) | `token_unreadable` (a connection IS stored and this deployment cannot decrypt it — a missing or changed key; the UI must offer reconnect, NOT show an empty list) | `upstream_error`. Every non-`ok` state carries `events: []` AND a reason, so an empty list is never ambiguous. Each event carries `{id, title, description, start, end, location, all_day, duration_minutes, attendees:[{email, display_name, name_basis, organizer, is_self, response_status}]}`; `start`/`end` are `{kind: at, at}` for a real instant or `{kind: all_day, at: null, date}` for an all-day boundary — a bare date is never widened into an instant. An `ok` read SAVES each delivered event's id, title, start, length, description, and for each attendee the name the calendar gave (an attendee the calendar names only by address is saved unnamed: never the address, nor a name made from it) and whether they are the organizer or you, private to this account, and only while the calendar is connected, so a meeting preview can start from an event id; a copy no read refreshes for 30 days is deleted, and disconnecting deletes them all. It also records the last successful sync time. It writes nothing to the registry and nothing at Google.","operationId":"calendar_events","parameters":[{"description":"Forward window in days, 1..=30 (default 7).","in":"query","name":"days","required":false,"schema":{"format":"int64","type":"integer"}},{"description":"Max events, 1..=50 (default 50).","in":"query","name":"max","required":false,"schema":{"format":"int32","minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The envelope described above."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: calendar_unavailable}` — the database, not the calendar."}},"security":[{"bearer":[]}],"summary":"The owner's upcoming calendar events, normalized.","tags":["calendar"]}},"/v1/calendar/imports/{id}":{"get":{"description":"Read back one confirmed calendar import by its `import_id`: `{import_id, description, imported_at, people: [{entity_id, path, display_name, email, event}]}`. Owner-scoped — somebody else's import (or an unknown one) is the SAME 404, never a 403, which would confirm it exists.","operationId":"calendar_import","parameters":[{"description":"The `import_id` from a confirm call.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The import record."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown, or not yours."}},"security":[{"bearer":[]}],"summary":"What one confirmed import wrote.","tags":["calendar"]}},"/v1/calendar/match":{"post":{"description":"PROPOSE registry matches for a list of attendees. Body: `{attendees: [{email?, display_name?, raw?}]}` — `raw` accepts a typed token like `\"Jane Doe <jane@co.com>\"`. For each attendee the response carries `{email, display_name, name_basis, match, matched, alternatives, would_mint_path}`: `match` is `existing` or `new`, `matched` names the dossier it looks like and WHY (`basis`: `email` — this exact address is already on that person — or `name`), `alternatives` are other people you can re-point to, and `would_mint_path` states exactly what a confirm would create when nothing matched. Attendees that parse to nothing come back under `skipped` with a reason. THIS ENDPOINT WRITES NOTHING: no entity is minted and no assertion is stored until POST /v1/calendar/confirm names the ones a human accepted.","operationId":"calendar_match","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{proposals: [...], skipped: [...], written: false}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."}},"security":[{"bearer":[]}],"summary":"Propose entity matches for a list of attendees — resolve-only, zero writes.","tags":["calendar"]}},"/v1/cast-approvals":{"get":{"description":"Cast approvals, newest first (at most 200). An admin sees every account's requests in every state; any other caller sees their own account's. Each record carries `usable` (approved and unexpired), `expired`, and its append-only `history`.","operationId":"list_cast_approvals","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{approvals: [...]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: cast_approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]},"post":{"description":"Request a cast approval for the CALLER's account: a deep_sim or portal_sim run may then admit a cast above the admin tier (20), up to `max_cast`, by sending `cast_approval_id`. Any operator may request; only an admin approves. The request is recorded append-only and starts in state `requested`.","operationId":"create_cast_approval","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCastApprovalReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The approval record, state `requested`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_cast_approval, reason}` — empty reason, `max_cast` not above the admin tier or above `cast_approval_max_cast`, or `valid_days` out of bounds."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: cast_approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/cast-approvals/{id}/approve":{"post":{"description":"Admin only. Approve a `requested` cast approval: it becomes usable by its account's runs until `expires_at` (now + `valid_days`) or a revoke.","operationId":"approve_cast_approval","parameters":[{"description":"Approval id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CastApprovalDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The approval record, state `approved`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: cast_approval_conflict, reason}` — not in state `requested`, or N is above the current `cast_approval_max_cast`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/cast-approvals/{id}/reject":{"post":{"description":"Admin only. Reject a `requested` cast approval. Final.","operationId":"reject_cast_approval","parameters":[{"description":"Approval id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CastApprovalDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The approval record, state `rejected`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: cast_approval_conflict, reason}` — not in state `requested`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/cast-approvals/{id}/revoke":{"post":{"description":"Admin only. Revoke an `approved` cast approval: no NEW run may use it. Runs already admitted under it keep their frozen cast cap.","operationId":"revoke_cast_approval","parameters":[{"description":"Approval id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CastApprovalDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The approval record, state `revoked`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: cast_approval_conflict, reason}` — not in state `approved`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/claims/{content_hash}":{"get":{"description":"Every moment sharing this `content_hash` — the same CLAIM, each row one TELLING — in chronological order. The hash is the link (tt-core's canonical hash over {label, occurs_at, participants}); there is no join table. 404 when no moment carries the hash.","operationId":"get_claim","parameters":[{"description":"Full content hash, `sha256:<hex>`","in":"path","name":"content_hash","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{content_hash, tellings: [{moment_id, run_id, t, label, occurs_at, grounding, created_at}], count}` — tellings chronological."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"No moment carries this content_hash — typed `{error: not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: tt_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Every telling of one claim BY THIS CALLER, chronological. The content hash\ncovers exactly `{label, occurs_at, participants}` (tt-core), so equal hashes\nare the same claim retold — both kept, linked by the hash itself.","tags":["tt"]}},"/v1/console":{"get":{"description":"Assemble and serve the caller's live ConsoleDoc (contract `console/0.1`, CONTRACTS §9): `{v, credits, running, results, feed, action, synths, news, schedule, pending_forecasts, record, prices}`. Humanized timestamps render in the zone named by the optional `X-User-Timezone` header (IANA name); missing/invalid degrades to UTC, labeled UTC. Validated against its own validator before serving — an invalid assembly is a typed 500, never served broken. `x-timepoint-workspace` scopes the `find_money` pre-read ONLY: named, it counts and lists the GROUNDED deal record of that workspace (every member's) that a run started there would source; every other section stays the caller's own personal record.","operationId":"get_console","parameters":[{"description":"IANA timezone name (e.g. America/Denver). Absent or unrecognized → times render UTC and say UTC.","in":"header","name":"X-User-Timezone","required":false,"schema":{"type":["string","null"]}},{"description":"Workspace uuid (W2) for the `find_money` pre-read only. Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The `console/0.1` document for the caller."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_not_found}` — a malformed, unknown or not-a-member `x-timepoint-workspace`."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_doc, errors}` (validator refusal) or `{error: db_unavailable}`."}},"security":[{"bearer":[]}],"summary":"The caller's live ConsoleDoc, validated before serving.","tags":["docs"]}},"/v1/delivered":{"get":{"description":"The runs delivered to the caller, newest first: run id, title (the short title, ≤ 60 characters: the one written at intake, else derived from the question), `question` (the whole question as asked), kind, delivered_at and the documents available (`cockpit`, `report`, `portal`, `plan`). A run is listed exactly when its review is `delivered` and the caller is its client or a member of the workspace its engagement is linked to — the same rule every run read applies, so each listed id opens with `get_run`, `get_run_cockpit` and the other run reads. Paginated by `limit` (1–100, default 20) and `offset`; `next_offset` is null on the last page.","operationId":"list_delivered_results","parameters":[{"description":"Page size, 1–100 (default 20).","in":"query","name":"limit","required":false,"schema":{"format":"int64","type":"integer"}},{"description":"Rows to skip (default 0).","in":"query","name":"offset","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{results: [DeliveredRun], limit, offset, next_offset}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"`limit` outside 1–100 or a negative `offset`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/delivered` — the runs delivered to the caller (P2c). Read-only,\nany authenticated identity, its own deliveries only.","tags":["client_services"]}},"/v1/drilldown/jobs":{"get":{"description":"The active space's drill-down jobs, newest first (at most 200), optionally filtered to one kickoff. Each item carries its frozen `max_level` and `search_depth`, its committed research `spend_usd` (`{exa, openrouter}`), its `pending_proposals` count and the honest `research_enabled`/`research_note`. Any member of the space reads.","operationId":"list_drilldown_jobs","parameters":[{"description":"Only jobs linked to this kickoff.","in":"query","name":"kickoff_id","required":false,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items, research_enabled, research_note}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_unavailable}`."}},"security":[{"bearer":[]}],"tags":["drilldown"]},"post":{"description":"Start a drill-down pass (D5, gate G11): a shallow territory pass (a scope named directly, or one of a kickoff's APPROVED scopes) or a pass rooted at an existing entity (org, role or person — its kind fixes the start level). Queued for the worker; `research_enabled` says whether anything researches it yet (false until D4's budget ledger and loop exist — see `research_note`). Administrators only (the same rule as approving research, `approvals::can_approve`): a provider or client member gets 403, an outsider or unknown kickoff/scope 404/422.","operationId":"create_drilldown_job","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response (header idempotent-replay: true) instead of queuing a second pass; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDrilldownJobReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The queued job, plus `research_enabled`/`research_note`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_forbidden | workspace_read_only}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}` — an unresolvable kickoff/parent/root."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_drilldown_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_unavailable}`."}},"security":[{"bearer":[]}],"tags":["drilldown"]}},"/v1/drilldown/jobs/{id}":{"get":{"description":"One drill-down job in the active space: the job, its research steps, and a per-level proposal count summary (`{\"L0\": {\"pending\": .., \"approved\": .., \"rejected\": .., \"superseded\": ..}, ...}`). Unknown, in another space and not yours are the same 404. Any member of the space reads.","operationId":"get_drilldown_job","parameters":[{"description":"Drill-down job id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{...job, steps, proposal_counts_by_level, research_enabled, research_note}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_unavailable}`."}},"security":[{"bearer":[]}],"tags":["drilldown"]}},"/v1/drilldown/jobs/{id}/deepen":{"post":{"description":"Deepen from an approved entity or a pending/approved proposal off job `id` (D3/D5): one root calls a single deepen, several open one child job per root up to `drilldown_depth::MAX_FANOUT` (20) — a longer list is not refused, it is truncated, and `truncated` says so. Idempotent per (parent job, root, start level): re-deepening the same root at the same start (and the same `max_level` and `profile_queries`, when named) while its job is still queued/running returns that same job rather than a duplicate. Queued for the worker; `research_enabled`/`research_note` are the same honest state as create. Administrators only, same rule as `POST /v1/drilldown/jobs`.","operationId":"deepen_drilldown_job","parameters":[{"description":"The parent drill-down job to deepen","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeepenDrilldownJobReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{jobs, truncated, errors: [{index, error}], research_enabled, research_note}`. A root that failed (wrong kind, another space, a rejected/superseded proposal) is reported in `errors` alongside whatever roots did succeed."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_forbidden | workspace_read_only}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}` — unknown parent job (a single-root deepen only; a multi-root request instead reports it per-root in `errors`)."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_drilldown_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_unavailable}`."}},"security":[{"bearer":[]}],"tags":["drilldown"]}},"/v1/drilldown/jobs/{id}/proposals":{"get":{"description":"A drill-down job's proposals in the active space, for approval (K4, PLAN policy 5): entity, edge, fact and merge proposals with `status` (pending | approved | rejected | superseded), level, a one-line `summary`, basis, sources, confidence, the `statement`, K2 lint `flags` (`[{lint, reasons}]`) and any decision already taken. Pending only unless `status=all`. Administrators only.","operationId":"list_drilldown_proposals","parameters":[{"description":"Drill-down job id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"`pending` (default) or `all`.","in":"query","name":"status","required":false,"schema":{"type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{source: drilldown_job, job_id, kickoff_id, scope_name, job_status, decidable, counts, items}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: approval_forbidden}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_decision}` — bad `status`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["approvals"]}},"/v1/drilldown/jobs/{id}/proposals/decide":{"post":{"description":"Approve or reject a batch of a drill-down job's proposals, all or nothing, in one transaction (K4, PLAN policy 5). Approve writes into the job's space with the proposed basis, never ACCUMULATED: an entity is resolved or minted (its id recorded on the proposal); an edge is stated through E2 (GROUNDED needs an https source); a GROUNDED fact becomes an assertion through W2's gate (a GENERATED fact is refused: no writer records one); a fact about the territory is recorded only. A merge is applied: its left entity is merged into its right one, which survives with the left's facts and edges (a still-pending entity proposal on one side is approved as the other side's entity instead of being minted). An edge or fact on an entity proposal needs that proposal approved first or in the same batch. Reject writes nothing to knowledge. Decisions are final: the same decision again is `unchanged`, the opposite one 409. Administrators only.","operationId":"decide_drilldown_proposals","parameters":[{"description":"Drill-down job id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProposalDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{source, job_id, decision, results: [{id, kind, outcome: applied | recorded | unchanged, entity_id?, edge_id?, reason?}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: approval_forbidden}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found | proposal_not_found, proposal_ids?}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: decision_final | proposal_superseded, proposal_ids}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: proposal_not_applicable | note_required_for_generated, reason, proposal_ids}` — nothing was applied. note_required_for_generated: approving a GENERATED entity or edge needs a note saying what was checked (not blank, not a placeholder); grounded ones need none. The note is recorded on each decided proposal."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["approvals"]}},"/v1/drilldown/jobs/{id}/proposals/decide_by_rule":{"post":{"description":"Decide a batch of a drill-down job's PENDING proposals by rule instead of by id (K4 \"smaller fixes\", PLAN policy 5): every proposal `rule` matches gets its own outcome, decided independently — one refusal never rolls back another (each proposal runs in its own SQL savepoint). `rule` filters on `kind` (entity | edge | fact | merge), `grounded_only`, `min_confidence`, `source_domains` (every source URL's host must be listed) and `no_lint_flags`, AND'd together; it must set at least one of these — an empty rule, which would match every pending proposal, is refused. At most 500 matches are decided per call; anything beyond that comes back `skipped` (re-run to continue, never silently dropped). `preview: true` runs the identical match-and-apply pass and reports it, but commits nothing; `preview: false` applies it. Approve NEVER approves a GENERATED proposal by rule — refused `generated_needs_individual_review`, since a rule note is one line for the whole batch, never the per-item note a GENERATED approval needs (decide it by id instead, `POST .../proposals/decide`) — and a merge only applies when both sides are already entities (a still-pending side needs its own approval by id first). Administrators only.","operationId":"decide_drilldown_proposals_by_rule","parameters":[{"description":"Drill-down job id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{source, job_id, decision, preview, matched, processed, results: [{id, kind, outcome: approved | rejected | skipped | refused, detail?, reason?, entity_id?, edge_id?}]}`. `matched` is every pending proposal the rule matched; `processed` is how many of those were decided (at most 500); `preview: true` writes nothing."},"400":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_decision, reason}` — an empty or malformed rule."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: approval_forbidden}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["approvals"]}},"/v1/drilldown/jobs/{id}/spend":{"get":{"description":"Research spend for a drill-down job (D4, gate G11). A PASS is a root job plus every deepen job descended from it; they share one cap per provider (Exa, OpenRouter), frozen from `run.sim_params` when the root job was created. `pass.providers` gives each provider's cap, reserved (held by a live call), spent and remaining; `pass.jobs` each job's share; `job` this job's own share; `stop_reason` why it stopped (a `budget_stopped` job names the provider). Amounts in nanodollars and USD. Unknown, in another space and not yours are the same 404. Any member of the space reads.","operationId":"get_drilldown_job_spend","parameters":[{"description":"Drill-down job id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{job_id, pass_id, status, stop_reason, budget_stopped, job, pass: {providers, jobs, cap_bounds}, research_enabled, research_note}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_unavailable}`."}},"security":[{"bearer":[]}],"tags":["drilldown"]}},"/v1/drilldown/jobs/{id}/spend/caps":{"post":{"description":"Set the research caps of job `id`'s pass (D4, gate G11): the root job and all its deepen jobs. Each value must lie within its `run.sim_params` bounds (`research_pass_cap_exa_usd`, `research_pass_cap_openrouter_usd`); the database logs every change with its actor in `run.research_cap_changes`. A raise does not restart a stopped job: deepen from it, and the child spends against the new cap. Administrators only, the same rule as starting a pass: a provider or client member gets 403, an outsider 404.","operationId":"set_drilldown_pass_caps","parameters":[{"description":"Any drill-down job of the pass","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPassCapsReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The pass's spend after the change (the `GET .../spend` shape)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_forbidden | workspace_read_only}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_research_cap, reason}`: out of bounds, not a number, or nothing to change."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: drilldown_unavailable}`."}},"security":[{"bearer":[]}],"tags":["drilldown"]}},"/v1/edges":{"post":{"description":"Create a typed, directed, scored, attributed edge between two entities the caller may write in the request's space (personal, or the workspace named by `x-timepoint-workspace`; both endpoints must be in that space). `vocabulary` is stamped by the server from the pinned TT relation; a symmetric pair is sorted into canonical order before the write. `basis` may be GROUNDED (needs >=1 https:// source) or GENERATED; ACCUMULATED is refused (hard policy 3 — that basis is written only by a run). Every other rule (relation exists, endpoint kinds allowed, attribute schema, tenure order, one live statement per scope) is enforced by the database and reported by constraint name.","operationId":"create_edge","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEdgeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The written edge row."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — an endpoint entity is unknown or not the caller's."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: unknown_relation_kind | accumulated_not_writable | invalid_basis | invalid_score | grounded_needs_source | refused, reason}`."}},"security":[{"bearer":[]}],"tags":["edges"]}},"/v1/edges/{id}/retract":{"post":{"description":"\"Delete\" an edge: write a tombstone row — `supersedes` = this one, `retracted = true` — restating the SAME relation, endpoints and attributes under the SAME basis rules as any row (a GROUNDED retraction names its own source). `{id}` must be the current, non-retracted edge and the caller's own. The list endpoint stops showing it as soon as this commits.","operationId":"retract_edge","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"The current edge id to retract","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetractEdgeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The tombstone row."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no such CURRENT edge for this caller."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: accumulated_not_writable | invalid_basis | invalid_score | grounded_needs_source | refused, reason}`."}},"security":[{"bearer":[]}],"tags":["edges"]}},"/v1/edges/{id}/supersede":{"post":{"description":"\"Update\" an edge: insert a new row with `supersedes` = this one, restating the SAME relation and endpoints under a revised claim (new basis/score/sources/attributes). `{id}` must be the current (head, non-retracted) edge and the caller's own; superseding an already-superseded or retracted row is refused typed. Same basis rule as create: GROUNDED or GENERATED, never ACCUMULATED.","operationId":"supersede_edge","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"The current edge id to supersede","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupersedeEdgeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The new head edge row."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no such CURRENT edge for this caller."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: accumulated_not_writable | invalid_basis | invalid_score | grounded_needs_source | refused, reason}`."}},"security":[{"bearer":[]}],"tags":["edges"]}},"/v1/entities":{"post":{"description":"Resolve-or-mint a registry entity by `{kind, display_name}` (kind ∈ person|org|market|role; name trimmed, nonempty, ≤200 chars). Resolution is name-aware and idempotent — POSTing the same (kind, name) again returns the SAME entity_id, never a duplicate row. Returns `{entity_id, kind, slug, path, display_name}`. Awareness is a separate write: PUT /v1/entities/{id}/awareness.","operationId":"create_entity","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEntityReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The resolved-or-minted entity: `{entity_id, kind, slug, path, display_name}` (200 whether resolved or freshly minted — the operation is an idempotent upsert)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_kind | invalid_display_name | no_mintable_slug | entity_language_rejected, reason(s)}` — the last is K2's entity-language lint (PLAN policy 2)."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: entity_registry_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Resolve-or-mint an entity by (kind, display_name) — the TOOLS gallery's way\ninto the registry. Thin by design: `entities::resolve_or_mint` is the ONE\nminter (this module's header contract); `frame_id` rides as `\"\"` so resolution\nis purely name-aware (slugify(display_name)) — a frame id here would make the\nname-blind step-1 lookup resolve the wrong entity. Idempotent: the same\n(kind, name) POSTed again returns the same `entity_id`. Awareness is NOT\nwritten here — `put_awareness` stays the one writer of `entity.awareness`.","tags":["entities"]}},"/v1/entities/by-path/{kind}/{slug}":{"get":{"operationId":"get_entity_by_path","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"person | org | market","in":"path","name":"kind","required":true,"schema":{"type":"string"}},{"description":"Registry slug (kebab-case)","in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The entity dossier (same shape as by-id)"},"404":{"description":"Unknown path (typed not_found)"}},"security":[{"bearer":[]}],"summary":"The dossier by registry path `/kind/slug`. A kind outside person|org|market|role can\nhold no row (schema CHECK), so the lookup itself yields the typed 404 — no\nseparate validation branch to drift.","tags":["entities"]}},"/v1/entities/search":{"get":{"operationId":"search_entities","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Name or slug fragment, at most 160 characters","in":"query","name":"q","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"`{items:[{entity_id,id,name,kind,path,facts[]}]}` — at most 12 hits; facts are this owner's GROUNDED/ACCUMULATED assertions (≤24 each)."},"422":{"description":"`{error: invalid_query}` when `q` exceeds 160 characters."}},"security":[{"bearer":[]}],"summary":"Owner-scoped name/slug search for the cast picker. Write-scoped (the\ncaller's own records only) so a grant cannot put someone else's dossier\ninto a simulation that will accrue assertions. Empty `q` returns no hits\nrather than listing the registry.","tags":["entities"]}},"/v1/entities/{id}":{"get":{"operationId":"get_entity","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Stable registry entity id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The entity dossier: {entity, awareness: {enabled, level|null}, assertions[], participation, runs_citing[], edges[]} — an absent awareness row serves the labeled default {enabled: false, level: null}. `edges` (Wave CR node E2) lists CURRENT edges touching this entity from either side, direction-aware (see entities::list_current_edges)."},"404":{"description":"Unknown entity id (typed not_found)"}},"security":[{"bearer":[]}],"summary":"The dossier by stable registry id. Typed 404 for an unknown id.","tags":["entities"]}},"/v1/entities/{id}/assertions":{"post":{"description":"Write ONE assertion to an entity's dossier on a person's authority: `{attribute, value, note?, source_url?}`. The row rides `entities::commit_assertions` — the SAME six-rule gate the simulation path uses (kebab attribute, bounded value, confidence in (0,1], the entity must exist, duplicates skipped) — so the dossier never writes around the gate. Provenance is honest and explicit: basis GROUNDED, source `console_manual` when the person typed it and `console_link <url>` when they approved it from a link they pasted, with the note appended after ` :: `; `run_id` is NULL because no run authored it. A gate skip (typically an identical assertion already on the dossier) is NOT an error: it answers 200 `{written: false, reason}` and says so.","operationId":"add_entity_assertion","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Stable registry entity id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsoleAssertionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{written, reason?, basis, source, attribute, value, language_flags}` — `written: false` with a plain reason when the gate skipped it; `language_flags` (K2's entity-language lint, PLAN policy 2) is `[]` when clean."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown entity."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_attribute | invalid_value | invalid_note | invalid_url | entity_language_rejected, reason(s)}`."}},"security":[{"bearer":[]}],"summary":"Add ONE assertion to a dossier, as a person. Goes through\n`entities::commit_assertions` — the same gate a simulation goes through,\nnever around it.","tags":["entities"]}},"/v1/entities/{id}/awareness":{"put":{"description":"Set the per-entity awareness switch (Media M1 + G1 research): `{enabled, level?, research?}` where level (1 daily · 2 six-hourly · 3 hourly) is required when enabling. `research` is TRI-STATE: omit the field to leave the stored research_source unchanged (a level-only PUT never wipes a live source); send explicit `null` to clear it; send a string to set it — closed set, currently only `\"openrouter_web\"` (typed 422 `invalid_research_source` otherwise). Upserts `entity.awareness` recording `updated_by` = the caller; disable keeps the row, its last level AND its research_source (the sweep gates on enabled); disabling a never-enabled entity is an idempotent no-op (a level alone never mints a row). Returns `{entity_id, awareness: {enabled, level|null, research|null}, updated_at, updated_by}`. The monitor sweep (B6.1) fires enabled+sourced rows on cadence at 1 credit per fired look, billed on use to `updated_by`; insufficient balance auto-clears the source. Monthly billing arrives with subscriptions (M3) — the `awareness_l1..3` price rows are served via /v1/prices meanwhile.","operationId":"put_awareness","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Stable registry entity id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AwarenessReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The new state: `{entity_id, awareness: {enabled, level|null, research|null}, updated_at, updated_by}` (nulls on the no-row disable no-op — labeled absence)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown entity id (typed not_found)."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: level_required | invalid_level | invalid_research_source, reason}`."}},"security":[{"bearer":[]}],"summary":"Flip the awareness switch for an entity: validate → 404 unknown entity →\nupsert with `updated_by` = the authenticated caller → return the new state.","tags":["entities"]}},"/v1/entities/{id}/display-name":{"put":{"description":"Correct an entity's display name (trimmed, nonempty, ≤200 chars). The SLUG and therefore the registry path are deliberately untouched — ids and paths are stable forever and every link already written to this dossier keeps resolving; only the name and the `content_hash` that names the row change. Returns `{entity_id, display_name, path, content_hash, path_changed: false, language_flags}` (K2's entity-language lint; `language_flags` is `[]` when clean).","operationId":"put_entity_display_name","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Stable registry entity id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisplayNameReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{entity_id, display_name, path, content_hash, path_changed, language_flags}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown entity."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_value, reason}` — empty or oversize name. Or `{error: entity_language_rejected, reasons}` — K2's entity-language lint (PLAN policy 2)."}},"security":[{"bearer":[]}],"summary":"Correct an entity's display name in place.","tags":["entities"]}},"/v1/entities/{id}/edges":{"get":{"description":"Current (live, unsuperseded, non-retracted) edges touching this entity from either side, direction-aware: `label` reads correctly whichever side the entity is on (outgoing uses the relation's own label, incoming uses its `inverse_label`, symmetric reads the same either way), and every row carries `nature` so a GENERATED `knows` edge never reads as part of a structural path. `?structural_only=true` filters to `nature = structural` only. Scoped to the request's space (`x-timepoint-workspace`; absent = personal).","operationId":"list_entity_edges","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Stable registry entity id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Filter to nature = structural","in":"query","name":"structural_only","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{id, relation_kind, label, direction, nature, other_entity_id, attributes, basis, score, source_urls, observed_at, valid_from_earliest, valid_to_latest, run_id, created_at}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — the entity is unknown or not readable in this space; `{error: workspace_not_found}` for a workspace header the caller is not a member of."}},"security":[{"bearer":[]}],"tags":["edges"]}},"/v1/entities/{id}/import-preview":{"post":{"description":"Read one pasted link and PROPOSE candidate assertions about this entity. This endpoint WRITES NOTHING — it is a proposal a person approves one by one via POST /v1/entities/{id}/assertions, which is the only path to the record. The fetch is real (tp-llm's ChainFetcher: plain HTTP first, firecrawl only when the site walls it) and every failure is typed by name: 422 invalid_url | unsupported_url | nothing_to_read, 402 llm_no_headroom, 502 fetch_failed | llm_failed, 503 reading_unavailable. Each candidate must carry a quote that appears VERBATIM in the fetched page; ones that do not are dropped in code and counted in `dropped`, never repaired. Returns `{source: {url, final_url, title, fetched_at, chars, truncated}, candidates: [{attribute, value, quote}], dropped: [reason], cost_usd}`.","operationId":"entity_import_preview","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found; a client member writing answers 403 workspace_read_only.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"Stable registry entity id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPreviewReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The proposal. Nothing was written."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_no_headroom, gate}` — nothing spent."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown entity."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_url | unsupported_url | nothing_to_read, reason}`."},"502":{"content":{"application/json":{"schema":{}}},"description":"`{error: fetch_failed | llm_failed, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: reading_unavailable, reason}`."}},"security":[{"bearer":[]}],"summary":"Read a link and PROPOSE assertions about this entity. Writes nothing.","tags":["entities"]}},"/v1/export/entities":{"get":{"operationId":"export_entities","parameters":[{"description":"`json` (default) or `csv`. CSV needs `part`.","in":"query","name":"format","required":false,"schema":{"type":["string","null"]}},{"description":"One of accounts, contacts, roles, relationships, facts. Absent = all (JSON only).","in":"query","name":"part","required":false,"schema":{"type":["string","null"]}},{"description":"Include GENERATED facts (default false).","in":"query","name":"include_generated","required":false,"schema":{"type":["boolean","null"]}},{"description":"Delta export: an RFC 3339 instant, or `checkpoint` for the workspace's\nlast recorded export. Only records created or touched since, plus a\n`removed` part (relationships retracted or replaced since).","in":"query","name":"since","required":false,"schema":{"type":["string","null"]}},{"description":"`all` (default): every research pass. `in`: leave out the passes a\nperson ruled out of scope for any engagement linked to this workspace\n(and their deepen children), the facts approved from them, the edges\nthey sourced and the entities they minted that nothing else supports.","in":"query","name":"engagement_scope","required":false,"schema":{"type":["string","null"]}},{"description":"`provider` (default): the full record. `client`: what the client's own\nread-delivered surfaces and wrap-up show — relationships follow the\nfacts rule, each fact's `source` is its first URL only, and\n`research_jobs` is left out.","in":"query","name":"view","required":false,"schema":{"type":["string","null"]}},{"description":"The workspace to export (required). A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"JSON `{manifest, accounts[], contacts[], roles[], relationships[], facts[], entities[], research_jobs[]}` (or only `part`), or `text/csv` for one `part`. Contacts are flattened through each person's current `holds-office` → `office-of`; facts are GROUNDED plus simulation facts from runs delivered into this workspace. `entities` is every current record of every kind (`{entity_id, kind, slug, display_name, path, duplicate_of}`); `research_jobs` the workspace's drill-down passes. With `engagement_scope=in`, passes ruled out of scope for an engagement linked to this workspace (and their deepen children) are left out of `research_jobs`, with the facts approved from them, the edges they stated (matched by relation, from and to; by relation and shared source URL only when an endpoint no longer resolves) and the entities they minted during the pass that no remaining fact or edge supports — so the contacts, roles, accounts and relationships built from those leave too. A record that pre-dates the pass, or that an in-scope pass also approved, is never withheld; `manifest.engagement_scope` is `{applied, excluded_jobs, facts_withheld, entities_withheld, edges_withheld}`. With `view=client`, the document is the client's own view: relationships follow the facts rule, each fact's `source` is its first URL, and `research_jobs` is absent. With `since`, only what was created or touched after it, plus `removed[]`; `manifest.as_of` is where the next delta starts."},"403":{"description":"`{error: export_forbidden}` — a client (or non-writer) member."},"422":{"description":"`{error: export_needs_workspace | invalid_export_request}`."}},"security":[{"bearer":[]}],"summary":"The CRM export of this workspace's modeled entities.","tags":["entities"]}},"/v1/files":{"get":{"description":"The documents delivered to the caller (briefs, playbooks, target lists, CRM exports…): file id, folder, name, title, content type, size, sha256 and delivery time. Listed once an admin has delivered it, to the engagement's client or a member of the workspace the engagement is linked to. Read one with `get_file`. A film (`storage: \"object\"`) names its `poster_file_id` and `captions_file_id` (not listed on their own) and its `duration_seconds`; open any of them with `stream_file`. A report deck (a PowerPoint file in `Report decks`) carries `deck: {pages, length, generated_at}`; download it with `get_file_link`.","operationId":"list_my_files","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{files: [{file_id, engagement_id, folder, name, title, content_type, size_bytes, sha256, delivered_at, storage, duration_seconds, poster_file_id, captions_file_id, poster_for, captions_for}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"`GET /v1/files` — the files delivered to the caller.","tags":["client_services"]}},"/v1/files/{id}":{"get":{"description":"One delivered document: its metadata and, for a text format (markdown, CSV, JSON, plain text), its full `text` (up to 2 MB). PDFs return embedded text when extractable with `text_source=extracted`; this is never OCR and failure is non-fatal. Stored media (a film, poster or captions file) carries no text either; open it with `stream_file`. A report deck (PowerPoint) carries no text; download it with `get_file_link`. Unknown and not-yours are the same 404.","operationId":"get_file","parameters":[{"description":"file id (from `list_my_files`)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{file, text}` (`text` null for a PDF or an oversized file)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, not delivered, withdrawn, or not yours — `{error: not_found}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/files/{id}` — one delivered file, with its text.","tags":["client_services"]}},"/v1/files/{id}/link":{"get":{"operationId":"get_file_link","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"{file_id,name,content_type,url,expires_at}. A five-minute download capability. Every redemption rechecks delivery, live identity and the issuing MCP key, if any. Private object downloads redirect to a short-lived private storage URL."},"404":{"description":"Unknown, undelivered, withdrawn or not yours."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/files/{id}/stream":{"get":{"description":"Watch or open a film, poster or captions file delivered to the caller: a 302 to a presigned URL valid for 5 minutes, or with `format=json` the same URL as `{url, expires_at}`. Never cached. A poster or captions file opens only while its film is delivered. Unknown, not delivered, withdrawn and not-yours are the same 404.","operationId":"stream_file","parameters":[{"description":"File id (from list_my_files)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"`json` answers `{url, expires_at}` with 200 instead of a 302 (for a page\nthat authenticates with a bearer header, which `<video src>` cannot send).","in":"query","name":"format","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"With `format=json`: `{url, expires_at}`."},"302":{"description":"Location: the presigned URL."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: storage_unconfigured}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/forecasts/{id}/attest":{"post":{"description":"Attest what actually happened against one of the caller's UNRESOLVED forecasts (the YESTERDAY feedback loop). Takes a structured `outcome` (`confirmed` | `closed_differently` | `still_open`), a free-text `note`, or both. `confirmed`/`closed_differently`/note-only mint a `user_attested` GROUNDED moment on the forecast's run — participants = the predicate's `participants_any` registry paths, label `USER ATTESTED: {composed}` (structured outcomes compose `confirmed as forecast — {claim}` / `closed differently than forecast — {claim}`, with the note as ` · {note}`), provenance carries `forecast_id` + `attest_outcome` — then attempt a NORMAL classification (non-fatal; a closed LLM gate leaves the moment honestly untagged). `still_open` mints NOTHING: `run.forecasts` has no acknowledgement column, so the response is a typed no-op (`written: false`) stating that nothing was written. The forecast is NEVER resolved inline: the hourly sweep owns every verdict, and it judges only after the resolution date has fully elapsed.","operationId":"attest_forecast","parameters":[{"description":"Forecast id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttestForecastReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Minted: `{moment_id, forecast_id, outcome, resolves, classification}` — the moment landed; the sweep judges on the stated date. classification ∈ tagged | abstained | untagged | `unavailable: …`. `still_open`: `{forecast_id, outcome, moment_id: null, written: false, reason, resolves}` — an explicit no-observation acknowledgement; nothing was written."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown id, or not the caller's forecast (a stranger learns nothing)."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_resolved, outcome}` — the sweep already judged; verdicts never reopen."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_note | invalid_outcome | no_participants}`."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable | attest_unmintable}`."}},"security":[{"bearer":[]}],"summary":"Tell Timepoint what happened: mint the evidence, never the verdict.","tags":["register"]}},"/v1/forecasts/{id}/matchable":{"post":{"description":"Retrofit a matchable Lens-A event node onto one of the caller's UNRESOLVED forecasts whose predicate has `lens_a: null` (the quicksim-minted shape that can NEVER score a HIT). Classifies the stored claim through the TT classifier (one NORMAL protocol pass — real model, reject-never-repair); when a node lands at mass ≥ 0.35 (directly under lens_a, or via the Lens-B shadow bridge), writes `predicate ‖ {lens_a, min_mass: 0.35, lens_a_basis, matchable_at}` and mirrors the node into `taxonomy_id`. When the classifier abstains, rejects twice, lands only sub-threshold mass, or the LLM gate is closed, NOTHING is written and the refusal is typed with the honest reason. Idempotent on an already-matchable forecast: 200 with the standing node, `already_matchable: true`, no classifier call, no write. Never touches a resolved forecast.","operationId":"make_forecast_matchable","parameters":[{"description":"Forecast id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{forecast_id, lens_a, can_hit: true, basis}` — the node landed and the predicate now names it (`already_matchable: true` rides instead of `basis` when it already did)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown id, or not the caller's forecast (a stranger learns nothing)."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_resolved, outcome}` — the sweep already judged; verdicts never reopen."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: classifier_abstained, reason}` — no node landed (abstention, double rejection, sub-threshold mass, or a closed LLM gate — the reason states which; nothing written). `{error: no_participants}` — a participant-less predicate could never resolve, so a node would be a false capability."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Make a HIT possible: classify the stored claim and anchor the predicate.","tags":["register"]}},"/v1/invites":{"get":{"description":"The caller's own invites, newest first: `{invites: [{id, trial_cr, sponsor_cap_cr, created_at, expires_at, redeemed_at, agent_label, revoked_at}…]}`. The codes are NOT returned and cannot be — only their sha256 was ever stored. `redeemed_at`/`revoked_at` are null when the state has not happened: absence is labeled, never invented.","operationId":"list_invites","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{invites: […]}` — possibly empty."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: invites_unavailable}`."}},"security":[{"bearer":[]}],"summary":"The caller's invites. Codes are NOT here — only their sha256 was ever stored.","tags":["invites"]},"post":{"description":"Mint an agent invite code. The plaintext `tpi_…` code is returned EXACTLY ONCE (only its sha256 is stored) together with a copy-pasteable `snippet` your agent can follow to connect. `trial_cr` (0..=100, default 25) is the free grant the redeeming agent receives; `sponsor_cap_cr` (default 0) is a CEILING on credits moved from your balance at redemption — nothing is held now, and if your balance is short at redeem time the transfer is what exists and the redemption response says so. Agent-scoped tokens are refused with 403 `agents_cannot_invite`: agents do not recruit agents.","operationId":"create_invite","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInviteReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{invite_id, code, expires_at, trial_cr, sponsor_cap_cr, snippet}` — `code` is shown here and never again."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: agents_cannot_invite}` — the caller's token carries the `agent` capability."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: bad_trial_cr | bad_sponsor_cap_cr | bad_ttl, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: invites_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Mint an agent invite. The code appears exactly once, in this response.","tags":["invites"]}},"/v1/invites/{id}/revoke":{"post":{"description":"Revoke one of your invites — creator only. BEFORE redemption the code stops resolving. AFTER redemption this also kills the agent: its registry row is deleted (the next call it makes answers 401) and its remaining AVAILABLE balance is transferred back to you as a paired `transfer` entry. Credits already HELD against an in-flight run are not yanked — those runs settle or release normally, and the response says so in `held_cr_still_running`. An unknown invite and somebody else's invite are the same 404 — a refusal never confirms existence.","operationId":"revoke_invite","parameters":[{"description":"invite id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{revoked, was_redeemed, agent_owner, returned_cr, held_cr_still_running, note}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown invite, not yours, or already revoked."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: invites_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Revoke an invite. Before redemption: the code dies. After: the agent dies.","tags":["invites"]}},"/v1/kickoffs":{"get":{"description":"The active space's kickoffs, newest first (at most 100): `{items: [{id, workspace_id, title, question, status, compiled_run_id, created_by, created_at, updated_at}]}`. Any member reads, a client member included. A kickoff a retraction withdrew is left out unless `include_withdrawn=true` (never for a client member).","operationId":"list_kickoffs","parameters":[{"description":"Also list kickoffs a retraction withdrew, as tombstones (no question,\ntitle \"Withdrawn kickoff\"). Never for a client member. Default false.","in":"query","name":"include_withdrawn","required":false,"schema":{"type":"boolean"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]},"post":{"description":"Start a kickoff (status `draft`) in the active space: the `x-timepoint-workspace` workspace, or the caller's personal space without the header. Provider and admin members write; a client member is refused. Optional `extracted_entities` / `proposed_edges` / `scopes` hand-author proposals exactly as `POST /v1/kickoffs/{id}/proposals` does (administrators only, same checks; the kickoff then starts `extracted`); without them nothing changes.","operationId":"create_kickoff","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response (header idempotent-replay: true) instead of re-running; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKickoffReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The kickoff document (`kickoff.schema.json`)."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only | kickoff_author_forbidden}` — a client member, or proposals from a non-administrator."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_not_found}` — not a member of the named workspace."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: contact_detail_refused | entity_language_rejected}` — proposals refused as a batch."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}":{"get":{"description":"One kickoff document (`kickoff.schema.json`) in the active space. Unknown, in another space and not yours are the same 404.","operationId":"get_kickoff","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The kickoff document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"410":{"content":{"application/json":{"schema":{}}},"description":"Withdrawn by a retraction: `{error: withdrawn, kickoff_id, status, created_at, created_by, withdrawn_at, withdrawn_by}`, never content."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/align":{"post":{"description":"Light alignment (K5, gate G12; D2b's fuzzy dedupe is a separate, later node): `extracted` -> `aligned`. Sets `entity_id` on every `extracted_entities` proposal that matches an existing entity in this kickoff's own space by EXACT kind + slug; no match leaves it null (a new-entity candidate, for K4's approval to mint). Provider or admin.","operationId":"align_kickoff","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The kickoff document, `status: aligned`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_wrong_status, status, needed}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/compile":{"post":{"description":"Compile an `approved` kickoff into the Boss's deep_sim request and admit it through `POST /v1/runs`' own path, in the kickoff's space: cast = the approved, aligned entities (knowledge in this space) with up to 3 GROUNDED facts each, relationships = the current edges among the cast, query = the question plus approved scopes. `sim_params`, `models` and `cast_approval_id` follow `POST /v1/runs` exactly; a cast above the default cap asks for a cap equal to the cast (admin only; above 20 with an approval). Nothing is confirmed or reserved: the quote is indicative (a roster the size of the cast); the binding quote is the run's confirm_plan card. In the same transaction the kickoff moves `approved` → `compiled` → `planned` and records `compiled_run_id`; a refusal leaves it `approved`. Idempotent: a `planned` kickoff answers 200 with its existing run and `replayed: true`, and the replay ignores the request body (`parent_run_id` included). Send `{}` for no overrides. Lineage: `parent_run_id` names the run this one follows from; it must be a run that is not withdrawn, in the same space as the kickoff (a workspace kickoff: a run of that workspace; a personal kickoff: your own personal run), else `422 invalid_parent_run` (unknown, another space's and withdrawn ids are one answer). It is frozen as the run's `parent_run_id` (`run.parent_run_id` in the answer) and recorded on its `run.accepted` event as `lineage: {parent_run_id, relation: \"child\"}`. A kickoff freezes its workspace at creation: with no `x-timepoint-workspace` header this compiles in the kickoff's OWN workspace (you must still be a member); a header naming a DIFFERENT workspace than the kickoff's own is refused, `409 workspace_mismatch`, never silently `not_found`. A personal kickoff is unaffected — send no header.","operationId":"compile_kickoff","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the kickoff's own workspace (personal, if it has none) — you must still be a member of it. Naming a workspace other than the kickoff's own is refused (409 workspace_mismatch). A malformed id, an unknown workspace and one you are not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"M4: a caller-chosen key (≤200 chars), on top of this operation's own `replayed: true` idempotency. The same caller + key + method/path/body within 24h replays the exact first response (header idempotent-replay: true) instead of re-running the compile; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompileReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Replay of an already planned kickoff (`replayed: true`; the body is ignored)."},"201":{"content":{"application/json":{"schema":{}}},"description":"`{kickoff_id, status: planned, run_id, run: {run_id, kind, state, workspace_id, created_by, query, parent_run_id}, run_controls, quote, replayed: false}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_no_headroom}` — the balance gate is closed."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only | forbidden | cast_approval_required | cast_approval_invalid, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_not_approved | workspace_mismatch, reason, status?}` — `workspace_mismatch`: the header named a workspace other than the kickoff's own."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_missing_question | kickoff_malformed | sim_shape_ref_unresolvable | entity_not_aligned | entity_not_in_space | empty_cast | invalid_query, reason, proposal_ids?}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable | runs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/extract":{"post":{"description":"Extract candidate entities/edges/scopes from a kickoff's own notes (K5, gate G12): `draft`/`extracted` -> `extracted`. Renders a prompt over the kickoff's question and notes, calls the shallow research model with a bounded token limit and a pre-call cost ceiling against a kickoff-scoped research budget (migration 0072; hard stop, same ledger as drill-down passes), maps the result into `extracted_entities`/`proposed_edges`/ `scopes` proposals. Nothing is written to entity.*. Refuses a contact detail in any field (PLAN.md policy 1) and an entity-language `Reject` (K2) as a whole-batch typed error; a `Flag` is recorded on the kickoff's `decisions`. Off by default (`TP_KICKOFF_EXTRACT_ENABLED=1` required) and administrators only.","operationId":"extract_kickoff","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The kickoff document, `status: extracted`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_research_budget_exhausted, reason}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only | kickoff_extract_forbidden | kickoff_extract_disabled}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_wrong_status, status, needed}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_missing_question | contact_detail_refused | entity_language_rejected | kickoff_malformed}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: research_model_unavailable | kickoff_budget_unavailable | extraction_failed | kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/independent-reviews":{"get":{"description":"A kickoff's independent reviews, oldest first, with a summary: `{subject: {kind, id}, workspace_id, summary: {count, stages: {pre_spend}}, items}`; the stage carries `{count, approve, reject, latest: {id, verdict, created_at} | null}`. The kickoff must be visible in the active space. Provider and admin members read; a client member is refused.","operationId":"list_kickoff_independent_reviews","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{subject, workspace_id, summary, items}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: reviews_provider_only}`: a client member."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"410":{"content":{"application/json":{"schema":{}}},"description":"Withdrawn by a retraction: the kickoff's tombstone, never its reviews."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: independent_reviews_unavailable}`."}},"security":[{"bearer":[]}],"tags":["reviews"]},"post":{"description":"Record an independent pre-spend review of a kickoff (stage `pre_spend`), by the caller's own login (never named in the body) and its sign-in method (`token`, `magic_link` or `google`; an MCP call is `token`). The reviewer must not be the kickoff's proposer (its `created_by`). A cancelled kickoff takes no review. A personal kickoff is visible to its owner only, so every review of one is refused as `own_proposal`: independent review is a workspace feature. Name the kickoff's workspace in `x-timepoint-workspace`; a provider or admin member writes, a client member is refused. Reviews are append-only.","operationId":"record_kickoff_independent_review","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordReviewReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The stored review: `{id, subject_kind: kickoff, kickoff_id, run_id: null, workspace_id, stage: pre_spend, verdict, reasons, subject_sha256, reviewer, reviewer_via, proposer, created_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only}` (a client member) or `{error: own_proposal, reason}` (the reviewer proposed this kickoff). Nothing is written."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`: unknown, in another space and not yours are one answer."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_cancelled}`."},"410":{"content":{"application/json":{"schema":{}}},"description":"Withdrawn by a retraction: the kickoff's tombstone, as `GET /v1/kickoffs/{id}` serves it."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_independent_review | stage_not_for_subject, reason}`: unknown stage or verdict, 0 or more than 20 reasons, an empty or over-long reason, a malformed `subject_sha256`, an unknown field, or stage plan or terminal on a kickoff."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: independent_reviews_unavailable}`."}},"security":[{"bearer":[]}],"tags":["reviews"]}},"/v1/kickoffs/{id}/proposals":{"get":{"description":"A kickoff's research proposals in the active space, for approval (K4, PLAN policy 5): its extracted entities, proposed edges and scopes, each with `status` (pending | approved | rejected), a one-line `summary`, basis, sources, confidence, the `statement` itself and K2 lint `flags` (always `[]` for kickoff proposals), plus `counts` and the drill-down jobs serving it with their pending counts. Pending only unless `status=all`. Administrators only.","operationId":"list_kickoff_proposals","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"`pending` (default) or `all`.","in":"query","name":"status","required":false,"schema":{"type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{source: kickoff, kickoff_id, title, status, decidable, counts, items, drilldown_jobs}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: approval_forbidden}` — not an administrator."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_decision}` — bad `status`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["approvals"]},"post":{"description":"Hand-author kickoff proposals (K5 follow-up for X1): append `extracted_entities` / `proposed_edges` / `scopes` in K1's item types, marked admin-authored on `decisions`. Administrators only (`approvals::can_approve`). Allowed while the kickoff is `draft`, `extracted`, `aligned` or `researching`; a `draft` becomes `extracted`. 1 to 200 items; ids new to the kickoff; entity kind person/org/role only; GROUNDED needs an https source; edges use the E1 vocabulary between entity ids in this request or the kickoff. A contact detail or an entity-language `Reject` refuses the whole batch; a `Flag` is recorded. No spend, no model call, nothing written to entity.* (approval, K4, is the only write).","operationId":"author_kickoff_proposals","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthoredProposals"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The kickoff document with the appended proposals."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_author_forbidden}` — not an administrator."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_wrong_status, status, needed}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: contact_detail_refused | entity_language_rejected | kickoff_malformed}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/proposals/decide":{"post":{"description":"Approve or reject a batch of a kickoff's proposals, all or nothing, in one transaction (K4, PLAN policy 5). Approve writes into the kickoff's space: an entity is resolved or minted and its id written back to `extracted_entities[].entity_id` (so K3 can compile); an edge is stated through E2 with its proposed basis (GROUNDED needs an https source); a scope is recorded only. Reject writes nothing to knowledge. The decision is recorded in the kickoff's `approvals` / `decisions`; when nothing is pending and something is approved the kickoff moves to `approved`. Decisions are final: the same decision again is `unchanged`, the opposite one 409. The kickoff must be `awaiting_approval`. Administrators only.","operationId":"decide_kickoff_proposals","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProposalDecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{source, kickoff_id, decision, status, counts, results: [{id, kind, outcome: applied | recorded | unchanged, entity_id?, edge_id?, reason?}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: approval_forbidden}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found | proposal_not_found, proposal_ids?}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_not_awaiting_approval | decision_final, status?, proposal_ids?}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: proposal_not_applicable | kickoff_malformed | note_required_for_generated, reason, proposal_ids?}` — nothing was applied. note_required_for_generated: approving a GENERATED entity or edge needs a note saying what was checked (not blank, not a placeholder); grounded ones need none."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: approvals_unavailable}`."}},"security":[{"bearer":[]}],"tags":["approvals"]}},"/v1/kickoffs/{id}/recompile":{"post":{"description":"RX5: compile a `planned` kickoff again after its run `failed`. `compile` replays a planned kickoff's run whatever its state; this returns the kickoff to `approved` (its `decisions` record the failed run's id) and admits a fresh run exactly as `compile` does. Only while the recorded run is `failed`: a `created` or `running` run is refused, and a `complete` one keeps replaying through `compile`. The research approver only (`approvals::can_approve`). Body as `compile`; send `{}` for no overrides.","operationId":"recompile_kickoff","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompileReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"A concurrent recompile already admitted a run (`replayed: true`)."},"201":{"content":{"application/json":{"schema":{}}},"description":"`{kickoff_id, status: planned, run_id, run, run_controls, quote, replayed: false}` — the fresh run."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: recompile_forbidden | workspace_read_only | …}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_not_recompilable, status, run_state}` — not planned, or its run is not `failed`."},"422":{"content":{"application/json":{"schema":{}}},"description":"As `compile`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable | runs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/spend":{"get":{"description":"Research spend of a kickoff's extraction pass (K5, gate G12; migration 0072, the same `run.research_spend` ledger D4 built for drill-down passes): cap, committed, remaining, OpenRouter only. `null` `pass` when extraction was never called. Any member of the space reads (D5's own rule for a job's spend view).","operationId":"get_kickoff_spend","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{kickoff_id, pass: {..} | null}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/kickoffs/{id}/submit":{"post":{"description":"Freeze the proposal set for review (K5, gate G12; K4's gap): `extracted`/`aligned`/`researching` -> `awaiting_approval`. No more automatic research writes to this kickoff after this call. Provider or admin.","operationId":"submit_kickoff","parameters":[{"description":"Kickoff id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The kickoff document, `status: awaiting_approval`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoff_wrong_status, status, needed}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["kickoffs"]}},"/v1/ladders":{"post":{"operationId":"launch_ladder","parameters":[{"in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Launch a zero-hold parent with frozen configuration and bounded child spend authority"},"422":{"description":"Invalid configuration, unsupported rung or ungrounded base"}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/ladders/{id}":{"get":{"operationId":"get_ladder","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Search state, candidates, spend authority and durable ledger"},"404":{"description":"Ladder not found"}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/ladders/{id}/spend/{decision_id}":{"post":{"operationId":"decide_ladder_spend","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"decision_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Authorize one child's explicit USD ceiling, or stop the search, with the configured approver class"},"403":{"description":"Person sign-in required"},"409":{"description":"Decision already answered or search cap reached"}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/ladders/{id}/stop":{"post":{"operationId":"stop_ladder","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Durably stop new admissions and plan consents; already dispatched calls may settle"},"404":{"description":"Ladder not found"}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/me/assets":{"get":{"description":"Everything delivered to the caller, in one document: delivered simulation results (with a reader's title and the documents each has: `cockpit`, `report`, `portal`, `plan`), delivered engagement wrap-up reports, delivered documents (`list_my_files`/`get_file`), the engagements whose record the caller may read (`engagements`, each opened with `get_engagement_record`), the workspaces the caller belongs to with the number of shared research records in each, and how to connect an AI assistant (the MCP URL and the caller's own access keys, never their secrets). Each part follows the same read rule as its own tool: open a result with `get_run_report`, a wrap-up with `get_wrapup_report`, research with `search_entities`.","operationId":"list_my_assets","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{identity, client_name, results[], wrapups[], files[], engagements[], workspaces[], assistant_access}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"`GET /v1/me/assets` — everything delivered to the caller.","tags":["client_services"]}},"/v1/me/engagements/{id}/grant-book":{"get":{"description":"The grant book of one engagement the caller is the client of (or a member of the workspace it is linked to; the record's rule): one row per grant its searches found, with its decision, how far it went and the next step (`status_word`: found, not eligible, set aside, read: likely win, read: likely loss, simulating, simulated: won, simulated: lost). No internal id but the `run_id` of a delivered run: `grant_id` is a key made from the funder and program, `also_found_in` and `searches` are empty. A quick read, and a simulation's result, are shown once its run was delivered, or while the engagement's switch shows unfinished work (`unfinished_shown` on the record, on by default); with the switch off a simulation not delivered is left out and only grants with a delivered read or simulation are listed. As CSV: `/v1/me/engagements/{id}/crm-export?part=grants`. Unknown and not-yours are the same 404.","operationId":"get_my_engagement_grant_book","parameters":[{"description":"Engagement id (from `list_my_assets`, `engagements`)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, question, counts: {searches, found, eligible, approved, quick_reads, simulated, won, lost}, rows: [{grant_id, funder, program, title, website, award_min_usd, award_max_usd, deadline, found_at_level, applicant_path, eligibility, eligibility_quote, decision, quick_read: {outcome, award_p, top_outcome, run_id} | null, simulation: {run_id, status, top_outcome, estimate, result_word, the_move, delivered_at} | null, next_step, status_word, searches, also_found_in, times_found, first_found_at}]}`"},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: unknown, or not the caller's."}},"security":[{"bearer":[]}],"summary":"`GET /v1/me/engagements/{id}/grant-book` — the client's grant book.","tags":["client_services"]}},"/v1/me/engagements/{id}/record":{"get":{"description":"Everything from one engagement the caller is the client of (or a member of the workspace it is linked to), in one document: every run attached to it or created in its workspace for it, newest first (a delivered one with its `run_id` and the readings it has, `docs`; one that was not delivered with no id, only its title, dates, `status` (did_not_finish, in_progress or not_delivered), a coarse `reason` category and a plain `note`; never the recorded error text); a grant search's counts (`grants: {found, eligible, approved, greyed}`); the delivered wrap-up; the delivered files, films and report decks (as `list_my_files` lists them); and, when the engagement is linked to a workspace the caller belongs to, the CRM export's parts in the client view with their row counts (`crm_export`, made when downloaded). Runs not delivered are listed only while the engagement's switch says so (`unfinished_shown`, on by default). Unknown and not-yours are the same 404.","operationId":"get_engagement_record","parameters":[{"description":"Engagement id (from `list_my_assets`, `engagements`)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{engagement_id, question, client_name, opened_at, closed_at, unfinished_shown, counts, runs[], wrapup, files[], crm_export}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: unknown, or not the caller's."}},"security":[{"bearer":[]}],"summary":"`GET /v1/me/engagements/{id}/record` — everything from one engagement.","tags":["client_services"]}},"/v1/me/workspaces":{"get":{"description":"Any authenticated caller. The caller's OWN workspace memberships (never another identity's) — how the UI and a provider's agent learn which `x-timepoint-workspace` header to send. No admin capability required.","operationId":"list_my_workspaces","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{workspace_id, display_name, role}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"GET /v1/me/workspaces — the caller's own memberships, no `admin` capability\nrequired: this is how the UI and P2 learn which `x-timepoint-workspace`\nheader to send, so any authenticated caller must be able to read their own\nrow of it (not another identity's — `name = $1` binds the caller alone).","tags":["client_services"]}},"/v1/meetings/preview":{"post":{"description":"Preview one meeting, privately: the odds of what you want out of it, each other attendee's stance and objections, the turning point, and a walk-back from what you want to three moves (or a no-route with the nearest reachable outcome), plus a rehearse link to Play on the same cast. Give the meeting, pasted invite text, or a `calendar_event_id` from your own connected calendar (an operator token lists them with `calendar_events`; a connector cannot, so give the meeting or paste the invite); another account's event, or an unknown one, is a 404. Starts two runs whose credits the ledger holds; the answer is `pending` until both runs end, then read it at `get_meeting_preview`: `complete` when both delivered, `partial` when one delivered and the other ended without a reading (only the delivered half is charged; `unavailable` says which part is missing and why), `failed` when neither delivered (nothing charged). Private to you: attendee names are in this view only. A new account's first quick previews are free (the house pays, under a daily cap; `funding: free`): the odds and stances run free, and the walk-back runs on credits when the account has them, else it is left out (`walk_back.verdict: not_included`). Once today's free previews are used up, an account without credits gets 402 `free_previews_closed_today`.","operationId":"preview_meeting","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeetingPreviewReq"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"The meeting preview (private)."},"401":{"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: insufficient_credits}`, or `{error: free_previews_closed_today, reason}` when today's free previews are used up and the account has no credits."},"404":{"description":"`calendar_event_id` unknown, or not on your calendar."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."}},"security":[{"bearer":[]}],"summary":"MP1: preview a meeting. Starts the simulation and the walk-back and returns\nthe preview, `pending` until both runs end (read it at `GET /v1/meetings/{id}`).","tags":["meetings"]}},"/v1/meetings/{id}":{"get":{"description":"Read one of your meeting previews (`preview_meeting`) again; it fills in as its runs deliver. `status` is `pending` until both runs end, then `complete`, `partial` (one half delivered; only it is charged; see `unavailable`) or `failed` (nothing delivered, nothing charged). `walk_back.verdict` is `route` only when at least one move held on its blind replay, `unjudged` when a route was drawn but none held and at least one could not be judged, `not_held` when every move was judged and none held, else `no_route` or `failed`. A drawn route carries `walk_back.checks: {held, unjudged, missed}` (null otherwise); an unjudged move is never counted as missed. Private: another account's id is a 404.","operationId":"get_meeting_preview","parameters":[{"description":"Meeting preview id (mp_…)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The meeting preview (private)."},"404":{"description":"Unknown, or not yours."}},"security":[{"bearer":[]}],"summary":"Read a meeting preview again (the poll). Owner only; anyone else is a 404.","tags":["meetings"]}},"/v1/models":{"get":{"operationId":"list_models","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items, defaults:{planning, simulation, dialog, synthesis, research_shallow, research_deep, interpretation}, models:[{slug, display_name, context_length}], synthesis:{mode, pair}}` — `models` is the configured models (env, stored defaults, synthesis pair) that are live and allow structured outputs, without prices; `items` is the short list of slugs this deployment offers (env and admin-configured models that are registered and live); `defaults` is each role's current default; `synthesis.mode` is `single` or `alternate`. The full registry-backed menu with prices is `GET /v1/admin/model-roles`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/outcomes":{"post":{"description":"Record what happened for one of your forecasts (a meeting preview `mp_…` or a pin `pin_…`). Settles it once: computes the Brier score, seals a sha256 hash (only the hash is sealed; visibility private_hash_only; the content stays private) and returns your private record: settled count, mean Brier and calibration bins. `/v1/meetings/{id}/outcome` and `/v1/pins/{id}/outcome` are the same call by route. Owner only: another account's id is a 404.","operationId":"record_outcome","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutcomeReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The settled record (private)."},"401":{"description":"Missing/unknown bearer token."},"404":{"description":"Unknown, or not yours."},"409":{"content":{"application/json":{"schema":{}}},"description":"`already_settled` or `forecast_not_ready`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."}},"security":[{"bearer":[]}],"summary":"MP3: record what happened.","tags":["outcomes"]}},"/v1/pins":{"post":{"description":"Pin an outcome by a date, privately. Walks backward from it: a route of dated milestones, each checked blind, or a no-route with the nearest reachable outcome and the candidates tried. Creates a watch (the monitors mechanism) that alerts when the route changes; the watch spends nothing until you choose a research source. Starts a run whose credits the ledger holds; `pending` until it delivers, then read it at `get_pin`. A walk that cannot try a route ends `failed` with a fixed `reason` and releases its credits.","operationId":"pin_outcome","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinReq"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"The pin (private)."},"401":{"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"Not enough credits."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."}},"security":[{"bearer":[]}],"summary":"MP2: pin an outcome by a date.","tags":["pins"]}},"/v1/pins/{id}":{"get":{"description":"Read one of your pins (`pin_outcome`) again; it fills in when its walk delivers. `verdict` is `pending`, `route` (at least one milestone held on its blind replay), `unjudged` (a route was drawn but none held and at least one could not be judged), `not_held` (a route was drawn, every milestone was judged and none held), `no_route` or `failed`. A drawn route (`route`, `unjudged`, `not_held`) carries `checks: {held, unjudged, missed}`, null otherwise; an unjudged milestone is never counted as missed. Each milestone is a dated event on the route; the walk's suggested test for it is `next_action`. Each milestone's `check.judged: false` means its replay could not be judged: unscored, never a miss. A `failed` pin carries `reason`, a fixed sentence (the goal could not be read as a dated event, a spending ceiling, or the walk stopped before proposing a route) and was not charged. Private: another account's id is a 404.","operationId":"get_pin","parameters":[{"description":"Pin id (pin_…)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The pin (private)."},"404":{"description":"Unknown, or not yours."}},"security":[{"bearer":[]}],"summary":"Read a pin again (the poll). Owner only; anyone else is a 404.","tags":["pins"]}},"/v1/play/allowance":{"get":{"description":"What Play costs you right now: free turns left on this account, whether the house still pays for free turns today, the price of one turn once the free turns are used, and your available credits.","operationId":"get_play_allowance","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Allowance"}}},"description":"Your Play allowance."},"401":{"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error, reason}`: degraded."}},"security":[{"bearer":[]}],"summary":"Your free Play turns and the turn price","tags":["play"]}},"/v1/play/attempts":{"get":{"description":"Your Play attempts, newest first (at most 100).","operationId":"list_play_attempts","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptList"}}},"description":"Your attempts."},"401":{"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"List your Play attempts","tags":["play"]},"post":{"description":"Take the seat in a Play challenge and start an attempt. Give exactly one of `challenge_id`, `run_id` (a run with a playable challenge) or `preview_id` (one of your meeting previews with a challenge). A live public or unlisted challenge, or one of your own, can be played. Starting is free; each turn uses a free turn first, then credits. Private to you.","operationId":"create_play_attempt","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptView"}}},"description":"The new attempt (private)."},"401":{"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"No playable challenge for that."},"409":{"content":{"application/json":{"schema":{}}},"description":"`challenge_invalid`, or `too_many_active`: too many open attempts on this account."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."}},"security":[{"bearer":[]}],"summary":"Start a Play attempt on a challenge","tags":["play"]}},"/v1/play/attempts/{id}":{"get":{"description":"One of your Play attempts: the challenge, the table's support now, every turn with the replies, and the score. Another account's attempt is a 404.","operationId":"get_play_attempt","parameters":[{"description":"Attempt id (at_…)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptView"}}},"description":"The attempt (private)."},"401":{"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, or not yours."}},"security":[{"bearer":[]}],"summary":"Read one of your Play attempts","tags":["play"]}},"/v1/play/attempts/{id}/abandon":{"post":{"description":"Give up one of your active Play attempts. A finished attempt is a 409.","operationId":"abandon_play_attempt","parameters":[{"description":"Attempt id (at_…)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptView"}}},"description":"The abandoned attempt."},"401":{"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, or not yours."},"409":{"content":{"application/json":{"schema":{}}},"description":"`attempt_over`."}},"security":[{"bearer":[]}],"summary":"Abandon a Play attempt","tags":["play"]}},"/v1/play/attempts/{id}/handle":{"post":{"description":"Put one of your finished Play attempts on its challenge's leaderboard under a handle: 3 to 24 letters, digits, `_` or `-`, unique on that board, never your account name. Optional; without a handle an attempt stays off the board.","operationId":"set_play_attempt_handle","parameters":[{"description":"Attempt id (at_…)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HandleReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttemptView"}}},"description":"The attempt with its handle."},"401":{"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, or not yours."},"409":{"content":{"application/json":{"schema":{}}},"description":"`not_finished`, `handle_taken` or `replay_mismatch`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."}},"security":[{"bearer":[]}],"summary":"Put a Play attempt on the leaderboard under a handle","tags":["play"]}},"/v1/play/attempts/{id}/turns":{"post":{"description":"Say something to the table. Each person replies in their own voice, and the judge credits only what your words actually address; the engine caps how much any one turn can move anyone. Uses a free turn while you have one and the house is open, otherwise holds one turn's price in credits and charges it only when the turn is stored (a failed turn charges nothing). A turn is never charged twice.","operationId":"post_play_turn","parameters":[{"description":"Attempt id (at_…)","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnView"}}},"description":"What the turn did."},"401":{"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`insufficient_credits`: no free turns and not enough credits."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, or not yours."},"409":{"content":{"application/json":{"schema":{}}},"description":"`attempt_over`, `turn_conflict`, `replay_mismatch`, `challenge_retired` or `too_many_failures`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`supply_closed`, `judge_failed` or `model_unavailable`; nothing was charged."}},"security":[{"bearer":[]}],"summary":"Take a turn in a Play attempt","tags":["play"]}},"/v1/prices":{"get":{"description":"Current price list: `{version, items: {<item>: credits…}, effective_from}` from the max-version `ledger.price_list` rows — served from the database, never compiled in.","operationId":"get_prices","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The max-version price list document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token: `{error: unauthorized, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unseeded price list — typed `{error: not_found}`; absence is a state, never invented."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: ledger_unavailable}` — named degraded mode; internals stay in the log."}},"security":[{"bearer":[]}],"summary":"`{version, items: {<item>: credits…}, effective_from}` from the max-version\nprice_list rows — served from the database, never compiled in.","tags":["ledger"]}},"/v1/publications":{"get":{"operationId":"list_publications","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""}},"security":[{"bearer":[]}],"tags":["publishing"]},"post":{"operationId":"create_publication","requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""}},"security":[{"bearer":[]}],"tags":["publishing"]}},"/v1/publications/{id}":{"get":{"operationId":"get_publication","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""}},"security":[{"bearer":[]}],"tags":["publishing"]},"put":{"operationId":"save_publication","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""}},"security":[{"bearer":[]}],"tags":["publishing"]}},"/v1/publications/{id}/actions":{"post":{"operationId":"publication_action","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""}},"security":[{"bearer":[]}],"tags":["publishing"]}},"/v1/responses":{"post":{"description":"Post a comment or a follow-up request on an item delivered to the caller (`item_kind` `result` or `wrapup`, and its id). `kind` is `comment` (a note on the item) or `follow_up` (a question the caller wants answered next; it stays `open` until the provider answers or closes it). Plain text, 1 to 2000 characters. Allowed exactly where the item is readable; unknown, not delivered and not yours are the same 404. Nothing is sent anywhere: the provider sees it in the app.","operationId":"post_item_response","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostResponseReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"`{id, kind, state, created_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, not delivered, or not yours — `{error: not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"Bad kind or empty/oversized body."},"429":{"content":{"application/json":{"schema":{}}},"description":"Too many messages in the last hour."}},"security":[{"bearer":[]}],"summary":"`POST /v1/responses` — a comment or a follow-up on a delivered item.","tags":["client_services"]}},"/v1/responses/{item_kind}/{item_id}":{"get":{"description":"The comments and follow-up requests on one item delivered to the caller, oldest first, each with its replies. `item_kind` is `result` (a delivered simulation, by run id from `list_delivered_results`) or `wrapup` (a delivered wrap-up, by wrap-up id from `list_delivered_wrapups`). A follow-up carries a `state`: `open`, `answered` or `closed`. Readable exactly where the item itself is; unknown, not delivered and not yours are the same 404. Post with `post_item_response`.","operationId":"list_item_responses","parameters":[{"description":"`result` or `wrapup`","in":"path","name":"item_kind","required":true,"schema":{"type":"string"}},{"description":"run id (result) or wrap-up id (wrapup)","in":"path","name":"item_id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{item_kind, item_id, thread: [{id, kind, body, created_at, state, mine, from, replies[]}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown, not delivered, or not yours — `{error: not_found}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/responses/{item_kind}/{item_id}` — the thread on one delivered item.","tags":["client_services"]}},"/v1/runs":{"post":{"description":"Admit a run. Order is law: validate (`quick_sim` requires `query` ≤500 chars; `story_sim` requires `url` — http/https, ≤2000 chars; `news_scan` takes neither — CONTRACTS §11.9; `deep_sim` requires `query` ≤2000 chars — §11.11; `find_money` takes an optional `query` ≤500 chars, defaulting to its self-description; `find_grants` is admin-only (it spends research budget), requires a workspace, and takes as `query` (≤500 chars) the applicant's OWN project for this search — one of the client's projects; a slate never mixes them — while the funder programs are what its slate lists; it also takes `applicant_entity_id`, `applicant_paths` and `geography`) → LLM balance gate (a closed gate refuses with 402 `llm_no_headroom` and reserves NOTHING) → reserve credits at the kind's served price (1h hold TTL) — EXCEPT `deep_sim`, whose admission reserves NOTHING (the quote is composed at the PLAN phase and reserved only on the confirm_plan `proceed` decision; `reservation_id` is null), and `find_money` and `find_grants`, zero-hold parents whose spends are their children's (quick_sims hold at fan-out; each promoted deep_sim prices at its own confirm; find_grants' one sourcing pass spends against its provider caps, frozen at admission from the sim_params defaults and reported on `GET /v1/runs/{id}/spend`) → one tx {run row + `run.accepted` event + queue row}. Returns 202 with the run id and its SSE events URL; the run itself executes async on the worker. E3 (W2): named `x-timepoint-workspace`, the run is admitted in that workspace (provider/admin members only — a client member is refused); absent, the caller's personal space, exactly as before workspaces existed. `find_money` in a workspace sources that workspace's GROUNDED deal record (every member's, never the caller's personal one) and its children run there too; `find_grants` has no personal form and requires one. `news_scan` and a public research run (`publication_id`) have no workspace form and refuse one.","operationId":"create_run","parameters":[{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response (header idempotent-replay: true) instead of re-running admission; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRunReq"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Admitted: `{run_id, reservation_id, events_url, kind, state: created}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"Typed refusal: `{error: llm_no_headroom, gate}` (balance gate closed — nothing reserved) or `{error: insufficient_credits}` (ledger refusal)."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: forbidden, reason}` — a field the caller may not set. `models` (the planning/simulation model picks) and any `admin_only` key in `sim_params` are editor capabilities; `cast` is not, and stays open to any account for entities it owns. `find_grants` spends research budget and needs the `admin` capability. Also `{error: cast_approval_required | cast_approval_invalid, reason}` — a cast (or cast cap) above 20 without a usable `cast_approval_id`, or one that is missing, not yours, pending, rejected, revoked, expired or smaller than the cast. Also `{error: workspace_read_only, reason}` — a client member of the named workspace may never start a run there. Nothing reserved."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_not_found}` — a malformed, unknown or not-a-member `x-timepoint-workspace`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: reservation_conflict}` — a hold for this run already exists; `{error: deep_sim_draft_cap, cap, reason}` — the caller already has `cap` (3) deep_sim plans not yet confirmed; confirm or cancel one (`answer_decision`) first. Nothing reserved."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_kind | invalid_query | invalid_url | unsupported_url | workspace_unsupported | workspace_required | query_required | applicant_not_in_workspace | invalid_applicant_paths | invalid_geography, reason}` — `workspace_unsupported`: `news_scan` or a public research run (`publication_id`) named a workspace; neither has a workspace form. `workspace_required`: a `find_grants` run without `x-timepoint-workspace`. `query_required`: a `find_grants` run without its project. `applicant_not_in_workspace`, `invalid_applicant_paths`, `invalid_geography`: a `find_grants` scope field that does not validate."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable | ledger_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Admission: auth → validate → balance gate → reserve → one tx {run + event + queue}.","tags":["runs"]}},"/v1/runs/latest":{"get":{"description":"The CALLER's most recent run in ANY state (CONTRACTS §11.4): `{id, state}`. Caller-scoped — this reads the latest run YOU created, never the deployment's global latest.","operationId":"get_latest_run","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{id, state}` of the caller's most recent run."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"The caller has no runs yet — typed `{error: not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Most recent run in ANY state (CONTRACTS §11.4).","tags":["runs"]}},"/v1/runs/latest/cockpit":{"get":{"description":"The cockpit default (CONTRACTS §11.4): the `cockpit_doc` of the CALLER's most recent COMPLETE run that has one, served verbatim. Caller-scoped — never the deployment's global latest.","operationId":"get_latest_cockpit","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The `cockpit/0.1` document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"The caller has no complete run with a cockpit_doc yet — typed `{error: not_found}`."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Cockpit default: the most recent COMPLETE run with a cockpit_doc.","tags":["docs"]}},"/v1/runs/{id}":{"get":{"description":"One run row plus its reservation: `{id, kind, state, phase, parent_run_id, price_list_version, created_by, query, created_at, terminal_at, reservation_state, reservation, models, funding}`. A run without a reservation serves explicit nulls — absence is labeled, never invented. `models` is the run's frozen admission record of the model slug per role (`planning`, `simulation`, `dialog`, `synthesis`, …), the disclosure of what ran it; null when the run froze none. `funding` is `free` when the freemium house budget paid for it (a new account's first quick previews; it then holds no reservation), else `credits`.","operationId":"get_run","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The run document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run, or a run that is not yours — the SAME typed `{error: not_found}` either way (a refusal never confirms that somebody else's run exists)."},"410":{"content":{"application/json":{"schema":{}}},"description":"Your run, withdrawn by a retraction: its tombstone `{error: withdrawn, run_id, kind, state: withdrawn, run_state, created_at, created_by, withdrawn_at, withdrawn_by, query_sha256}`, never content."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Run row + reservation state; absence is labeled with explicit nulls.","tags":["runs"]}},"/v1/runs/{id}/chat":{"post":{"description":"Converse ABOUT a delivered run (CONTRACTS §11.7). The server assembles the context fresh per call — the run row, the stored CockpitDoc (404 when absent), the per-moment dialog, the analysis contract, and the client-held history (≤10 turns, each ≤2k chars) — under the versioned `run_chat` honesty prompt: answers come ONLY from the simulation record, absences say so, speculation is labeled, staged-and-synthetic framing is maintained. Credit-free but spend-tracked (≤ $0.02/message, `llm.spend` tagged with the run id) and balance-gated. The server holds no conversation state: send the history back with each call.","operationId":"run_chat","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{answer, cost_usd}` — the model's plain-text answer and the real recorded spend of this message."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"Typed refusal `{error: llm_no_headroom, gate}` — the balance gate is closed; nothing was spent."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run, a run that is not yours, or a run without a cockpit_doc artifact — the same typed `{error: not_found, reason}` (a refusal never confirms that somebody else's run exists)."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_message | invalid_history, reason}` — empty/oversize message, >10 turns, oversize turn, or a role outside user|assistant."},"502":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_failed, reason}` — the model call itself failed (transport/provider)."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: chat_unavailable, reason}` — model unconfigured/dead or database unavailable."}},"security":[{"bearer":[]}],"summary":"Converse about a delivered run — record-grounded, credit-free, spend-tracked.","tags":["chat"]}},"/v1/runs/{id}/cockpit":{"get":{"description":"The run's stored `cockpit_doc` artifact (contract `cockpit/0.1`), served VERBATIM from `run.artifacts` — never re-assembled on read — except the appendix's VOLATILE parts (forecast rows, verdicts), which attach at read because they change after the run completes — and `models`, the run's frozen record of the model slug per role, attached at read as disclosure.","operationId":"get_run_cockpit","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The `cockpit/0.1` document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"No cockpit_doc artifact for this run, an unknown run, or a run that is not yours — the same typed `{error: not_found}` (a refusal never confirms that somebody else's run exists)."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: db_unavailable}`."}},"security":[{"bearer":[]}],"summary":"The stored cockpit_doc artifact for a run, verbatim — with ONE stated\nexception: the volatile appendix parts (forecast rows that resolve later,\nappend-only verdicts) attach at read, the `with_taxonomy` discipline.","tags":["docs"]}},"/v1/runs/{id}/decisions/{decision_id}":{"post":{"description":"Answer an open decision request on a paused run (B4.0, CONTRACTS §11.11): `{picks: [option ids]}` — between `min` and `max` of the request's `options[].id`, no duplicates. Only the run's creator may answer (others see 404). Records the decision + `run.decision` event and RESUMES the run by re-queueing its job; the pipeline proceeds over the approved picks (e.g. the J4 editorial gate admits story_sim children only for approved picks). Answers are once-only.","operationId":"answer_decision","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Decision request id","in":"path","name":"decision_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnswerReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, decision_id, kind, picks, requeued: true}` — decision recorded, run re-queued."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run/decision — or a caller who is not the run's creator (typed `not_found`; existence unleaked)."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_decided}` (answers are once-only) or `{error: run_terminal}`; `{error: decided_through_proposals}` for a find_grants `grant_proposals` decision, which is decided proposal by proposal."},"410":{"content":{"application/json":{"schema":{}}},"description":"`{error: decision_expired}` — the window closed; the sweep fails the run typed."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_picks, reason}` — min/max/unknown-id/duplicate violations."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: decisions_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Answer a run's open decision: validates owner (non-owners see 404 — existence\nunleaked) / undecided (409) / unexpired (410) / picks (422), then ONE tx\n{guarded decided_at flip + `run.decision` event + the run's job RE-QUEUED in\nits original queue env} — the resume. The pipeline reads the decided marker\non re-dispatch and proceeds over the approved picks.","tags":["decisions"]}},"/v1/runs/{id}/events":{"get":{"description":"Server-Sent Events stream of the run's persisted `run_events` rows in seq order (the SSE stream IS the log — nothing synthesized). Each frame's `id` is the row seq, `event` is the row type, and `data` is `{seq, type, payload, created_at}`. The stream ends only AFTER the `run.terminal` row is streamed; completion is never inferable from stream closure. Supports resume via `Last-Event-ID` header or `from` query param (strictly-after seq); `:hb` heartbeat comments while polling.","operationId":"run_events_sse","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Resume strictly after this seq (same semantics as `Last-Event-ID`).","in":"query","name":"from","required":false,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"text/event-stream":{"schema":{"type":"string"}}},"description":"`text/event-stream` of persisted run events; terminal frame type is `run.terminal`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run, or a run that is not yours — the same typed `{error: not_found}` before any streaming (never an empty stream, never a 403 that would confirm existence)."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable}`."}},"security":[{"bearer":[]}],"summary":"SSE ≡ the log: persisted `run_events` rows in seq order, ending after `run.terminal`.","tags":["runs"]}},"/v1/runs/{id}/independent-reviews":{"get":{"description":"A run's independent reviews, oldest first, with a summary: `{subject: {kind, id}, workspace_id, summary: {count, stages: {plan, terminal}}, items}`; each stage carries `{count, approve, reject, latest: {id, verdict, created_at} | null}`. The run must be visible in the active space (name its workspace in `x-timepoint-workspace`). Provider and admin members read; a client member is refused.","operationId":"list_run_independent_reviews","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{subject, workspace_id, summary, items}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: reviews_provider_only}`: a client member."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`."},"410":{"content":{"application/json":{"schema":{}}},"description":"Your run, withdrawn by a retraction: its tombstone, never its reviews."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: independent_reviews_unavailable}`."}},"security":[{"bearer":[]}],"tags":["reviews"]},"post":{"description":"Record an independent review of a run, by the caller's own login (never named in the body) and its sign-in method (`token`, `magic_link` or `google`; an MCP call is `token`). Stage `plan`: the run reached its confirm_plan decision. Stage `terminal`: the run is complete or failed. The reviewer must not be the run's proposer (its `created_by`). A personal run is visible to its owner only, so every review of one is refused as `own_proposal`: independent review is a workspace feature. Name the run's workspace in `x-timepoint-workspace`; a provider or admin member writes, a client member is refused. Reviews are append-only.","operationId":"record_run_independent_review","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the caller's personal space. A malformed id, an unknown workspace and one the caller is not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordReviewReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The stored review: `{id, subject_kind: run, kickoff_id: null, run_id, workspace_id, stage, verdict, reasons, subject_sha256, reviewer, reviewer_via, proposer, created_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_read_only}` (a client member) or `{error: own_proposal, reason}` (the reviewer proposed this run). Nothing is written."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}`: unknown, in another space and not yours are one answer."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_plan_yet}` (stage plan before the confirm_plan decision) or `{error: run_not_terminal, run_state}` (stage terminal before complete or failed)."},"410":{"content":{"application/json":{"schema":{}}},"description":"Your run, withdrawn by a retraction: its tombstone, as `GET /v1/runs/{id}` serves it."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_independent_review | stage_not_for_subject, reason}`: unknown stage or verdict, 0 or more than 20 reasons, an empty or over-long reason, a malformed `subject_sha256`, an unknown field, or stage pre_spend on a run."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: independent_reviews_unavailable}`."}},"security":[{"bearer":[]}],"tags":["reviews"]}},"/v1/runs/{id}/plan":{"get":{"description":"The `plan` artifact of a deep_sim run — roster (with registry paths), quote and brief — served verbatim so the plan survives approval.","operationId":"get_run_plan","parameters":[{"description":"run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The plan artifact."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run, not yours, or no plan artifact — typed `{error: not_found}`."}},"security":[{"bearer":[]}],"summary":"The stored `plan` artifact of a deep_sim run — the roster (each entry with its\nregistry `path`, so the plan page can link `/entity`), the quote and the\nbrief. Served verbatim, and — the point of the endpoint — it OUTLIVES\napproval: the roster used to be reachable only in the pre-approval decision\ncard, so a completed run became unfindable by the question it answered and\nthe entities it named. This is what the `/plan?run=` page reads.","tags":["docs"]}},"/v1/runs/{id}/portal":{"get":{"description":"The `portal_doc` of a portal_sim run — the WHOLE ensemble (every candidate, retained or not), the convergence/divergence profile, and the instrumentation, with node labels attached from the live bundle and `models`, the run's frozen record of the model slug per role, attached as disclosure.","operationId":"get_run_portal","parameters":[{"description":"run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The `portal/0.1` document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run, not yours, or no portal_doc — typed `{error: not_found}`."}},"security":[{"bearer":[]}],"summary":"The stored PortalDoc (`portal/0.1`), taxonomy attached at read time (the\ncockpit's discipline: the artifact is history; the vocabulary is live).","tags":["docs"]}},"/v1/runs/{id}/reanalyze":{"post":{"description":"Re-run the deep_sim ANALYZE step over the STORED timeline with the current analysis prompt, the plan's locked invariants and its time frame. Deep_sim runs in the complete state with single-model synthesis only. Writes a new VERSIONED `analysis` artifact (with the deterministic answer check) and replaces the cockpit doc's answer and answer-check finding, in one transaction; moments, gate report, assertions, participation, phase and settlement are untouched. A failed model call writes nothing. Changing the report moves its evidence hash, as reaudit does. Priced `reanalyze = 2` credits, reserve→settle under a fresh audit key. Emits NO run_events — the run's terminal stays its last event; the artifacts, the ledger, and this response are the record.","operationId":"reanalyze_run","parameters":[{"description":"Run id (a delivered deep_sim run)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, analysis_version, reanalysis_of_version, prompt_version, action, answer_check, analysis_content_hash, previous_analysis_content_hash, cockpit_content_hash, settled_credits, cost_usd}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_no_headroom, gate}` or `{error: insufficient_credits}` — nothing reserved/settled."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run — typed `{error: not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: reanalyze_unavailable, reason}` — not a deep_sim, not complete, withdrawn, A/B synthesis, sealed, missing its artifacts, or its analysis changed during the call (nothing written)."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: reanalyze_failed, reason}` — the model call failed or its output stayed invalid after one retry; nothing was written and the hold was released."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable | ledger_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Re-run ANALYZE over the STORED timeline with the current analysis prompt.","tags":["runs"]}},"/v1/runs/{id}/reaudit":{"post":{"description":"Re-run the deep_sim GATE's two judges (allegiance, typicality) plus the pure time-binding check over the STORED timeline (CONTRACTS §11.11 B4.2). Deep_sim runs in the complete (clean or blocked-for-review) state only. Writes a new VERSIONED `gate_report` artifact; a blocked delivery whose reaudit comes back clean flips to delivered-clean (the run row's phase). Priced `reaudit = 2` credits, reserve→settle under a fresh audit key (the run's own reservation is already terminal). Emits NO run_events — the run's terminal stays its last event; the artifact, the ledger, and this response are the record.","operationId":"reaudit_run","parameters":[{"description":"Run id (a delivered deep_sim run)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, verdict, previous_verdict, gate_report_version, flipped, settled_credits, cost_usd}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_no_headroom, gate}` or `{error: insufficient_credits}` — nothing reserved/settled."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown run — typed `{error: not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: reaudit_unavailable, reason}` — not a deep_sim, not delivered, or missing its artifacts."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable | ledger_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Re-run the GATE's two judges over the STORED timeline (B4.2).","tags":["runs"]}},"/v1/runs/{id}/replicate":{"post":{"description":"Replicate a COMPLETE deep_sim run that a kickoff compiled: one new kickoff cloned from the source kickoff (same owner, workspace, question, notes, proposals and scopes; title `Replicate: <source title>`; created by you; status `approved`, its approvals the source's re-signed by you, and a `replicate-…` decision naming the source run, the source kickoff and who approved it there), then ONE deep_sim run admitted from the clone through admission's own path (the clone moves approved → planned in the same transaction). The replicate freezes the SOURCE's frozen cast (the same entity ids and fact ids, in order, never a fresh fact selection), its resolved sim params, its resolved model picks and its office-context date, so a default changed since changes nothing; a model no longer available refuses. Before anything is written the controls the replicate would freeze are compared with the source's on query, original_query, cast (entity and fact ids), edges among the cast, office context (content hash), sim params and models: any difference refuses with `409 replicate_not_identical` and `differences: [{field, reason}]`, unless `allow_differences: true`, which admits it and reports them. Never differs silently. Lineage: the run's `parent_run_id` is the source, and its `run.accepted` event records `lineage: {parent_run_id, relation: \"replicate\", source_kickoff_id}`. Spend: nothing is reserved; the replicate's plan phase composes its own quote, held only when someone answers the NEW run's confirm_plan decision (never auto-confirmed). The research approver only (`approvals::can_approve`), because the replicate is created already approved; a model pick and admin-only sim params need the `admin` capability. `dry_run: true` runs every check and writes nothing, not even the kickoff. Space: with no `x-timepoint-workspace` header this acts in the run's own workspace (you must be a member); a header naming a different workspace is `409 workspace_mismatch`. Send `{}` for the defaults.","operationId":"replicate_run","parameters":[{"description":"The source run: a complete deep_sim run a kickoff compiled","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the run's own workspace (personal, if it has none) — you must still be a member of it. Naming a workspace other than the run's own is refused (409 workspace_mismatch). A malformed id, an unknown workspace and one you are not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}},{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the exact first response (header idempotent-replay: true) instead of replicating again; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior: every call that admits mints a new replicate.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplicateReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Dry run: `{dry_run: true, would_admit, replicate_of, source_kickoff_id, quote, resolved, refusals, identical, differences, reserves: \"nothing\"}`; nothing written. A refusal before the identity check lists it in `refusals` with `identical` and `differences` null."},"201":{"content":{"application/json":{"schema":{}}},"description":"`{replicate_of, source_kickoff_id, kickoff_id, run_id, run: {run_id, kind, state, workspace_id, created_by, query, parent_run_id}, run_controls, quote (indicative, unreserved), identical, differences, reserves: \"nothing\", next: \"answer the new run's confirm_plan decision to spend\"}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_no_headroom, gate}` — the balance gate is closed."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: replicate_forbidden | workspace_read_only | forbidden | cast_approval_required | cast_approval_invalid, reason}` — not the research approver; a client member; a model pick or admin-only param without `admin`; a cast cap above the admin tier without a usable `cast_approval_id`. Nothing written."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}` — the run is not visible in this space (the same answer `GET /v1/runs/{id}` gives)."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_kickoff_run | run_not_complete | replicate_not_identical | workspace_mismatch, reason, state?, differences?}` — no kickoff in this space compiled the run; the run is not `complete` (`state` names it); the replicate would differ (`differences: [{field, reason}]`); the header named another workspace. Nothing written."},"410":{"content":{"application/json":{"schema":{}}},"description":"Your run, withdrawn by a retraction: its tombstone, as `GET /v1/runs/{id}` answers."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_replicable | invalid_sim_params | invalid_query | kickoff_malformed, reason}` — not a deep_sim run (or one with no frozen controls); a frozen value admission now refuses (an out-of-bounds param, a model no longer available, a fact no longer on the actor's record). Nothing written."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable | kickoffs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/runs/{id}/replication":{"get":{"description":"Replication verdicts for a run, read from either side: `{run_id, replicate_of: uuid | null, pairs: [{original: {run_id, state, top_outcome: {label, p, tied_labels?} | null}, replicate: {…}, verdict, basis}]}`. The pairs are (the run this one replicates, this run) when this run is a replicate, then (this run, r) for each replicate r of this run, newest first. `verdict`: `replicated` when both runs are complete and their top outcomes match; `not_replicated` when both have answers whose top outcomes differ; `pending` while either is created or running; `unavailable` when either failed or has no answer outcomes. The top outcome is the highest-probability outcome of the run's current answer (the one its report shows, after any reanalysis); a tie at the top compares the set of tied labels; labels match after case-folding, removing punctuation and collapsing whitespace. Labels are model-written text, so an equivalent outcome worded differently reads `not_replicated`; `basis` states the rule on every pair. Deterministic, no model call. Only runs visible in the space are shown (a client member sees runs delivered to them). Space: as `replicate_run` (absent header = the run's own workspace, if you are a member).","operationId":"get_run_replication","parameters":[{"description":"A run: an original or a replicate","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Workspace uuid to act in (W2). Absent = the run's own workspace (personal, if it has none) — you must still be a member of it. Naming a workspace other than the run's own is refused (409 workspace_mismatch). A malformed id, an unknown workspace and one you are not a member of all answer 404 workspace_not_found.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{run_id, replicate_of, pairs}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found | workspace_not_found}` — not visible in this space."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: workspace_mismatch}` — the header named a workspace other than the run's own."},"410":{"content":{"application/json":{"schema":{}}},"description":"Your run, withdrawn by a retraction: its tombstone."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/runs/{id}/report":{"get":{"description":"It is an EXPORT, not the product's report room. The product used to open\nthis in a bare popup with no top bar, no rail and no way back; that reading\nis `/report?run={id}` now (ui/src/tools/Report.tsx), which flies the same\nCockpitDoc through the same renderers as the cockpit and the storyboard.\nThis response therefore carries its own way home: an export that lands in a\nbrowser (a bookmark, a `curl > file.html`, a pasted link) must still say\nwhere it came from and offer the door back into the product.","operationId":"get_run_report","parameters":[{"description":"run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"text/html":{}},"description":"long-form HTML report"},"404":{"description":"unknown run, not yours, or no cockpit_doc — the same typed `{error: not_found}`"}},"security":[{"bearer":[]}],"summary":"The long-form HTML rendering of a stored CockpitDoc — answer, then\ntimepoints with moments and dialog, NO coordinates, honesty labels inline.\nThe only server-rendered reading.","tags":["docs"]}},"/v1/runs/{id}/share":{"post":{"operationId":"create_share","parameters":[{"description":"run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"{url, token, expires_at} — the token appears exactly once"}},"security":[{"bearer":[]}],"summary":"Mint a share for a delivered run. Returns the URL once; only the hash is kept.","tags":["shares"]}},"/v1/runs/{id}/simulation":{"get":{"operationId":"list_simulation_branches","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"410":{"description":""}},"security":[{"bearer":[]}],"summary":"List the run's simulation branches: `{run_id, branches: [{branch_id,\nrevision, state_hash, manifest_hash}]}` (empty when the run has none).\nReadable by whoever may read the run: its creator, a client it was\nDELIVERED to, and a team key's engagement grant. Never a share artifact.\nUnknown and not-yours are one 404; a withdrawn run is 410 for its creator\nand 404 for everyone else.","tags":["simulation"]},"post":{"operationId":"initialize","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""}},"security":[{"bearer":[]}],"summary":"Initialize an explicit resource-disclosure/1 manifest (source_revision,\nknowledge, branch, initial_world). Reusing an identical manifest is idempotent.\nRules are declared assumptions; this does not validate narrative forecasts.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}":{"get":{"operationId":"read","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""},"410":{"description":""}},"security":[{"bearer":[]}],"summary":"Replay every accepted event and compare the stored branch projection,\nscenario knowledge included. Readable by the run's creator, a client it was\nDELIVERED to, and a team key's engagement grant; never a share artifact.\nThe creator and a team-grant reader get the full view; any other reader\ngets the client view, which withholds the authoring account\n(`knowledge.owner_id`) and says so with `identity_withheld: true`. Unknown\nand not-yours are one 404; a withdrawn run is 410 for its creator only.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}/actions":{"post":{"operationId":"act","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""}},"security":[{"bearer":[]}],"summary":"Apply an explicit Proposal: id, branch, expected_revision, actor, action,\nmodel_receipts. Domain rejection returns an accepted=false durable receipt.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}/actors/{actor}":{"get":{"operationId":"actor","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"actor","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""},"410":{"description":""}},"security":[{"bearer":[]}],"summary":"Inspect only the named actor's permitted knowledge at the committed head.\nSame readers and views as the branch read: the creator and a team-grant\nreader get the full view, a client the run was DELIVERED to gets the client\nview (`identity_withheld: true`); never a share artifact.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}/dialog":{"post":{"operationId":"dialog","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""}},"security":[{"bearer":[]}],"summary":"Optional actor-scoped speech. Input: id, revision, actor, present, voice\n(plain/brief). Credit-free, provider-accounted, at most four turns per event.\nPending means unresolved work; retrying its ID never resends inference.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}/fidelity":{"post":{"operationId":"fidelity","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""}},"security":[{"bearer":[]}],"summary":"Record a shadow coverage evaluation. Input: id, revision, predictions. Every\ncommitted event/actor is counted under uniform/1 and simple/1. Missing readings\nremain unknown; this never suppresses mandatory work or claims measured savings.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}/fork":{"post":{"operationId":"fork","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""}},"security":[{"bearer":[]}],"summary":"Fork an exact committed prefix: child UUID and parent revision. Initial state\ncomes from replay, not an independently generated predecessor description.","tags":["simulation"]}},"/v1/runs/{id}/simulation/{branch}/search":{"post":{"operationId":"search","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"branch","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":""},"404":{"description":""},"409":{"description":""}},"security":[{"bearer":[]}],"summary":"Evaluate and retain explicit candidate action paths from one committed state.\nInput: id, revision, target_cash_cents, candidates, max_steps (0..128).\nFeasible ranking excludes invalid and incomplete evaluations. No model call.","tags":["simulation"]}},"/v1/runs/{id}/spend":{"get":{"description":"One run's spend: `{run_id, kind, state, credits: {reservation_state, reserved, settled}, usd: {openrouter, exa}, openrouter_calls, estimated_usd, children: {runs, openrouter_usd, credits_settled}}`. `usd.openrouter` sums the run's recorded model calls (`estimated_usd` is the part recorded as an estimate); a run makes no Exa calls itself, so `usd.exa` is 0 (research spend is per pass, on `GET /v1/drilldown/jobs/{id}/spend`). `children` sums the runs this run spawned (news_scan stories, find_money and find_grants quick sims). A find_grants run also carries `pass: {exa_usd, openrouter_usd, cap_exa_usd, cap_openrouter_usd, stopped}` — its one sourcing pass's committed spend (settled actuals, else the reservation) against the caps frozen at admission, and whether a cap stopped it; its `usd.exa` is the pass's Exa spend. The run's creator or an administrator; anyone else gets the same 404 as an unknown run.","operationId":"get_run_spend","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The run's spend."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable | status_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/runs/{id}/steps":{"get":{"operationId":"list_orchestration_steps","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Frozen configuration, authority, revisioned steps and append-only events"},"404":{"description":"Run not found"}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/runs/{id}/steps/{step_id}":{"post":{"operationId":"decide_orchestration_step","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"step_id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"Approve, review, edit or decline one open orchestration revision"},"403":{"description":"Required credential class or independent proposer check failed"},"409":{"description":"Run or step already decided"}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/runs/{id}/storyboard/charts":{"get":{"description":"Chart state for a run's storyboard: `{rendered: [t…], abstained: [t…], price_cr}`. Absence of a t means that frame is uncharted until a reader pays for the attempt.","operationId":"list_storyboard_charts","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{rendered, abstained, price_cr}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: run_not_found}` — unknown run OR a run that is not yours."}},"security":[{"bearer":[]}],"summary":"Which frames hold charts (or stored abstentions) + the served price.","tags":["storyboard"]},"post":{"description":"Ask the chart agent for one frame's quantitative reading. Charges the served `storyboard_chart` price BEFORE the call; refunds typed on failure AND on honest abstention (`{abstained: true, reason}`); a delivered chart stays charged and is stored once forever as `storyboard_chart_t{t}` (spec-validated, reject-never-repair).","operationId":"render_storyboard_chart","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{t, rendered|abstained, charged_cr, already?}`."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: insufficient_credits}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: run_not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: bad_timepoint}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: chart_gen_unavailable | agent_output_invalid}` (after refund)."}},"security":[{"bearer":[]}],"summary":"Chart one frame — charge first, one validation retry, refund on failure or\nabstention; the abstention is stored so repeat clicks answer free.","tags":["storyboard"]}},"/v1/runs/{id}/storyboard/charts/{t}":{"get":{"description":"The validated chart spec (or stored abstention) for one frame. 404 `not_rendered` until a reader pays for the attempt.","operationId":"get_storyboard_chart","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"t","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{spec, model}` — spec may be an abstention."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_rendered | run_not_found}` — the latter also covers a run that is not yours."}},"security":[{"bearer":[]}],"summary":"The stored spec — the client renders ONLY this.","tags":["storyboard"]}},"/v1/runs/{id}/storyboard/images":{"get":{"description":"Rendered storyboard frames for a run: `{rendered: [t…], price_cr}`. Absence of a t means that frame is text-only until a reader pays to render it.","operationId":"list_storyboard_images","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{rendered, price_cr}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: run_not_found}` — unknown run OR a run that is not yours."}},"security":[{"bearer":[]}],"summary":"Which frames are rendered + the served price — the page paints buttons from\nthis, never from a compiled-in price.","tags":["storyboard"]},"post":{"description":"Render the image for one storyboard frame `{t}`. Charges the caller the served `storyboard_image` price BEFORE the generation call (refunded typed if generation fails); an already-rendered frame answers `{already: true}` and charges nothing. The png lands as the run artifact `storyboard_image_t{t}`.","operationId":"render_storyboard_image","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{t, rendered, charged_cr, already?}`."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: insufficient_credits}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: run_not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: bad_timepoint}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: image_gen_unavailable}` — no key, no model, budget gate, or provider failure (after refund)."}},"security":[{"bearer":[]}],"summary":"Render one frame — charge first (the credit buys the real attempt), refund\ntyped on failure, store once forever.","tags":["storyboard"]}},"/v1/runs/{id}/storyboard/images/{t}":{"get":{"description":"The rendered png for one storyboard frame. 404 `not_rendered` until a reader pays to render it — absence is a state, not an error page.","operationId":"get_storyboard_image","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"in":"path","name":"t","required":true,"schema":{"format":"int64","type":"integer"}}],"responses":{"200":{"content":{"text/plain":{"schema":{"type":"string"}}},"description":"image/png bytes."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_rendered | run_not_found}` — the latter also covers a run that is not yours."}},"security":[{"bearer":[]}],"summary":"The stored png, served with the bearer (the page fetches → blob URL).","tags":["storyboard"]}},"/v1/runs/{id}/wait":{"get":{"description":"Long-poll one run. Holds the request until the run is `complete` or `failed`, pauses for a decision (phase `awaiting_decision`), leaves `since_state` or `since_phase`, or `timeout_s` passes (at most 50 s, default 25 s). Answers 200 with the `GET /v1/runs/{id}` document plus `{changed, timed_out, waited_ms}`; call again with the returned state to keep waiting. Readable exactly as `GET /v1/runs/{id}` is.","operationId":"wait_run","parameters":[{"description":"Run id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"Seconds to wait at most, 1 to 50 (default 25).","in":"query","name":"timeout_s","required":false,"schema":{"format":"int64","minimum":0,"type":"integer"}},{"description":"The state the caller last saw (`created`, `running`, …). The wait\nreturns as soon as the run is in another state. Without it the wait\nreturns when the run is terminal or waits on a decision.","in":"query","name":"since_state","required":false,"schema":{"type":"string"}},{"description":"The phase the caller last saw; the wait also returns when it changes.","in":"query","name":"since_phase","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The run document with `{changed, timed_out, waited_ms}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — unknown run, or not yours."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: runs_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/settings/name-lookups":{"get":{"description":"The caller's name-look-up setting and what it is doing right now: `{choice: \"allow\"|\"forbid\"|\"consent_gate\", note, asks: [...], withheld: [...]}`. `choice` defaults to `allow` for a caller who has never set it (no row is written on read). Both lists are derived from the setting AS IT STANDS, not from stored history: `asks` are the people a look-up is waiting on the caller for, `withheld` are the watched people the setting is currently keeping Timepoint from looking up, and both are empty under `allow`. Every entry carries a plain-sentence `note`. Read-only — reading never raises a question.","operationId":"get_name_lookups","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The setting and its current effect."},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"The setting, the questions waiting on the user, and the names being left alone.","tags":["settings"]},"put":{"description":"Set the caller's name-look-up choice. Body: `{choice: \"allow\"|\"forbid\"|\"consent_gate\"}` — anything else is a typed 422 `invalid_choice` and nothing is written. Answers already given about individual people are kept, so switching away from `consent_gate` and back does not re-ask. Returns `{choice, note}`.","operationId":"put_name_lookups","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The stored choice."},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"`{error: invalid_choice}`."}},"security":[{"bearer":[]}],"summary":"Change the setting. Takes effect on the next look-up, server-side.","tags":["settings"]}},"/v1/settings/name-lookups/asks/{entity_id}":{"post":{"description":"Answer for ONE person: `{allow: true|false}`. The answer is remembered for that person and applies to every later look-up (answering again replaces it). Answering someone who was never asked about is legal and simply records the answer in advance. An entity id that is not one of the caller's own answers 404 — exactly as an id that never existed does, so the refusal says nothing about which records are out there.","operationId":"answer_name_lookup_ask","parameters":[{"description":"The registry entity id.","in":"path","name":"entity_id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The recorded answer."},"401":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"`{error: not_found}`."}},"security":[{"bearer":[]}],"summary":"Answer one of the questions — remembered for that person from then on.","tags":["settings"]}},"/v1/shared/{token}":{"delete":{"operationId":"revoke_share","parameters":[{"description":"the tps_ share token","in":"path","name":"token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"revoked"},"403":{"description":"not the creator"}},"security":[{"bearer":[]}],"summary":"Revoke (creator only). The page stops resolving immediately.","tags":["shares"]}},"/v1/sim-params":{"get":{"description":"The `run.sim_params` defaults (V3, gate G8), for every signed-in caller. Each item: `key`, `kind` (the family its prefix names: deep_sim | portal_sim | quick_sim | story_sim | research | cast_approval), `run_kinds` (the kinds that take it as a per-run `POST /v1/runs {sim_params}` override), `default`, `min`, `max`, `value_type` (int | numeric), `class`, `admin_only` (only an admin may override it), `affects_price` and `description`. Non-admins see every row too: they need the defaults that apply to them, and the numbers carry no secret; `admin_only` marks what they cannot override. `cast_tiers` restates the cast rows (C1): the default entity cap, the admin tier (the row max), facts per entity and the largest approval N.","operationId":"list_sim_params","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{is_admin, items:[{key, kind, run_kinds, default, min, max, value_type, class, admin_only, affects_price, description}], cast_tiers:{entity_key, fact_key, default_entities, admin_max_entities, facts_per_entity, default_facts, approval_max_cast}}`"},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: sim_params_unavailable}`."}},"security":[{"bearer":[]}],"tags":["runs"]}},"/v1/staged-actions":{"get":{"description":"Admin. Staged actions, newest first (at most 200), filtered by `status` (default `requested`, the ones awaiting a person). Each carries its `summary`, the `preview` recorded at staging, and its append-only `history`.","operationId":"list_staged_actions","parameters":[{"description":"`requested` (default), `executed`, `rejected`, `cancelled` or `all`.","in":"query","name":"status","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{actions: [...]}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_staged_action}`: unknown status."}},"security":[{"bearer":[]}],"tags":["admin"]},"post":{"description":"Admin. Stage a production write for a person to approve in /service: `deliver_review`, `deliver_wrapup`, `deliver_file`, `merge_clients`, `retract_items` or `grant_credits`. This is how an agent delivers: agent and API tokens cannot deliver or merge directly. Staging runs the write's own checks and rolls them back; the record's `preview` is what it would return, or its refusal. A `retract_items` request is checked as a preview only (see `POST /v1/admin/retractions/preview`) and is refused now (409 `staged_action_refused`, refusal `preview_changed` or `not_retractable`) unless it matches its `preview_hash` and can run. A `grant_credits` request (target `{owner_id, credits}`) adds credits to an existing identity: it is refused now (422 `invalid_staged_action`) unless `owner_id` names an identity and `credits` is a whole number from 1 to the current `credit_grant_max_credits`; its `preview.would_return` is `{owner_id, credits, balance_before, balance_after}`. Nothing changes until a person approves. Staging the same kind and target again while one is open returns the open one (200); for `grant_credits` that is one open request per `owner_id`, whatever the amount.","operationId":"stage_action","parameters":[{"description":"M4: a caller-chosen key (≤200 chars). The same caller + key + method/path/body within 24h replays the first response (header idempotent-replay: true) instead of re-running; the same key with a different request answers 422 idempotency_key_reused; a concurrent duplicate answers 409 idempotency_in_progress (retryable). Absent = ordinary behavior — including this operation's own same-kind-and-target replay.","in":"header","name":"Idempotency-Key","required":false,"schema":{"type":["string","null"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"An open request for this kind and target already exists; it is returned unchanged."},"201":{"content":{"application/json":{"schema":{}}},"description":"The staged action, status `requested`, with `summary` and `preview`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_refused, refusal, refusal_status}`: a `retract_items` request whose preview changed or cannot run, or a `grant_credits` request whose checks could not run; nothing was staged."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_staged_action, reason}`: unknown kind, bad target, empty reason; for `grant_credits`, an unknown `owner_id` or `credits` that is not a whole number from 1 to the current cap."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_actions_unavailable}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/staged-actions/grants":{"get":{"description":"Admin. Approval grants, newest first (at most 100): who wrote each, the note, when it expires, its `status` (`active`, `expired`, `revoked` or `used`) and its items (each staged action, its status, and whether an agent used it).","operationId":"list_approval_grants","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{grants: [...]}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."}},"security":[{"bearer":[]}],"tags":["admin"]},"post":{"description":"Admin, through your own browser sign-in only (email link or Google; never a token, an MCP token or a session over MCP). Let an agent approve these staged deliveries (`deliver_review`, `deliver_wrapup`, `deliver_file`; never `grant_credits` or `retract_items`). You must be allowed to approve each one yourself. The grant binds each action and what it would deliver now; it lasts 24 hours, each item is used once, and you can revoke it. An agent approves with `grant_id`, your `note` restated exactly as `grant_note`, and its own `agent_note`.","operationId":"create_approval_grant","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantReq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"The grant: `{id, granted_by, granted_via, note, created_at, expires_at, revoked_at, revoked_by, status, items: [{staged_action_id, kind, summary, action_status, used_at, used_by}]}`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required | own_request, reason}`: not an admin, not your browser sign-in, or you could not approve one of them yourself."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`: an id names no staged action."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_conflict}`: one is no longer requested."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_staged_action | not_grantable, reason}`: bad note or ids, or a kind an agent may never approve."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/staged-actions/grants/{id}/revoke":{"post":{"description":"Admin, through your own browser sign-in only. Revoke an approval grant: no agent may approve under it from now on. Items already used stay used.","operationId":"revoke_approval_grant","parameters":[{"description":"Grant id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The grant, `status: revoked`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_conflict}`: already revoked."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/staged-actions/{id}":{"get":{"description":"Admin. One staged action, plus `check_now`: its write's checks run again now and rolled back (`{ok, would_return}` or `{ok: false, status, refusal}`), so the approver sees what approving would do at this moment. `can_approve` says whether this caller, through this credential, may approve or reject it now; `approve_refusal` says why not (null when it may). `grant` is the approval grant it was approved under, else the newest live one covering it (`{id, granted_by, note, expires_at, revoked_at, used_at, used_by, usable, content_changed}`), or null; `can_grant` says whether you may write a grant for it now (your browser sign-in); `can_approve_with_grant` says whether this agent may approve it under that grant now.","operationId":"get_staged_action","parameters":[{"description":"Staged action id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The staged action with `check_now`, `can_approve` and `approve_refusal`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/staged-actions/{id}/approve":{"post":{"description":"Admin. A person approves directly: a browser sign-in, a person's session over MCP, or a person's API or MCP token (recorded as `decided_via`: `magic_link`, `google`, `mcp:<method>`, `mcp_token` or `token`). AN AGENT may approve only a delivery (`deliver_review`, `deliver_wrapup`, `deliver_file`) and only under a person's approval grant (an operator's token, `operator`, approves deliveries only and needs no grant; `merge_clients`, `retract_items` and `grant_credits` need a person's browser sign-in, 403 `person_sign_in_required`): send `grant_id`, `grant_note` (the person's note, restated exactly) and `agent_note` (what you are doing, 10 to 500 characters). The grant must be live (24 hours, not revoked), cover this action, be unused for it, and what the action would deliver must not have changed since the grant; otherwise 403 `grant_refused`. Without a grant an agent gets 403 `person_sign_in_required`; ask a person to write a grant in /service. The requester may approve its own request only under a grant. Any person admin may approve any client's request. Approve a `requested` staged action: every check of its write runs again and the write happens now, recorded as the approver's. You cannot approve an action you staged yourself (a request a token staged may be approved by that identity's own browser sign-in). If a check refuses now, nothing is written, the refusal is recorded, and the action stays `requested`. Approving `grant_credits` writes one ledger grant and one credit-grant record naming who asked and who approved; its `result` is `{owner_id, credits, balance_before, balance_after, entry_id, grant_id}`. Its checks run again first: a cap lowered since staging refuses it (409, refusal `invalid_staged_action`).","operationId":"approve_staged_action","parameters":[{"description":"Staged action id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The staged action, status `executed`, with `result`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required | own_request | grant_refused, reason}`: not an admin, an agent without a grant (`person_sign_in_required`), the requester without a grant, or a grant that does not allow it (`grant_refused`, the reason says why)."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_conflict}` (no longer requested), `{error: content_changed}` (under a grant: the content changed while approving; nothing written) or `{error: staged_action_refused, refusal, refusal_status}`: the write's own refusal."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_grantable | invalid_staged_action}`: a kind an agent may never approve, or a missing or bad `agent_note`."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/staged-actions/{id}/cancel":{"post":{"description":"Admin. Withdraw a `requested` action you staged (the same identity that staged it; a token may cancel its own). Final; nothing is written.","operationId":"cancel_staged_action","parameters":[{"description":"Staged action id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The staged action, status `cancelled`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | not_requester}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_conflict}`: no longer requested."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/staged-actions/{id}/reject":{"post":{"description":"Admin, and a person, never an agent, through any credential, under the same rule as approve (not the requester). An approval grant never lets an agent reject. Reject a `requested` staged action. Final; nothing is written.","operationId":"reject_staged_action","parameters":[{"description":"Staged action id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The staged action, status `rejected`."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin | person_sign_in_required | own_request, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: staged_action_conflict}`: no longer requested."}},"security":[{"bearer":[]}],"tags":["admin"]}},"/v1/stats/tt":{"get":{"description":"TT integrity statistics in TWO clearly-named parts, each with its own honest basis: `{yours, house}`, where each part is `{moments_total, classified, abstained, never_classified, abstain_rate, by_day, basis}`. `yours` counts moments from runs the caller started — exact, their data. `house` counts moments from the house's published news wire — exact, and safe to serve because that wire is public by construction. THERE IS NO THIRD BUCKET: no other account's activity is aggregated here at any granularity, and no figure is noised, rounded or otherwise fabricated. `abstain_rate` = abstained / **classified** — how often the reader DECLINED when it was actually asked. It is `null` when that part has nothing classified, with `basis` saying so (absence labeled, never a fabricated 0). `never_classified` is reported separately and is NOT in the denominator: a moment nobody put to the reader is not a moment the reader declined, and dividing by the total conflated the two. `by_day` holds that part's most recent ≤14 days carrying moments, chronological: `[{day, total, abstained}]`.","operationId":"tt_stats","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The statistics document."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: tt_unavailable}`."}},"security":[{"bearer":[]}],"summary":"The published integrity statistic, split into the caller's own and the\nhouse's — the same law every other run-scoped read already obeys.","tags":["tt"]}},"/v1/stats/wall":{"get":{"operationId":"wall_stats","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"{synths_monitored{yours}, sims_run{yours,house}} — every figure is split into the caller's OWN activity and the HOUSE's, and there is no third bucket: no other account's activity is aggregated here at any granularity. Both halves are exact counts, never approximated or noised. The watch counter is the caller's own only, because a watch is private and the house holds none"}},"security":[{"bearer":[]}],"summary":"BETA §10's two wall numbers, served (B6.4): Synths under monitoring and sims\nrun — the caller's own, and the house's. Queries only.","tags":["stats"]}},"/v1/trajectory":{"get":{"description":"Your Trajectory room in one read. Always 200. `gate` says whether TP_TRAJECTORY_ENABLED switched it on for this deployment; while it is off every write answers 503 `trajectory_not_enabled`. `calendar.standing` is the stored connection read without a Google round trip (`not_configured`, `not_connected`, `token_unreadable`, `scope_missing` or `connected`, with a reason) and `calendar.last_read` the state of the last trailing read (null before one). `defaults` carries 85 years, 8 hours of sleep, 07:00 to 23:00 and an 8-week window, with why 85; `categories` is the taxonomy in display order. `profile` and `sundial` are null until you start the clock; the sundial is your remaining waking hours drawn as one day, with the dial's clock reading and one sentence. `snapshot` is the latest unexpired calendar aggregate: hours and events a week per category, never an event title. `portfolio` (null until a calendar read or a slider gives it rows) projects each category over the rest of your life: share of the waking week, lifetime hours, share of remaining conscious life, minutes of the life-day, the observed weekly range and an uncalibrated value score, with unscheduled time as its own row; a week over its waking hours is scaled with a warning, never capped silently. `time_machine` is cumulative hours per row at each age up to your life expectancy. `quotes` are credits from the live price list (null when a price is not listed, never 0). `sims` and `whats_next` fill in later. Every projection is deterministic arithmetic over your own numbers, not a prediction. Private: only your own rows are read.","operationId":"get_trajectory","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The Trajectory document (private)."},"401":{"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"The Trajectory room, in one read.","tags":["trajectory"]}},"/v1/trajectory/portfolio":{"post":{"description":"Read your primary calendar's last `weeks` weeks (4 to 12; null reads your profile's window) live, classify the events, and keep the result as one aggregate snapshot: hours and events a week per category, never an event, a title or a person. Declined, free, cancelled and all-day events are set aside (counted in the snapshot's `dropped`), and double-booked minutes count once. Answers 200 `{document, calendar: {state, reason}, series, notes}`: `document` is the room as `get_trajectory` reads it, `calendar` the state of THIS read (`ok` or one of the six calendar refusals, with its reason), `series` the recurring activities found, each with the rule that chose its category (shown to you in this response only and never stored), and `notes` plain sentences on what was set aside. When the read is not `ok` nothing is stored and the document is unchanged. Needs a profile (409 `profile_required`); 422 `invalid_request` for a window outside 4 to 12; 503 `trajectory_not_enabled` while TP_TRAJECTORY_ENABLED is off.","operationId":"build_trajectory_portfolio","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortfolioReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{document, calendar, series, notes}` (private)."},"401":{"description":"Missing/unknown bearer token."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: profile_required, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: trajectory_not_enabled, reason}`."}},"security":[{"bearer":[]}],"summary":"Read the calendar's last few weeks and rebuild the portfolio from them.","tags":["trajectory"]}},"/v1/trajectory/profile":{"delete":{"description":"Forget your Trajectory: the profile, every calendar snapshot and every simulation link are deleted. Runs a simulation already admitted stay in your run history, and your calendar connection is not touched. Idempotent: `{forgotten: true, had_profile, snapshots_deleted, sims_deleted, note}`. Answers whether or not TP_TRAJECTORY_ENABLED is on: a person can always forget what was kept about them.","operationId":"forget_trajectory","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"What was forgotten."},"401":{"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"Forget Trajectory: profile, snapshots and sim links.","tags":["trajectory"]},"put":{"description":"Start or change your Trajectory profile. The first call needs `birth_date` (YYYY-MM-DD) or `birth_year` (stored as July 2 of that year, `birth_precision: year`); every other field keeps its stored value, or the default, when left out, and `ideal_hours: null` clears a saved ideal week. Refusals are 422 `{error: invalid_request, reason}`: your age must be above 0 and below `life_expectancy_years`, which is 50 to 120; sleep 0 to 16 hours; `wake` before `bed`, and `bed - wake` within two hours of the waking day your sleep leaves; `window_weeks` 4 to 12; hours 0 to 168 a week on known categories (unscheduled is what the others leave and cannot be set); at most 50 overrides of 2 to 80 characters; ratings finite. Answers the whole document, as `get_trajectory`. 503 `trajectory_not_enabled` while TP_TRAJECTORY_ENABLED is off.","operationId":"put_trajectory_profile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The Trajectory document (private)."},"401":{"description":"Missing/unknown bearer token."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: trajectory_not_enabled, reason}`."}},"security":[{"bearer":[]}],"summary":"Start or change the Trajectory profile.","tags":["trajectory"]}},"/v1/trajectory/projection":{"post":{"description":"Arithmetic over your own stored numbers (the latest calendar snapshot and your sliders, the same week the room shows), nothing read live and nothing stored. `at_age` (0 to your life expectancy) answers `cumulative`: hours per row by that age at your current pace. `ideal_hours` (hours a week per category, 0 to 168; a category left out keeps its current hours; unscheduled cannot be set) answers `compare`: per category the current and ideal hours a week and the ideal's change to lifetime hours, minutes of your life-day and share of remaining conscious life, the same for unscheduled time, an over-budget warning when the ideal week holds more than your waking hours (scaled to fit, never capped silently), and one sentence. Either part is null when not asked for. A deterministic projection, not a prediction. Needs a profile (409 `profile_required`); 422 `invalid_request` with a reason; 503 `trajectory_not_enabled` while TP_TRAJECTORY_ENABLED is off.","operationId":"project_trajectory","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectionReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{at_age, cumulative, compare}` (private)."},"401":{"description":"Missing/unknown bearer token."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: profile_required, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: trajectory_not_enabled, reason}`."}},"security":[{"bearer":[]}],"summary":"The time machine at one age, and an ideal week against the current one.","tags":["trajectory"]}},"/v1/trajectory/simulate":{"post":{"description":"Simulate where your current pattern leads, through the runs the product already has. `mode: quick` admits a `quick_sim` (the `quotes.quick` credits, held by the ledger) and answers `{run_id, read_url}`; `mode: deep` admits a `deep_sim` whose plan brings the exact quote as a decision on the run, and nothing is reserved until you confirm it; a `goal` is accepted in any mode only together with its `by` date (422 otherwise, so a deep query never drops it); `mode: walk_back` pins `goal` by `by` as an ordinary pin (`pin_outcome`; `quotes.walk_back` credits) and answers `{pin_id}`, which `record_outcome` scores later. The query is composed from your profile's numbers and category labels only, never an event title, a name or an address; it is returned as `query`. Every result is generated and uncalibrated, and says so in `label`. 409 `profile_required` before a profile; 402 when credits are short (nothing charged); 503 `trajectory_not_enabled` while off.","operationId":"simulate_trajectory","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrajectorySimulateReq"}}},"required":true},"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"`{mode, run_id | pin_id, credits_quoted, query, read_url, label}`."},"401":{"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"Not enough credits."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: profile_required}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_request, reason}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: trajectory_not_enabled, reason}`."}},"security":[{"bearer":[]}],"summary":"Start a trajectory simulation.","tags":["trajectory"]}},"/v1/tt/bundle":{"get":{"description":"The loaded TT taxonomy bundle, served VERBATIM (raw file bytes, never re-serialized) with `ETag: \"<schema> v<version>\"`. Send `If-None-Match` to get a 304 on an unchanged bundle.","operationId":"serve_bundle","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The bundle JSON, byte-for-byte the vendored file."},"304":{"description":"`If-None-Match` matched the current ETag; no body."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"500":{"content":{"application/json":{"schema":{}}},"description":"`{error: bundle_unavailable}` — the bundle file could not be read."}},"security":[{"bearer":[]}],"summary":"Serve the vendored bundle file VERBATIM (raw bytes, never re-serialized) with\n`ETag: \"<schema> v<version>\"`; `If-None-Match` hit → 304.","tags":["tt"]}},"/v1/uploads":{"post":{"description":"Accept ONE file as `multipart/form-data` and store the sanitized TEXT it contains (never the raw bytes, never raw HTML). Guardrails run in order and every one refuses typed, storing nothing: (1) a 2 MB size cap enforced WHILE STREAMING → 413 `file_too_large` naming the cap; (2) a content sniff — the BYTES decide the type, not the extension — accepting plain text, markdown, csv and html, refusing everything else 422 `unsupported_file_type` (a PDF gets its own honest reason: no PDF reader exists yet); (3) valid UTF-8 or 422 `not_utf8` (never a lossy decode); (4) sanitization — HTML is tag-stripped (scripts/styles dropped, entities decoded) and all text is scrubbed of control and invisible characters. Text longer than the stored ceiling is truncated and SAID SO. The stored text is DATA: wherever it reaches a model it rides inside a delimited untrusted-content block declared in a versioned prompt row, and the delimiters are neutralized in the text itself so they cannot be forged. Returns 201 `{upload_id, filename, kind, bytes, chars, truncated, preview, created_at}`. Reads are owner-scoped.","operationId":"create_upload","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"string"}}},"description":"multipart/form-data with one file part","required":true},"responses":{"201":{"content":{"application/json":{"schema":{}}},"description":"Stored: `{upload_id, filename, kind, bytes, chars, truncated, preview, created_at}`."},"400":{"content":{"application/json":{"schema":{}}},"description":"`{error: malformed_upload, reason}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"413":{"content":{"application/json":{"schema":{}}},"description":"`{error: file_too_large, reason, max_bytes}` — the cap is named and nothing was stored."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_file | unsupported_file_type | not_utf8 | empty_document, reason, accepted}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: uploads_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Accept one file, sanitize it, store the text.","tags":["uploads"]}},"/v1/uploads/{id}/entities":{"get":{"description":"The people, organizations and markets a stored reading found in the document, as PROPOSALS: `{upload_id, filename, written: false, proposals: [{index, name, kind, role, slug, entity_id, path, matches_existing, would_mint}]}`. This call is read-only — it resolves each name against the registry to say whether approving it would match an existing entity or mint a new one, and writes NOTHING. Approving is a separate POST to the same path carrying the indexes the human chose. Owner-scoped; 409 `no_extract` until the document has been read.","operationId":"list_entity_proposals","parameters":[{"description":"Upload id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The proposals; `written` is always false."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown upload, or one that is not yours."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_extract}` — read the document first."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: uploads_unavailable}`."}},"security":[{"bearer":[]}],"summary":"The proposals — nothing is written.","tags":["uploads"]},"post":{"description":"Write ONLY the proposals a human approved. The body names the proposal indexes (`{approve: [0, 2]}`) — an absent or empty list is a typed refusal, never an implicit approve-all, and an index outside the proposal list is refused before anything is written. Each approved name goes through `entities::resolve_or_mint` (the registry's one minter) and then through `entities::commit_assertions` (the ONE writer of assertions — the gate is code and this path goes through it, never around it). Each written row states its own provenance in the column named for it: basis GROUNDED (a person approved a real, named document), source `document_upload <upload id> :: <filename>, approved by <account>`, and no run id, because an upload is not a run. The value carries the claim alone — the role the document puts the actor in. Returns `{upload_id, approved, entities: [...], assertions: {committed, skipped}, provenance}`.","operationId":"approve_entities","parameters":[{"description":"Upload id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"What was written."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown upload, or one that is not yours."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_extract}` — read the document first."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_approval | entity_refused, reason, written: false}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: uploads_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Write the approved subset — resolve-or-mint, then one assertion each.","tags":["uploads"]}},"/v1/uploads/{id}/extract":{"post":{"description":"Read a stored document into a simulatable situation — the SAME structured extraction the link drop runs (`storysim::run_extract`), over a versioned prompt row that wraps the document in a delimited UNTRUSTED CONTENT block and states that the text is data, never instructions. Returns `{upload_id, filename, title, summary, scenario, cast: [{name, kind, role}], themes, model, tier, already}`. Charge-once: a document already read serves the stored reading with `already: true` and spends nothing. When the model cannot produce a valid reading the refusal is typed (422 `extract_invalid`) and NOTHING is written. Owner-scoped: an unknown upload and somebody else's upload are the same 404.","operationId":"extract_upload","parameters":[{"description":"Upload id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"The reading."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"402":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_no_headroom, gate}` — nothing was spent."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown upload, or one that is not yours — the same typed `{error: not_found}` either way."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: extract_invalid, reason, written: false}`."},"502":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_failed, reason, written: false}`."},"503":{"content":{"application/json":{"schema":{}}},"description":"`{error: llm_unconfigured | uploads_unavailable}`."}},"security":[{"bearer":[]}],"summary":"Read one stored document into a simulatable situation.","tags":["uploads"]}},"/v1/verdicts":{"post":{"description":"Assert a VERDICT about one of the caller's own moments — what the evidence currently supports, as an append-only assessment EVENT (never a column update). `verdict` is one of `supported | unsupported | contradicted`; `note` (required) carries the evidence in words; `supersedes` optionally names the prior verdict this one replaces in the view — the prior row persists. The response records the taxonomy release the assessment was made under, and `filter_version: null` labeled as 'assessed before filters existed' — the field is not accepted as input because no filter is deployed to cite.","operationId":"post_verdict","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerdictBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{verdict_id, moment_id, verdict, bundle_version, filter_version: null, filter_basis, asserted_at}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no such moment in the caller's account (absence and non-ownership answer alike)."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: bad_supersedes}` — the named verdict does not exist or is about a different moment."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_verdict}` — not one of the three standings, or an empty note."}},"security":[{"bearer":[]}],"tags":["tt"]}},"/v1/verdicts/{moment_id}":{"get":{"description":"What does the graph currently support for this moment? The CURRENT verdict (newest assessment) plus the full history of what was believed when, newest first — each row `{id, verdict, filter_version, bundle_version, note, asserted_by, asserted_at, supersedes}`. `current` is `null` for a moment that exists but has never been assessed — an absence, labeled in `basis`, never an implied standing. Readable for the caller's own moments and the house's.","operationId":"get_verdicts","parameters":[{"description":"The moment's id","in":"path","name":"moment_id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{moment_id, current, count, history, basis}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_found}` — no such moment among what the caller can read."}},"security":[{"bearer":[]}],"tags":["tt"]}},"/v1/workspaces":{"get":{"description":"Admin only. Every workspace, newest first: id, display name, creator and member count.","operationId":"list_workspaces","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{id, display_name, created_by, created_at, member_count}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only (W3, gate G7). Create a workspace; the creator is inserted as its `admin` member so it is never created with no one able to administer it.","operationId":"create_workspace","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{id, display_name}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/workspaces/{id}/members":{"get":{"description":"Admin only. A workspace's members: name, role, who added them and when.","operationId":"list_workspace_members","parameters":[{"description":"Workspace id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{items: [{name, role, added_by, added_at}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_workspace}`."}},"security":[{"bearer":[]}],"tags":["client_services"]},"post":{"description":"Admin only. Add an identity to a workspace with a role (`provider`, `client` or `admin`). ZERO CONTACT (PLAN.md policy 1): this never sends mail — adding a member is silent by construction. A `client` role is exclusive: that identity may hold no other workspace membership (the 0055 trigger enforces it), refused as `workspace_client_exclusive`. The identity must already have a token minted (`provision_client` for a client, or an existing account for a provider/admin).","operationId":"add_workspace_member","parameters":[{"description":"Workspace id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{workspace_id, name, role}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: no_workspace | no_identity}`."},"409":{"content":{"application/json":{"schema":{}}},"description":"`{error: already_member | workspace_client_exclusive, reason}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`{error: invalid_client_service_input | bad_role, reason}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/workspaces/{id}/members/remove":{"post":{"description":"Admin only. Remove a member from a workspace. ZERO CONTACT (PLAN.md policy 1): silent, no mail.","operationId":"remove_workspace_member","parameters":[{"description":"Workspace id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveMemberReq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{workspace_id, name, removed: true}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"403":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_admin, reason}`."},"404":{"content":{"application/json":{"schema":{}}},"description":"`{error: not_member}`."}},"security":[{"bearer":[]}],"tags":["client_services"]}},"/v1/world-model":{"get":{"operationId":"get_world_model","parameters":[{"description":"entities (default), facts, or relationships; iterate each to inventory the entire model.","in":"query","name":"part","required":false,"schema":{"type":"string"}},{"in":"query","name":"kind","required":false,"schema":{"type":"string"}},{"in":"query","name":"q","required":false,"schema":{"type":"string"}},{"in":"query","name":"cursor","required":false,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"limit","required":false,"schema":{"minimum":0,"type":"integer"}},{"in":"query","name":"updated_since","required":false,"schema":{"format":"date-time","type":"string"}},{"description":"in or all; client projections always use in. Only explicit scope decisions omit research.","in":"query","name":"engagement_scope","required":false,"schema":{"type":"string"}},{"in":"query","name":"include_generated","required":false,"schema":{"type":"boolean"}},{"description":"Shared workspace. MCP client and team keys inject their bound workspace.","in":"header","name":"x-timepoint-workspace","required":false,"schema":{"format":"uuid","type":["string","null"]}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"{manifest,part,items,entities,facts,relationships,total,next_cursor}. Iterate part=entities, facts and relationships independently using next_cursor until null; without filters this inventories every visible record, not a name-search subset. Entity pages also include their visible facts and relationships. Every fact carries basis, knowledge (sourced/client_statement/simulation/hypothesis; client_statement is a fact the client supplied about itself, never independently sourced), source_urls, observed_at, confidence and run_id. Pending proposals are never facts. Client readers omit undelivered run traces and internal research jobs. as_of describes this live read, not an immutable snapshot."},"404":{"description":"workspace_not_found"},"422":{"description":"invalid_export_request"}},"security":[{"bearer":[]}],"tags":["entities"]}},"/v1/wrapups":{"get":{"description":"The engagement wrap-up reports delivered to the caller, newest first: wrap-up id, engagement id, the engagement's question, the delivered version, its content hash, delivery time and how many simulations it rolls up. A wrap-up is listed once it has passed QA and been delivered, to the engagement's client or a member of the workspace the engagement is linked to. Open one with `get_delivered_wrapup` (structured) or `get_wrapup_report` (readable).","operationId":"list_delivered_wrapups","responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{results: [{wrapup_id, engagement_id, question, version, snapshot_hash, delivered_at, delivered_runs}]}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."}},"security":[{"bearer":[]}],"summary":"`GET /v1/wrapups` — the wrap-ups delivered to the caller.","tags":["client_services"]}},"/v1/wrapups/{id}":{"get":{"description":"One delivered engagement wrap-up as structured data: the delivered version's snapshot (`wrapup/0.1`) — the engagement, a summary, each delivered simulation's answer (outcomes with uncalibrated probabilities, the highest-leverage action, uncertainty, key moments; the full run opens with `get_run_report`), the research behind it (research jobs, approved record counts, current decision-makers with a re-verify flag) and provenance. Unknown and not-yours are the same 404.","operationId":"get_delivered_wrapup","parameters":[{"description":"wrap-up id (from `list_delivered_wrapups`)","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"`{wrapup_id, engagement_id, version, snapshot_hash, composed_at, delivered_at, snapshot}`."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown wrap-up, not delivered, or not yours — `{error: not_found}`."}},"security":[{"bearer":[]}],"summary":"`GET /v1/wrapups/{id}` — one delivered wrap-up, structured.","tags":["client_services"]}},"/v1/wrapups/{id}/report":{"get":{"description":"One delivered engagement wrap-up as a readable document: `format=html` (default; a self-contained page whose print stylesheet makes print-to-PDF the PDF export) or `format=md` (markdown, best for an agent). Same content and the same read rule as `get_delivered_wrapup`.","operationId":"get_wrapup_report","parameters":[{"description":"wrap-up id","in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"description":"`html` (default; print it to save a PDF) or `md`.","in":"query","name":"format","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"text/html":{}},"description":"The report."},"401":{"content":{"application/json":{"schema":{}}},"description":"Missing/unknown bearer token."},"404":{"content":{"application/json":{"schema":{}}},"description":"Unknown wrap-up, not delivered, or not yours — `{error: not_found}`."},"422":{"content":{"application/json":{"schema":{}}},"description":"`format` is not html or md."}},"security":[{"bearer":[]}],"summary":"`GET /v1/wrapups/{id}/report` — one delivered wrap-up, readable.","tags":["client_services"]}}},"security":[{"bearer":[]}],"servers":[{"description":"Production","url":"https://app.timepointai.com"}]}