{
  "components": {
    "schemas": {
      "ApiKeyRecord": {
        "additionalProperties": true,
        "description": "\u26a0\ufe0f THE PLAINTEXT KEY IS NEVER RETURNED HERE OR ANYWHERE. Only its sha256 is stored, and the\nstore never held the plaintext after mint - there is no recovery path, by construction. Same\nfor `webhook_secret`: returned ONCE at mint and never listed. Their absence is a guarantee, not\nan omission, which is why it is written down.",
        "properties": {
          "created_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At"
          },
          "created_by": {
            "default": "",
            "title": "Created By",
            "type": "string"
          },
          "credit_state": {
            "default": "unknown",
            "description": "\"funded\" (credit_usd is a number this key may spend) | \"none\" (no credit was ever reserved \u2014 the key cannot open a session; reserve some and it will) | \"unknown\" (the wallet could not be read \u2014 this is NOT a claim that the key has no credit, and nothing should be reserved on the strength of it). \n\n\ud83d\udd34 DEFAULTS TO \"unknown\", DELIBERATELY. The safe default for a field that gates a money decision is \"we do not know\", not the most common healthy value: a caller that forgets to populate it then shows an honest blank instead of a confident wrong answer.",
            "title": "Credit State",
            "type": "string"
          },
          "credit_usd": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "CREDIT RESERVED TO THIS KEY, and after #1876 the only pot a session of this key can spend \u2014 so this number alone decides whether it can run. \n\n\ud83d\udd34 null is NOT zero and NOT a single state: it means EITHER no credit was ever reserved here OR the wallet could not be read. `credit_state` tells you which; branch on that, never on this being null. `0.0` is a real number and means the key WAS funded and has run dry \u2014 refill it (#1837).",
            "title": "Credit Usd"
          },
          "grants": {
            "additionalProperties": {
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "description": "WHAT THIS KEY MAY DO, as {workspace: [verbs]} \u2014 the axis that gates `keys.mint` and `credit.reserve`, i.e. whether this credential can create other credentials and move money. Always DERIVED, never the raw column: a key minted before the matrix existed reports the authority it actually HAS rather than a null.\n\n\ud83d\udd34 `{}` MEANS DELIBERATELY NOTHING, AND THE FIELD IS NEVER ABSENT. #1957 shipped because the Postgres listing omitted it entirely while the in-memory one carried it \u2014 so production published `scopes` (the weaker axis) and silently dropped this one, and an ordinary reader doing `.get(\"grants\", [])` turned that absence into a confident 'holds nothing' about a key that could mint keys and move money. An absent authority field is worse than a wrong one: it reads as the reassuring answer.",
            "title": "Grants",
            "type": "object"
          },
          "key_id": {
            "description": "Handle for DELETE /v1/keys/{key_id}.",
            "title": "Key Id",
            "type": "string"
          },
          "label": {
            "default": "",
            "title": "Label",
            "type": "string"
          },
          "last_used_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Used At"
          },
          "money_mover_by": {
            "default": "",
            "description": "WHO ATTESTED THAT THIS KEY MAY MOVE MONEY, or \"\" for nobody (#4112). Holding `credit.reserve` in `grants` is no longer enough to call POST /v1/workspaces/*/keys/*/credit: the door reads this field too, and a key holding the verb with nothing here is refused.\n\n\ud83d\udd11 TWO FIELDS, DELIBERATELY. `credit.reserve` says a key CAN be a money-mover; this says a PERSON DECIDED it should be. Before #4112 only the first existed and was checked at mint alone, so a key that acquired the verb by drift or by a console click was indistinguishable at the door from one somebody meant to create. Keys carrying the verb when #4112 shipped read `grandfathered:4112` \u2014 that is a record of what was already live, NOT an attestation, and each one still owes a decision.\n\nSet it with POST /v1/keys/{key_id}/money-mover, withdraw it with DELETE. Both are human-only: an API key cannot attest, however privileged, because a key that could would be two calls away from authorising itself.",
            "title": "Money Mover By",
            "type": "string"
          },
          "prefix": {
            "description": "Non-secret leading characters, for matching a key you hold.",
            "title": "Prefix",
            "type": "string"
          },
          "revoked": {
            "default": false,
            "title": "Revoked",
            "type": "boolean"
          },
          "scopes": {
            "description": "#1625 opt-in powers this key holds: 'governance:author' (save/retire charters, register packs) and/or 'session:facts' (attest substrate facts on its own workspace's sessions, whoever writes the record). Visible here because an opt-in power you cannot see is one nobody audits.\n\n\ud83d\udd34 SCOPES SAY NOTHING ABOUT MONEY OR ABOUT ACCOUNT AUTHORITY, and `[]` does not mean the key is use-only. This axis is what a key may do to the RULES and the RECORD; `grants` is what it may do to the ACCOUNT, and a key can be empty here while holding `credit.reserve` there. Neither column describes a key on its own. The question 'may this key move money' is answered by `grants` together with `money_mover_by`, and never by this field.",
            "items": {
              "type": "string"
            },
            "title": "Scopes",
            "type": "array"
          },
          "webhook_disabled": {
            "default": false,
            "description": "TRUE after 20 consecutive delivery failures. THIS IS WHERE YOU DISCOVER YOUR RECEIVER HAS BEEN DEAD \u2014 nothing else surfaces it.",
            "title": "Webhook Disabled",
            "type": "boolean"
          },
          "webhook_failures": {
            "default": 0,
            "description": "Consecutive failures. Resets to 0 on any success.",
            "title": "Webhook Failures",
            "type": "integer"
          },
          "webhook_url": {
            "default": "",
            "title": "Webhook Url",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "key_id",
          "prefix",
          "workspace"
        ],
        "title": "ApiKeyRecord",
        "type": "object"
      },
      "ArtifactResponse": {
        "description": "What a chartered session BUILT, forwarded from the charter engine VERBATIM.\n\nThe inner shape is deliberately NOT re-declared field by field here. The charter artifact door owns\nit - charter order, labels, the seal, per-field provenance - and a second declaration in Cohort\nwould be a copy that drifts, which is exactly how a published contract starts lying. What this\nmodel pins is the CONTRACT COHORT MAKES: an ordered `fields` list, and enough top-level state\nfor a reader to know whether they are holding a finished thing or a draft.",
        "example": {
          "carried_exit_set": "es_born",
          "charter_digest": "k2:2ae401b2",
          "closing_statement": "Assistant generated successfully.",
          "disposition": "complete",
          "exit_status": "satisfied",
          "fields": [
            {
              "anchored": "ok",
              "label": "Name",
              "name": "name",
              "value": "Alan Walkings",
              "writer": "doer",
              "written": true
            }
          ],
          "seal_id": "5002703e03a9f889",
          "sealed": true,
          "sections": [
            {
              "fields": [
                "name"
              ],
              "name": "core"
            }
          ]
        },
        "properties": {
          "carried_exit_set": {
            "default": "",
            "description": "The ID of the non-optional exit set that carried the complete seal \u2014 the set whose closing_statement is above. A consumer branches on WHICH set finished the charter from this, rather than parsing the prose closing line. Empty when unsealed or when no non-optional set carried \u2014 unknown is not a fact, the same posture as closing_statement.",
            "title": "Carried Exit Set",
            "type": "string"
          },
          "chain_seq": {
            "default": 0,
            "description": "Chain position this artifact was read at.",
            "title": "Chain Seq",
            "type": "integer"
          },
          "charter_digest": {
            "default": "",
            "description": "The charter this artifact was built to.",
            "title": "Charter Digest",
            "type": "string"
          },
          "closing_statement": {
            "default": "",
            "description": "The authored closing line of the exit set that carried a complete seal.",
            "title": "Closing Statement",
            "type": "string"
          },
          "disposition": {
            "default": "",
            "description": "complete | partial | bypassed when sealed.",
            "title": "Disposition",
            "type": "string"
          },
          "exit_status": {
            "default": "",
            "description": "The charter's grade of the goal.",
            "title": "Exit Status",
            "type": "string"
          },
          "fields": {
            "description": "Every declared field with its value and PROVENANCE \u2014 `anchored: ok` means the value was verified against something the subject actually said. A subject-register field reports `withheld: true` rather than a blank: recorded, never stored.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Fields",
            "type": "array"
          },
          "outcome": {
            "default": "",
            "description": "The carrying exit set's OWN name for the finish that happened. A charter may declare that an outcome finishes over unknowns of a stated cause and carries them as findings; when it does, it names both ways of reaching it and this says which one occurred. Empty on a charter that declares no such tolerance, which has one way to be reached and needs no name to tell them apart.",
            "title": "Outcome",
            "type": "string"
          },
          "seal_id": {
            "default": "",
            "description": "Addresses the sealed chain for audit.",
            "title": "Seal Id",
            "type": "string"
          },
          "sealed": {
            "default": false,
            "description": "False means this is a DRAFT, not a product.",
            "title": "Sealed",
            "type": "boolean"
          },
          "sections": {
            "description": "The charter's own grouping, in declaration order.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Sections",
            "type": "array"
          },
          "tolerated": {
            "description": "What this record FINISHED OVER: each criterion whose unknown the carrying set declared it tolerates, with its cause, the sentence, and which set let it through. `disposition` stays `complete` for a finish with findings - the work IS finished - so this and `outcome` are how a reader of the record alone tells the two apart. Empty on a clean finish, and never populated without `outcome`.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Tolerated",
            "type": "array"
          }
        },
        "title": "ArtifactResponse",
        "type": "object"
      },
      "AuditList": {
        "additionalProperties": true,
        "description": "The account's management audit trail, newest first. (#1191 \u00a77)\n\n`actor` is the acting CREDENTIAL - `key:<key_id>` for a management key, a user id for a human -\nnot a display name. The question this answers is \"what do I revoke\", and a name does not survive\nthat question.",
        "example": {
          "count": 2,
          "entries": [
            {
              "action": "usage_key.minted",
              "actor": "key:key-9b2e44c1a077",
              "at": "2026-08-18T21:04:11Z",
              "detail": "workspace=acme-corp label='acme prod'",
              "org_id": "org-4a1f9c",
              "target": "key-3c7f10ab92de"
            },
            {
              "action": "workspace.created",
              "actor": "key:key-9b2e44c1a077",
              "at": "2026-08-18T21:04:10Z",
              "detail": "",
              "org_id": "org-4a1f9c",
              "target": "acme-corp"
            }
          ],
          "org_id": "org-4a1f9c"
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "entries": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Entries",
            "type": "array"
          },
          "org_id": {
            "title": "Org Id",
            "type": "string"
          }
        },
        "required": [
          "org_id",
          "count"
        ],
        "title": "AuditList",
        "type": "object"
      },
      "Balance": {
        "additionalProperties": true,
        "description": "dx-1: the caller's OWN budget row - granted, used, remaining. The number a new account\ncould never see before: /v1/budgets lists only users with usage rows, so a fresh account\nread as \"no budget\" from the only surface it could reach, with no way to tell zero-granted\nfrom not-yet-provisioned.",
        "example": {
          "budgets": [
            {
              "gpu_seconds_hard_cap": 1800,
              "gpu_seconds_used": 420.5,
              "projex_user_id": "usr-91b3",
              "usd_used": 1.05
            }
          ],
          "count": 1,
          "platform": "acme"
        },
        "properties": {
          "balance": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "Your ACCOUNT's quota row. `balance_usd` is the money you have left to APPOINT to a workspace \u2014 it is NOT what decides whether a session may run. Since #1876 a session spends the credit reserved to ONE API KEY, and the account rung is not reachable by a session at all: money travels account -> workspace -> key by explicit act, and only the key's wallet is spendable. So a healthy figure here says nothing about whether any work can start. \n\n\ud83d\udd34 THE FIGURE THAT DECIDES A SPEND is `credit_usd` on GET /v1/workspaces/{slug}/keys, per key. This description previously read 'the money that decides whether you may spend' \u2014 true before #1876, false after, and exactly the kind of superseded claim that gets printed on a customer's screen. \n\n`granted`/`used`/`remaining` are gpu-seconds: a METER, not a ceiling. null = no row provisioned yet \u2014 distinct from granted=0, which is a real (empty) wallet.",
            "title": "Balance"
          }
        },
        "title": "Balance",
        "type": "object"
      },
      "BalanceMovement": {
        "description": "One movement of the wallet, AS THE CUSTOMER SEES IT. (#1736, signed in #1738)\n\n\ud83d\udd34 THE ABSENCES ARE THE POINT. A manual grant or refund carries an operator note and the admin\nwho made it; neither appears here and neither may ever be added. They live in separate columns\nand both store twins serialise through quota/wallet.py's allowlist, so this model is a SECOND\nguard rather than the only one. `kind` says WHAT happened to the money, which is what a\ncustomer needs to reconcile a balance they did not move themselves.\n\n\u26a0\ufe0f `amount_usd` IS SIGNED. It was credits-only until refunds existed; a refund and a downward\ncorrection are negative. A customer whose balance fell must be able to see why, so the sign is\nload-bearing rather than cosmetic - read it, do not assume a magnitude.",
        "properties": {
          "amount_usd": {
            "default": 0.0,
            "description": "SIGNED: positive added, negative taken back",
            "title": "Amount Usd",
            "type": "number"
          },
          "created_at": {
            "default": "",
            "title": "Created At",
            "type": "string"
          },
          "kind": {
            "default": "",
            "description": "purchase | grant | refund | adjustment",
            "title": "Kind",
            "type": "string"
          }
        },
        "title": "BalanceMovement",
        "type": "object"
      },
      "BalanceMovementList": {
        "additionalProperties": true,
        "description": "EVERY MOVEMENT EXCEPT USAGE - purchases, grants, refunds and corrections, signed. Spending\nis itemised at /v1/usage and /v1/sessions/{id}/usage; a second view of the same debits is how\ntwo numbers for one fact start disagreeing. So this answers \"what did you do to my balance\"\nand the usage feed answers \"what did I spend\".",
        "example": {
          "count": 3,
          "movements": [
            {
              "amount_usd": -25.0,
              "created_at": "2026-09-05T09:31:02Z",
              "kind": "refund"
            },
            {
              "amount_usd": 100.0,
              "created_at": "2026-09-05T06:10:57Z",
              "kind": "purchase"
            },
            {
              "amount_usd": 50.0,
              "created_at": "2026-09-04T11:02:13Z",
              "kind": "grant"
            }
          ]
        },
        "properties": {
          "count": {
            "default": 0,
            "title": "Count",
            "type": "integer"
          },
          "movements": {
            "items": {
              "$ref": "#/components/schemas/BalanceMovement"
            },
            "title": "Movements",
            "type": "array"
          }
        },
        "title": "BalanceMovementList",
        "type": "object"
      },
      "BudgetList": {
        "additionalProperties": true,
        "example": {
          "budgets": [
            {
              "gpu_seconds_hard_cap": 1800,
              "gpu_seconds_used": 420.5,
              "projex_user_id": "usr-91b3",
              "usd_used": 1.05
            }
          ],
          "count": 1,
          "platform": "acme"
        },
        "properties": {
          "budgets": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Budgets",
            "type": "array"
          },
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          }
        },
        "required": [
          "platform",
          "count"
        ],
        "title": "BudgetList",
        "type": "object"
      },
      "BudgetUpdateRequest": {
        "additionalProperties": false,
        "description": "PATCH /v1/sessions/{id}/budget body. W4 mid-session cap change (raise or lower).",
        "properties": {
          "max_cost_usd": {
            "description": "The session's NEW declared dollar ceiling, replacing whatever it was created with (or last set to). A LOWER value is refused if it is already below what this session has recorded as spent \u2014 it cannot claw back money already spent. A HIGHER value takes effect on a running worker's very next request when one is live right now; see `applied_live` in the response.",
            "minimum": 0.0,
            "title": "Max Cost Usd",
            "type": "number"
          }
        },
        "required": [
          "max_cost_usd"
        ],
        "title": "BudgetUpdateRequest",
        "type": "object"
      },
      "BudgetUpdateResponse": {
        "example": {
          "accepted": true,
          "applied_live": true,
          "granted_usd": 5.0,
          "session_id": "api-1a2b3c4d"
        },
        "properties": {
          "accepted": {
            "description": "False = refused (see the 4xx instead \u2014 this field only appears on 200).",
            "title": "Accepted",
            "type": "boolean"
          },
          "applied_live": {
            "description": "True: a running worker's gateway key was updated in place, effective immediately. False: nothing is live right now (between respawns, or worker never connected) \u2014 the new cap is durably recorded and will apply correctly the next time a worker mints a key for this session; nothing enforces it until then because nothing is running.",
            "title": "Applied Live",
            "type": "boolean"
          },
          "granted_usd": {
            "title": "Granted Usd",
            "type": "number"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "accepted",
          "applied_live",
          "granted_usd"
        ],
        "title": "BudgetUpdateResponse",
        "type": "object"
      },
      "CharterBody": {
        "additionalProperties": true,
        "description": "A charter fetched back - from the one body store.",
        "example": {
          "charter": {
            "acceptance": [
              "\u2026"
            ],
            "title": "Premarital screening report"
          },
          "digest": "sha256:2db80b1c9f4e",
          "workspace": "acme"
        },
        "properties": {
          "charter": {
            "additionalProperties": true,
            "title": "Charter",
            "type": "object"
          },
          "digest": {
            "title": "Digest",
            "type": "string"
          },
          "note": {
            "default": "",
            "title": "Note",
            "type": "string"
          },
          "supersedes": {
            "default": "",
            "title": "Supersedes",
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "title": "Tags",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "digest",
          "workspace"
        ],
        "title": "CharterBody",
        "type": "object"
      },
      "CharterChanges": {
        "additionalProperties": true,
        "description": "#1427 AC13 - the structured diff from a version's ancestor to it.\n\nCOMPUTED ON READ, never stored: a recorded diff would be a third copy of the truth (two bodies\nplus a summary of their difference) and the copy is the one that goes stale.\n\nKEYED ON IDENTITY, NEVER ARRAY INDEX - fields by `name`, criteria and exit sets by `id`. An\nindex-keyed diff reports spurious changes the moment an author reorders a list, which is the\nmost common harmless edit there is, and a changelog that cries wolf on reorders is one nobody\nreads. It is also GENERIC over values: it reports THAT a param changed, never what the change\nmeans, so a new criterion kind needs no change here.",
        "example": {
          "changes": {
            "criteria_added": [],
            "criteria_changed": [
              {
                "changes": [
                  {
                    "from": "warn",
                    "key": "on_violation",
                    "to": "refuse"
                  }
                ],
                "id": "identity_present"
              }
            ],
            "criteria_removed": [],
            "exit_sets_added": [],
            "exit_sets_changed": [],
            "exit_sets_removed": [],
            "fields_added": [],
            "fields_changed": [],
            "fields_removed": [],
            "instruction_changed": []
          },
          "digest": "k2:9f4e2db80b1c",
          "from_digest": "k2:2db80b1c9f4e",
          "workspace": "acme"
        },
        "properties": {
          "changes": {
            "additionalProperties": true,
            "title": "Changes",
            "type": "object"
          },
          "digest": {
            "title": "Digest",
            "type": "string"
          },
          "from_digest": {
            "default": "",
            "title": "From Digest",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "digest",
          "workspace"
        ],
        "title": "CharterChanges",
        "type": "object"
      },
      "CharterDisciplines": {
        "additionalProperties": true,
        "description": "The write disciplines a field may declare (#4201): what it HOLDS and what a second write\ndoes, with three facts an authoring surface generates its controls from: `register` (the\nfield carries a register), `elements` (a set of elements with typed parts: declares `elements`\nand `element_key`, written one part of one element at a time with `element` + `sub`, an\nelement withdrawn with `remove`), `element_part` (the discipline may be a part's own). Served\nby the engine from the same table its validator accepts disciplines from, and NOT reshaped.",
        "example": {
          "disciplines": [
            {
              "element_part": false,
              "elements": true,
              "name": "collection",
              "register": false,
              "semantics": "a set of elements, each with an identity of its own and typed parts"
            }
          ]
        },
        "properties": {
          "disciplines": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Disciplines",
            "type": "array"
          }
        },
        "title": "CharterDisciplines",
        "type": "object"
      },
      "CharterDocument": {
        "additionalProperties": true,
        "description": "A charter document. Free-form except for what #4201 publishes: the `fields` list is\ndescribed (discipline enum, the collection's `elements` and `element_key`) and everything else\nis forwarded untouched, because the schema belongs to the engine.",
        "example": {
          "criteria": [
            {
              "field": "lines",
              "id": "c_role",
              "kind": "enum_membership",
              "on_violation": "refuse",
              "params": {
                "sub": "role",
                "values": "principal,secondary"
              }
            },
            {
              "field": "lines",
              "id": "c_count",
              "kind": "element_count",
              "params": {
                "min": "1"
              }
            }
          ],
          "exit_sets": [
            {
              "all": [
                "c_role",
                "c_count"
              ],
              "id": "done"
            }
          ],
          "fields": [
            {
              "criteria": [
                "c_role",
                "c_count"
              ],
              "discipline": "collection",
              "element_key": [
                "code"
              ],
              "elements": [
                {
                  "name": "code",
                  "required": true
                },
                {
                  "name": "role",
                  "required": true
                }
              ],
              "name": "lines"
            }
          ],
          "name": "line-items",
          "version": "v1"
        },
        "properties": {
          "fields": {
            "description": "The charter's fields. Each carries its discipline from the engine's closed set and, for a collection, its `elements` and `element_key`. Read the live vocabularies for the rest: /v1/charter-disciplines, /v1/charter-types, /v1/charter-kinds.",
            "items": {
              "$ref": "#/components/schemas/CharterField"
            },
            "title": "Fields",
            "type": "array"
          }
        },
        "title": "CharterDocument",
        "type": "object"
      },
      "CharterElementField": {
        "additionalProperties": true,
        "description": "One PART of a collection's element (#4201): what every element of the set is made of.",
        "example": {
          "discipline": "once",
          "name": "code",
          "required": true
        },
        "properties": {
          "description": {
            "default": "",
            "description": "Shown to the agent as the part's meaning.",
            "title": "Description",
            "type": "string"
          },
          "discipline": {
            "default": "once",
            "description": "The part's write discipline. The closed set is the engine's: narrative, once. Default once (settled by its first write); narrative is free text.",
            "enum": [
              "narrative",
              "once"
            ],
            "title": "Discipline",
            "type": "string"
          },
          "name": {
            "default": "",
            "description": "The part's name; a criterion's `sub` names it.",
            "title": "Name",
            "type": "string"
          },
          "required": {
            "default": false,
            "description": "A required part must be present before the element counts as complete; a per-element criterion grades an element only once it is complete, so a part a criterion grades should be required.",
            "title": "Required",
            "type": "boolean"
          }
        },
        "title": "CharterElementField",
        "type": "object"
      },
      "CharterField": {
        "additionalProperties": true,
        "description": "One field of a charter, as much of its shape as Cohort PUBLISHES (#4201). The field's full\nvocabulary belongs to the engine and is read live from `/v1/charter-types`,\n`/v1/charter-kinds` and `/v1/charter-disciplines`; what is declared here is the closed set of\ndisciplines and the two keys a collection adds, rendered from the engine's own contract so\nthis document cannot name a discipline the engine refuses or omit one it accepts. Every other\nkey is forwarded untouched.",
        "example": {
          "criteria": [
            "c_role",
            "c_count"
          ],
          "discipline": "collection",
          "element_key": [
            "code"
          ],
          "elements": [
            {
              "name": "code",
              "required": true
            },
            {
              "name": "role",
              "required": true
            },
            {
              "discipline": "narrative",
              "name": "note"
            }
          ],
          "name": "lines"
        },
        "properties": {
          "discipline": {
            "default": "",
            "description": "The field's write discipline: what it HOLDS and what a second write does. The closed set is the engine's: collection, computed, mono, narrative, once, slot. One write discipline (#4201): what a field written under it HOLDS and what a second write does. `register` says the field carries a register; `elements` says the field is a set of elements with typed parts (it declares elements and element_key, is written one part of one element at a time with element + sub, and an element is withdrawn with remove); `element_part` says the discipline may be a part's own. The closed set: collection, computed, mono, narrative, once, slot.",
            "enum": [
              "collection",
              "computed",
              "mono",
              "narrative",
              "once",
              "slot"
            ],
            "title": "Discipline",
            "type": "string"
          },
          "element_key": {
            "description": "COLLECTION ONLY: the parts that identify an element. Two elements with the same key are one and the second is refused (submit.duplicate_by_key); without a key the element's own id is its identity.",
            "items": {
              "type": "string"
            },
            "title": "Element Key",
            "type": "array"
          },
          "elements": {
            "description": "COLLECTION ONLY: the parts every element of the set is made of. A write addresses one part of one element with `element` (the writer's own id for the element) and `sub` (the part); `remove` withdraws an element. A criterion that takes `sub` grades that part of every element.",
            "items": {
              "$ref": "#/components/schemas/CharterElementField"
            },
            "title": "Elements",
            "type": "array"
          },
          "name": {
            "default": "",
            "description": "The field's name, as the agent addresses it.",
            "title": "Name",
            "type": "string"
          }
        },
        "title": "CharterField",
        "type": "object"
      },
      "CharterHistory": {
        "additionalProperties": true,
        "description": "#1427 AC12 - the ordered list of versions saved under one charter NAME.\n\n\"the user gets a digest and edit timestamp. and thats the history of the chart.\" (ruling 4)\nThere is no manual version number in this answer by design: `version` is free text carried\noff the body for display, never an address. Pin a DIGEST.",
        "example": {
          "count": 2,
          "name": "assistant",
          "versions": [
            {
              "comment": "refuse on violation rather than warn",
              "digest": "k2:9f4e2db80b1c",
              "retired": false,
              "saved_at": "2026-08-27T11:02:41Z",
              "supersedes": "k2:2db80b1c9f4e",
              "version": "v2"
            },
            {
              "comment": "",
              "digest": "k2:2db80b1c9f4e",
              "retired": false,
              "saved_at": "2026-08-24T08:17:03Z",
              "supersedes": "",
              "version": "v1"
            }
          ],
          "workspace": "acme"
        },
        "properties": {
          "count": {
            "default": 0,
            "title": "Count",
            "type": "integer"
          },
          "name": {
            "title": "Name",
            "type": "string"
          },
          "versions": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Versions",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "name",
          "workspace"
        ],
        "title": "CharterHistory",
        "type": "object"
      },
      "CharterKinds": {
        "additionalProperties": true,
        "description": "The criterion kinds a charter may enforce, and how each behaves at the door (#1640).\n\nThe other half of the #1424 vocabulary: an author who can see what a field HOLDS must equally\nsee what can be ENFORCED about it. write_gated + on_violation are the facts a customer\nmeasurably could not guess - enum_membership refuses a violating write at the submit door,\nand a customer rebuilt that enforcement in prompt heuristics while it sat unpublished.\n\n\ud83d\udd34 `unknown_causes` (#4122) IS THE SAME DEFECT, FOUND THE SAME WAY. An exit set's\n`tolerate_unknown` is a REQUIRED field drawn from a CLOSED set the engine enforces, and it\nwas published nowhere: a customer guessed a name, got a cause her criterion kind can raise\nbut her case never hits, and lost the run. Her words: \"I guessed a name instead of reading\none.\" Each kind now carries the causes THAT KIND can raise - per kind, not one global list,\nbecause a global list only tells you a cause exists, which is what she already believed.",
        "example": {
          "kinds": [
            {
              "name": "enum_membership",
              "on_violation": "on_violation: refuse refuses the violating write at the submit door, with the criterion's own sentence as the steer",
              "params": [
                {
                  "help": "the allowed values; anything else violates",
                  "name": "values",
                  "required": true,
                  "shape": "string_list"
                }
              ],
              "semantics": "the field's value must be one of the listed strings, exactly",
              "unknown_causes": [
                "charter_defect",
                "not_yet_written"
              ],
              "write_gated": true
            }
          ]
        },
        "properties": {
          "kinds": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Kinds",
            "type": "array"
          }
        },
        "title": "CharterKinds",
        "type": "object"
      },
      "CharterList": {
        "additionalProperties": true,
        "description": "REFERENCES ONLY, by design: a caller needing a body follows the digest to the body store, so\nthere is exactly one copy and no second to drift.",
        "example": {
          "charters": [
            {
              "created_at": "2026-08-18T09:14:22Z",
              "digest": "sha256:2db80b1c9f4e",
              "kind": "charter",
              "workspace": "acme"
            }
          ]
        },
        "properties": {
          "charters": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Charters",
            "type": "array"
          }
        },
        "title": "CharterList",
        "type": "object"
      },
      "CharterSaved": {
        "additionalProperties": true,
        "description": "The pin. `digest` is the content address returned at registration - the only stable handle.",
        "example": {
          "digest": "sha256:2db80b1c9f4e",
          "workspace": "acme"
        },
        "properties": {
          "digest": {
            "title": "Digest",
            "type": "string"
          },
          "name": {
            "default": "",
            "title": "Name",
            "type": "string"
          },
          "note": {
            "default": "",
            "title": "Note",
            "type": "string"
          },
          "retired": {
            "default": false,
            "title": "Retired",
            "type": "boolean"
          },
          "supersedes": {
            "default": "",
            "title": "Supersedes",
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "title": "Tags",
            "type": "array"
          },
          "version": {
            "default": "",
            "title": "Version",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "digest",
          "workspace"
        ],
        "title": "CharterSaved",
        "type": "object"
      },
      "CharterTypes": {
        "additionalProperties": true,
        "description": "The value types a field may declare, and each type's option CONTROLS (#1424).\n\nA rendering contract, never a second validator: the console draws its form from this so that\nthe form and the engine cannot disagree about what an author may declare. Each option carries\nits default, because an unchecked box that silently means \"required\" is a form lying about what\nit declares.",
        "example": {
          "types": [
            {
              "name": "free_text",
              "options": []
            },
            {
              "name": "timestamp",
              "options": [
                {
                  "default": "false",
                  "help": "Leave unchecked when your platform already knows the person's timezone.",
                  "key": "require_timezone",
                  "kind": "checkbox",
                  "label": "Timezone required"
                }
              ]
            }
          ]
        },
        "properties": {
          "types": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Types",
            "type": "array"
          }
        },
        "title": "CharterTypes",
        "type": "object"
      },
      "CharterValidation": {
        "additionalProperties": true,
        "description": "A DRY RUN: what saving would say, registering nothing. A 422 carries the validator's own body\nverbatim, so the reasons a charter was refused are its words and not a paraphrase.",
        "example": {
          "digest": "sha256:2db80b1c9f4e",
          "errors": [],
          "valid": true,
          "warnings": [
            {
              "code": "charter.wedge_risk",
              "field": "name",
              "severity": "critical",
              "subject": "c_name",
              "text": "\u2026this charter is valid and no session opened against it can seal"
            }
          ],
          "workspace": "acme"
        },
        "properties": {
          "digest": {
            "default": "",
            "title": "Digest",
            "type": "string"
          },
          "errors": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Errors",
            "type": "array"
          },
          "valid": {
            "title": "Valid",
            "type": "boolean"
          },
          "warnings": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Warnings",
            "type": "array"
          },
          "workspace": {
            "default": "",
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "valid"
        ],
        "title": "CharterValidation",
        "type": "object"
      },
      "ChatMessage": {
        "additionalProperties": true,
        "description": "The assistant turn. `content` is where the answer IS.\n\n\u26a0\ufe0f extra=\"allow\" is load-bearing, not tidiness. Choices are forwarded VERBATIM from the\ngateway, so a strict model would SILENTLY DELETE anything upstream adds - logprobs, refusal,\naudio, tool_calls - turning a documentation improvement into data loss. Declared fields are a\npromise about what is present, never a filter on what is not.",
        "properties": {
          "content": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The generated text. THIS is the answer.",
            "title": "Content"
          },
          "role": {
            "default": "assistant",
            "description": "OpenAI: always `assistant` on a response.",
            "title": "Role",
            "type": "string"
          }
        },
        "title": "ChatMessage",
        "type": "object"
      },
      "CheckResponse": {
        "example": {
          "verdict": {
            "blocks": [],
            "steers": []
          }
        },
        "properties": {
          "verdict": {
            "additionalProperties": true,
            "description": "The current charter verdict, no seal attempt.",
            "title": "Verdict",
            "type": "object"
          }
        },
        "title": "CheckResponse",
        "type": "object"
      },
      "Choice": {
        "additionalProperties": true,
        "description": "One completion. `choices[0]` unless you asked for more, and `n>1` is refused here.",
        "properties": {
          "finish_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "OpenAI: stop | length | content_filter. SDKs branch on this \u2014 `length` means the answer was CUT OFF at max_tokens, not that the model finished.",
            "title": "Finish Reason"
          },
          "index": {
            "default": 0,
            "title": "Index",
            "type": "integer"
          },
          "message": {
            "$ref": "#/components/schemas/ChatMessage"
          }
        },
        "required": [
          "message"
        ],
        "title": "Choice",
        "type": "object"
      },
      "ClaimElement": {
        "properties": {
          "is_quoted": {
            "default": false,
            "description": "Whether the workload's answer quoted this element as a string. A quoted NUMBER is refused (type discipline) \u2014 citing 87.0 as \"87.0\" is a category error.",
            "title": "Is Quoted",
            "type": "boolean"
          },
          "value": {
            "title": "Value",
            "type": "string"
          }
        },
        "required": [
          "value"
        ],
        "title": "ClaimElement",
        "type": "object"
      },
      "ClaimsRequest": {
        "properties": {
          "elements": {
            "description": "The workload's OWN extracted answer elements. Cohort never parses the answer format (no FINISH knowledge) \u2014 the workload extracts, Cohort gates.",
            "items": {
              "$ref": "#/components/schemas/ClaimElement"
            },
            "title": "Elements",
            "type": "array"
          },
          "seal": {
            "default": true,
            "description": "Attempt the seal after submitting (False = check only).",
            "title": "Seal",
            "type": "boolean"
          },
          "sentinels": {
            "description": "Workload-supplied absence markers (e.g. -1, a 'not found' phrase) that pass WITHOUT anchoring. Cohort hardcodes none.",
            "items": {
              "type": "string"
            },
            "title": "Sentinels",
            "type": "array"
          }
        },
        "title": "ClaimsRequest",
        "type": "object"
      },
      "ClaimsResponse": {
        "example": {
          "per_element": [
            {
              "ok": true,
              "reason": "anchored",
              "value": "S0581164"
            }
          ],
          "sealed": true,
          "verdict": {
            "blocks": [],
            "steers": []
          }
        },
        "properties": {
          "per_element": {
            "description": "Per-element Cohort-side verdict: sentinel / anchored / quoted_numeric_refused / anchor_missing. A B-refusal here never reaches the charter engine.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Per Element",
            "type": "array"
          },
          "sealed": {
            "description": "True iff the session sealed complete (no blocks).",
            "title": "Sealed",
            "type": "boolean"
          },
          "verdict": {
            "additionalProperties": true,
            "description": "The charter seal/check verdict VERBATIM (blocks/steers) \u2014 the caller drives its next turn from this; Cohort does not reinterpret charter semantics.",
            "title": "Verdict",
            "type": "object"
          }
        },
        "required": [
          "sealed"
        ],
        "title": "ClaimsResponse",
        "type": "object"
      },
      "CohortUsageExtra": {
        "description": "Cohort's billing facts. An EXTENSION - OpenAI clients ignore unknown keys, so adding this\ncosts compatibility nothing while keeping what Cohort uniquely knows.",
        "properties": {
          "cost_usd": {
            "default": 0.0,
            "description": "Computed at this request's model rate.",
            "title": "Cost Usd",
            "type": "number"
          },
          "gpu_seconds": {
            "description": "Wall-clock of the upstream call \u2014 what you are billed on.",
            "title": "Gpu Seconds",
            "type": "number"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "workspace",
          "gpu_seconds"
        ],
        "title": "CohortUsageExtra",
        "type": "object"
      },
      "CompletionRequest": {
        "additionalProperties": true,
        "description": "OpenAI chat-completions compatible: MORE than the OpenAI schema, never less.\n\nEvery field an OpenAI client sends is either honoured, forwarded, or REFUSED BY NAME. Nothing is\nsilently dropped - the previous version ignored eleven OpenAI parameters, and `stream: true` was\nthe dangerous one: a client asked for a stream, got a complete non-streamed 200, and waited for\nchunks that were never coming.",
        "properties": {
          "billing_key_id": {
            "default": "",
            "description": "WHICH API KEY'S WALLET THIS COMPLETION SPENDS (#1876). Cohort extension. Credit is spendable only from a key its workspace has funded, and a completion is spend, so this door bills exactly one key just as session create does. \n\n\ud83d\udd34 FOR A HUMAN CALLER ONLY. Authenticate with an API key and the billing key IS that key, taken from your credential \u2014 sending a DIFFERENT one is refused at 403, because a key able to nominate another key could spend somebody else's wallet. Sending your own is fine. \n\nA signed-in person holds no key, so they nominate one belonging to the workspace in this request. Omitting it as a human is refused at 402 (`no_billing_key`) naming what to pick, rather than silently billed to the account \u2014 the account is a SOURCE for reservations and is no longer reachable by a spend.",
            "title": "Billing Key Id",
            "type": "string"
          },
          "max_tokens": {
            "default": 1024,
            "description": "Clamped to 4096.",
            "minimum": 1.0,
            "title": "Max Tokens",
            "type": "integer"
          },
          "messages": {
            "description": "OpenAI `messages`. Typed rather than an undescribed object array \u2014 this is the field you have to CONSTRUCT, so leaving its shape underivable was the worse half of the same defect applejack reported against `choices`.",
            "items": {
              "$ref": "#/components/schemas/InputMessage"
            },
            "title": "Messages",
            "type": "array"
          },
          "model": {
            "default": "",
            "description": "Model id. Defaults to the fleet default.",
            "title": "Model",
            "type": "string"
          },
          "prompt": {
            "default": "",
            "description": "Cohort extension. Shorthand for a single user message; `messages` wins.",
            "title": "Prompt",
            "type": "string"
          },
          "stream": {
            "default": false,
            "description": "OpenAI SSE streaming. Deltas, then `data: [DONE]`.",
            "title": "Stream",
            "type": "boolean"
          },
          "stream_options": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/StreamOptions"
              },
              {
                "type": "null"
              }
            ],
            "description": "OpenAI. `{include_usage: true}` appends a final usage chunk."
          },
          "temperature": {
            "default": 0.7,
            "maximum": 2.0,
            "minimum": 0.0,
            "title": "Temperature",
            "type": "number"
          }
        },
        "title": "CompletionRequest",
        "type": "object"
      },
      "CompletionResponse": {
        "additionalProperties": true,
        "description": "The OpenAI chat.completion envelope, plus `cohort`.\n\n\u26a0\ufe0f THE PREVIOUS SHAPE WAS NOT OPENAI-COMPATIBLE and the published example did not even match\nthe model it documented: the example advertised `id` and `content`, neither of which existed,\nwhile the real fields were `text`, `workspace`, `token_input`, `token_output`. An SDK pointed at\nit got a 200 and then KeyError: 'choices'.",
        "example": {
          "choices": [
            {
              "finish_reason": "stop",
              "index": 0,
              "message": {
                "content": "The migration is complete.",
                "role": "assistant"
              }
            }
          ],
          "cohort": {
            "cost_usd": 0.0103,
            "gpu_seconds": 4.12,
            "workspace": "acme"
          },
          "created": 1786130000,
          "id": "chatcmpl-6b21e0f4a1d3",
          "model": "claude-opus-5",
          "object": "chat.completion",
          "usage": {
            "completion_tokens": 40,
            "prompt_tokens": 12,
            "total_tokens": 52
          }
        },
        "properties": {
          "choices": {
            "description": "Typed on purpose. Reported by applejack as a contract defect: these items were `{type: object, additionalProperties: true}`, so the schema said the answer was in here somewhere and not WHERE. Knowing it is choices[0].message.content required knowing the OpenAI envelope from OUTSIDE this document \u2014 the same derive-it-elsewhere problem as an absent base URL, in the one field that carries the answer.",
            "items": {
              "$ref": "#/components/schemas/Choice"
            },
            "title": "Choices",
            "type": "array"
          },
          "cohort": {
            "$ref": "#/components/schemas/CohortUsageExtra"
          },
          "created": {
            "title": "Created",
            "type": "integer"
          },
          "id": {
            "title": "Id",
            "type": "string"
          },
          "model": {
            "title": "Model",
            "type": "string"
          },
          "object": {
            "default": "chat.completion",
            "title": "Object",
            "type": "string"
          },
          "usage": {
            "$ref": "#/components/schemas/CompletionUsage"
          }
        },
        "required": [
          "id",
          "created",
          "model",
          "choices",
          "cohort"
        ],
        "title": "CompletionResponse",
        "type": "object"
      },
      "CompletionUsage": {
        "additionalProperties": true,
        "description": "Token counts. Typed for the same reason as Choice: a consumer should not have to know the\nOpenAI spec from outside this document to find them.",
        "properties": {
          "completion_tokens": {
            "default": 0,
            "title": "Completion Tokens",
            "type": "integer"
          },
          "prompt_tokens": {
            "default": 0,
            "title": "Prompt Tokens",
            "type": "integer"
          },
          "total_tokens": {
            "default": 0,
            "title": "Total Tokens",
            "type": "integer"
          }
        },
        "title": "CompletionUsage",
        "type": "object"
      },
      "ConditionCreate": {
        "additionalProperties": false,
        "description": "POST /v1/goals/{goal_id}/conditions. `predicate` is the ONLY required field on this surface.\n\nIts inner shape is validated separately (lang whitelist + the substring negative_guards waiver)\nand the 422 explains the rule - see _validate_predicate.",
        "properties": {
          "access_class": {
            "default": "effectful",
            "description": "Empty string is treated as 'effectful'.",
            "title": "Access Class",
            "type": "string"
          },
          "approved_by": {
            "default": "",
            "title": "Approved By",
            "type": "string"
          },
          "condition_id": {
            "default": "",
            "description": "Omit for a server-generated `cond-<12 hex>`.",
            "title": "Condition Id",
            "type": "string"
          },
          "drafted_by": {
            "default": "",
            "title": "Drafted By",
            "type": "string"
          },
          "freshness": {
            "default": "revalidate",
            "description": "Empty string is treated as 'revalidate'.",
            "title": "Freshness",
            "type": "string"
          },
          "predicate": {
            "additionalProperties": true,
            "description": "REQUIRED, non-empty. {lang: 'jsonpath'|'substring', expr: str, ...}. A substring predicate MUST carry non-empty negative_guards or it is refused.",
            "title": "Predicate",
            "type": "object"
          },
          "predicate_hash": {
            "default": "",
            "title": "Predicate Hash",
            "type": "string"
          },
          "schema_hash": {
            "default": "",
            "title": "Schema Hash",
            "type": "string"
          },
          "verifier_server": {
            "default": "",
            "title": "Verifier Server",
            "type": "string"
          },
          "verifier_tool": {
            "default": "",
            "title": "Verifier Tool",
            "type": "string"
          },
          "verify": {
            "additionalProperties": true,
            "title": "Verify",
            "type": "object"
          }
        },
        "required": [
          "predicate"
        ],
        "title": "ConditionCreate",
        "type": "object"
      },
      "ConditionEnvelope": {
        "additionalProperties": true,
        "example": {
          "condition": {
            "access_class": "effectful",
            "approved_by": "",
            "condition_id": "cond-1a2b3c4d5e6f",
            "drafted_by": "applejack",
            "freshness": "revalidate",
            "goal_id": "goal-4f2a91c07b3e",
            "platform": "acme",
            "predicate": {
              "expr": "$.status",
              "lang": "jsonpath",
              "op": "eq",
              "value": "done"
            },
            "predicate_hash": "",
            "schema_hash": "",
            "verifier_server": "crm",
            "verifier_tool": "get_order",
            "verify": {}
          }
        },
        "properties": {
          "condition": {
            "$ref": "#/components/schemas/ExitCondition"
          }
        },
        "required": [
          "condition"
        ],
        "title": "ConditionEnvelope",
        "type": "object"
      },
      "ConditionList": {
        "additionalProperties": true,
        "example": {
          "conditions": [
            {
              "access_class": "effectful",
              "approved_by": "",
              "condition_id": "cond-1a2b3c4d5e6f",
              "drafted_by": "applejack",
              "freshness": "revalidate",
              "goal_id": "goal-4f2a91c07b3e",
              "platform": "acme",
              "predicate": {
                "expr": "$.status",
                "lang": "jsonpath",
                "op": "eq",
                "value": "done"
              },
              "predicate_hash": "",
              "schema_hash": "",
              "verifier_server": "crm",
              "verifier_tool": "get_order",
              "verify": {}
            }
          ],
          "count": 1,
          "goal_id": "goal-4f2a91c07b3e",
          "platform": "acme"
        },
        "properties": {
          "conditions": {
            "items": {
              "$ref": "#/components/schemas/ExitCondition"
            },
            "title": "Conditions",
            "type": "array"
          },
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "goal_id": {
            "title": "Goal Id",
            "type": "string"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          }
        },
        "required": [
          "platform",
          "goal_id",
          "count"
        ],
        "title": "ConditionList",
        "type": "object"
      },
      "ContentDeleted": {
        "additionalProperties": true,
        "description": "One session's stored content, removed on request. The billing rows are not touched.",
        "example": {
          "deleted": true,
          "note": "content in this class is also swept automatically after 90 days; you did not have to ask",
          "session_id": "sess-2f9a4c1e",
          "usage_retained": "billing rows for this session are KEPT \u2014 you were billed for the work and the invoice must stay reconcilable"
        },
        "properties": {
          "deleted": {
            "title": "Deleted",
            "type": "boolean"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "deleted"
        ],
        "title": "ContentDeleted",
        "type": "object"
      },
      "CreateSessionRequest": {
        "additionalProperties": false,
        "description": "What a caller supplies to start a job.\n\nDeliberately absent: `session_id` (assigned here - a caller-chosen id could collide with a live\nsession or probe for one) and `workspace`/`platform` (resolved from the bearer claim).\n\nUNKNOWN FIELDS ARE REFUSED, NOT IGNORED (#996). Pydantic's default is `extra=\"ignore\"`, and on\na migration surface that default is a trap: this endpoint exists so callers can move off the\ngRPC `CohortHost` stream, and five of that message's seven fields have no counterpart here.\nUnder the default, a caller porting `identity_system_prompt` got **200 OK with an empty system\nprompt** - an agent with no identity, no error anywhere, and a symptom (\"the assistant seems\noff\") that gets debugged in the caller's code for a day. Measured 2026-08-07.\n\nSo the model forbids extras, and `_explain_grpc_fields` upgrades the generic rejection into a\nnamed replacement for the fields we KNOW people will send. Refusing `identity_system_prompt`\nwithout saying `system_prompt` converts a silent failure into a loud dead end, which is better\nand not by much.",
        "properties": {
          "assistant_id": {
            "default": "",
            "title": "Assistant Id",
            "type": "string"
          },
          "billing_key_id": {
            "default": "",
            "description": "WHICH API KEY'S WALLET THIS SESSION SPENDS (#1876). Credit is spendable only from a key its workspace has funded, so every session names exactly one key to bill and there is no keyless spend. \n\n\ud83d\udd34 FOR A HUMAN CALLER ONLY. If you authenticate with an API key, the billing key IS that key, taken from your credential \u2014 sending this field is refused at 403 rather than honoured, because a key able to nominate another key is a key able to spend somebody else's wallet. A signed-in person holds no key, so they nominate one: it must belong to the workspace named in this request, and the refusal says so when it does not. \n\nOmitting it as a human is refused at 402 (`no_billing_key`) naming what to pick, not silently billed to the account \u2014 the account is a SOURCE for reservations and is no longer reachable by a session.",
            "title": "Billing Key Id",
            "type": "string"
          },
          "charter_digest": {
            "default": "",
            "description": "Optional charter binding. When set, the session opens as a GOAL session: the digest must be a charter reference this workspace holds, the binding is pinned at open for the session's life, and COMPLETE will require the seal. Empty = charterless \u2014 today's behaviour, untouched, and no charter binding occurs.\n\nWHAT OUR AGENT SEES OF ITS RECORD (#4343). On a session our agent drives, it reads the record through its own view: every field's name and description, whether it is filled, and for a filled field its CURRENT value, marked as recorded by the agent or PROVIDED (written by anything else: your own attested writes, a measurement, a computed value, a value carried from an earlier session). A slot shows its status, and its value only when answered. So a value your systems attest is one the agent can use; you do not need to pass it a second way. The one exception: a field that holds the person's own words (subject register) never shows them. The view is not evidence - nothing in it can be cited as something the person said.",
            "title": "Charter Digest",
            "type": "string"
          },
          "charter_ref": {
            "default": "",
            "description": "#2050 \u2014 the charter to run, named by ADDRESS rather than by digest. Pass this OR `charter_digest`, never both.\n\nWHAT RESOLVES:\n  `k2:<hex>` (or legacy bare 64-hex) \u2014 a DIGEST. Exact and immutable; identical to passing `charter_digest`, and what production should name.\n  `<slug>` \u2014 the CURRENT version of that charter lineage in YOUR workspace. Naming no version asks for the one in use, as every registry reads an absent version. The author registers a new version and your next session picks it up, with no redeploy on your side.\n  `<slug>@latest` \u2014 the same thing said explicitly. Accepted because it is self-documenting; never required.\n  `<workspace>/<slug>` \u2014 either form written out; the workspace must be yours.\n\n\ud83d\udd34 `<slug>@<version>` DOES NOT RESOLVE. A version string is free text and is not unique, so it cannot name one charter. Use the slug for the current version, or pin the digest for an exact one.\n\nRESOLVED ONCE, AT OPEN, AND PINNED FOR THE SESSION'S LIFE. A version registered while your session is running does not change it: turns already graded were graded against the rules the session opened on, and re-resolving mid-session would make the session's own verdicts disagree with each other. Read the resolved digest back from `GET /v1/sessions/{id}` \u2014 the session RECORD stores the digest, never the string `latest`, so what a session ran stays reproducible after the lineage has moved on.\n\n\u26a0\ufe0f FOLLOWING `@latest` MEANS A NEW REGISTRATION REACHES YOUR NEXT SESSION WITH NO DEPLOY ON YOUR SIDE \u2014 that is the point of it, and it is also the thing to be deliberate about. Pass a digest wherever you want that not to happen; production normally should. Which form you use is your choice, per session, and the resolved digest is recorded either way so what ran is always readable afterwards.",
            "title": "Charter Ref",
            "type": "string"
          },
          "context": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "TRUSTED FACTS this session is checked against (#2190), keyed by the names the charter's context block declares: the date a value applies to, an attribute of the subject. They are yours to supply and the agent's to be measured by - it can never write or overwrite one, which is the point: a check whose standard the agent supplies is not a check. Validated against the charter's declaration BEFORE the session is created, so a required input you left out, a value of the wrong type, or a name the charter does not declare refuses this call (422, codes context.missing / context.type / context.undeclared) and creates nothing. Dates are YYYY-MM-DD civil dates and compare as DAYS, with no time zone applied. Ignored when charter_digest is empty.",
            "title": "Context",
            "type": "object"
          },
          "denied_tools": {
            "description": "security-2: per-session tool DENY SET (namespaced tool names, e.g. 'forge_push_records'). An EFFECTFUL call to a named tool is VETOED at declaration time and the agent is told, as an ordinary tool refusal, that the tool is not permitted for this session. This is the per-call, per-session lever \u2014 deny a tool HERE, for THIS session, without refusing it at hydration for every future turn (always) or handing it over (never).",
            "items": {
              "type": "string"
            },
            "title": "Denied Tools",
            "type": "array"
          },
          "doer": {
            "default": "platform",
            "description": "WHO DRIVES this session's agent (#1631). \"platform\" (default): Cohort spawns the worker, and that worker IS the agent \u2014 today's behaviour, untouched. \"client\": YOUR harness drives the agent on your side, and Cohort spawns NOTHING \u2014 it opens and pins the charter record and proxies grounding (evidence / claims / check / seal) for your doer. Requires charter_digest when set to \"client\": grounding an uncharted session is meaningless, and a client-doer session that grounds nothing is a session Cohort does nothing for. ORTHOGONAL to record_writer, which answers the DIFFERENT question of who WRITES the record \u2014 all four combinations are real except the incoherent one (see record_writer). Anything other than these two values is refused at 422.",
            "title": "Doer",
            "type": "string"
          },
          "external_doer": {
            "default": false,
            "description": "LEGACY SPELLING of doer=\"client\" (which also settles the record: with no worker of ours there is no agent to hold the record-keeping tools, so the client writes it). Kept working exactly as it was and not deprecated on the wire \u2014 every caller that sends it gets the identical session. Open a GROUNDING-ONLY session: Cohort opens and pins the charter record and proxies grounding (evidence / claims / check / seal) for a doer the CUSTOMER runs, and spawns NO worker of its own. Requires charter_digest \u2014 grounding an uncharted session is meaningless. Prefer `doer` in new code: it names the question (who drives) rather than the answer, and it composes with record_writer. Sending BOTH is fine when they agree; a contradiction (external_doer=true with doer=\"platform\") is refused at 422 rather than silently resolved.",
            "title": "External Doer",
            "type": "boolean"
          },
          "goal_id": {
            "default": "",
            "description": "Optional first-class goal. When set, the control plane resolves the Goal/Task rows and the session runs under the termination layer.",
            "title": "Goal Id",
            "type": "string"
          },
          "gpu_seconds_hard_cap": {
            "default": 900,
            "description": "Denial-of-wallet ceiling in billable-seconds, for the WHOLE session. 0 means UNCAPPED and must be set deliberately. \ud83d\udd34 CHOSEN ONCE, AT CREATE, AND NEVER RAISABLE \u2014 there is no route that changes it on a live session. Two consequences worth knowing before you pick a number. (1) Size it for everything the session may ever do, including any budget you might PATCH onto it later: raising `max_cost_usd` mid-session moves the LLM-token half of spend and does NOT move this, so a session stopped here cannot be rescued by adding credit. (2) Size it in TURNS, not in dollars \u2014 per-turn compute is what it counts, and it is metered per turn rather than by wall-clock, so a session sitting idle while somebody decides something consumes none of it.",
            "minimum": 0.0,
            "title": "Gpu Seconds Hard Cap",
            "type": "integer"
          },
          "inputs": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/SessionInputs"
              },
              {
                "type": "null"
              }
            ],
            "description": "LINEAGE (#4204): the sessions this one is created FROM. For each, the platform reads the stored transcript and/or the record of a session in your workspace that has ended, renders them as the first `messages` the agent reads (yours follow), computes the sha256 of each, and pins `source_session`, `source_transcript_sha256` and `source_record_sha256` on the session as CONTEXT INPUTS - so the charter MUST declare those three names (type string) and you may not pass them in `context` yourself. Refused: a source that has not ended (409 source_not_ended), an unknown or another workspace's session (404, the same body as any other read), a charter that does not declare the three names (422 naming them), the names in your own `context` (422). Read back on `GET /sessions/{id}` as `derived_from` here and `derived` on the source. Needs a charter."
          },
          "keycloak_sub": {
            "default": "",
            "title": "Keycloak Sub",
            "type": "string"
          },
          "locale": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/SessionLocale"
              },
              {
                "type": "null"
              }
            ],
            "description": "#1998: the subject's locale for a CHARTERED session ({tag, dialect}). Pinned on the charter engine's session at open and readable back on its state; used to resolve subject-register renderings. Ignored on a charterless session."
          },
          "max_cost_usd": {
            "default": 0.0,
            "description": "W4: this session's declared dollar ceiling. OMITTING IT NO LONGER MEANS UNBOUNDED: when you declare nothing, the server derives the ceiling from the billing key's own UNCOMMITTED credit (its reserved balance minus whatever is already promised to your sessions that are still running) and applies that instead. So the credit reserved to a key is the ceiling on what can be spent against it whether you name a number or not, and a key whose credit is entirely committed to running sessions is refused a new one with 429 `reserved` rather than admitted. \u26a0\ufe0f 429, NOT 402, AND THE DIFFERENCE IS WHAT YOU SHOULD DO ABOUT IT: `reserved` means the wallet HAS money that is already promised to sessions still running, so it clears on its own the moment one of them ends \u2014 wait, or end one. 402 on this API means the opposite (nothing changes until somebody acts on the account). This description said 402 until 2026-09-12 and an integrator had written their retry handling against it. Declaring a LOWER number still works and still wins; declaring a HIGHER one is refused if it does not fit. \ud83d\udd34 THE ONE RESIDUAL OVERSHOOT, STATED AS A NUMBER because integrators build credit products on it: a ceiling is checked BEFORE a turn and a turn's cost is known only AFTER, so the final turn may carry the balance past zero by AT MOST that one turn's cost. It is not a configurable overdraft and it does not compound across turns or across session resumes \u2014 the wallet goes negative by one turn and the key's next reservation absorbs it (#1740-A). Bounds BOTH halves of spend: the LLM-token half directly (sized onto the gateway vkey's own budget) and the compute half indirectly (converted server-side into an additional gpu_seconds_hard_cap tightening \u2014 the conversion rate is never disclosed, only applied). Like the sibling ceilings (max_turns, max_tokens, gpu_seconds_hard_cap), the crossing turn already ran and IS billed \u2014 this stops the NEXT turn, not the one in flight when the ceiling was crossed; there is no mid-turn refund. Update a LIVE session's declared cap with PATCH /v1/sessions/{id}/budget \u2014 this field only sets the cap at CREATE.",
            "minimum": 0.0,
            "title": "Max Cost Usd",
            "type": "number"
          },
          "max_tokens": {
            "default": 0,
            "description": "PER SESSION, not per turn: input+output tokens SUMMED ACROSS EVERY TURN of this session, enforced by the control plane at each metered turn \u2014 deliberately NOT a gateway key budget, which is advisory there. Ends the session with reason token_cap_exceeded once crossed. 0 = no cap. \ud83d\udd34 IT IS NOT A BOUND ON ONE TURN, and cannot be used as one: the ceiling is checked BETWEEN turns, so the turn that crosses it has already run in full. Nothing here bounds a single turn's size \u2014 see `context_length` on GET /v1/models for what the model itself will accept. \ud83d\udd34 LIKE max_turns, THIS CANNOT BE RAISED ONCE THE SESSION IS OPEN, and an ended session never accepts input again \u2014 only `max_cost_usd` is raisable mid-session (PATCH /v1/sessions/{id}/budget).",
            "minimum": 0.0,
            "title": "Max Tokens",
            "type": "integer"
          },
          "max_turns": {
            "default": 0,
            "description": "PER SESSION, not per turn: the total number of turns this session may run, counted across the whole session and enforced by the control plane at each metered turn (the session ends with reason max_turns_exceeded once crossed; the crossing turn already ran and is billed \u2014 no mid-turn refund, mirroring the gpu cap). 0 = no per-session cap. \ud83d\udd34 THIS CEILING CANNOT BE RAISED ONCE THE SESSION IS OPEN. There is no route that changes it on a live session, and an ENDED session never accepts input again \u2014 so a session stopped here cannot be rescued by any later call, at any price. Only `max_cost_usd` is raisable mid-session (PATCH /v1/sessions/{id}/budget). If you intend a dollar ceiling to be the one that stops a session, set this clear of where the session can reach. Set it from your own unit economics: GET /v1/usage/economics shows the turn-count distribution that justifies the number.",
            "minimum": 0.0,
            "title": "Max Turns",
            "type": "integer"
          },
          "mcp_servers": {
            "description": "Explicit per-session tool bundle. Empty = no MCP tools. All-or-nothing: if any server fails to connect the spawn fails closed rather than running with a silently reduced tool surface.",
            "items": {
              "$ref": "#/components/schemas/McpServerSpec"
            },
            "title": "Mcp Servers",
            "type": "array"
          },
          "messages": {
            "description": "Pre-hydrated context, e.g. the text of a fetched page.",
            "items": {
              "$ref": "#/components/schemas/SessionMessage"
            },
            "title": "Messages",
            "type": "array"
          },
          "metadata": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "OPAQUE caller metadata, echoed back on the create response, the session read and EVERY webhook delivery. Cohort never reads, parses or branches on any key. Use it to correlate a webhook back to whatever triggered the session \u2014 that removes the need to key your own table on session_id, and with it the race where a fast session's webhook beats your own INSERT. Bounded at 16 keys / 64-char keys / 512-char values and REFUSED at 400 when over, never truncated. NOT for credentials: it is echoed to your webhook target and rides in the worker's hydration context, so the agent can see it.",
            "title": "Metadata",
            "type": "object"
          },
          "model": {
            "default": "",
            "description": "REQUIRED, EXCEPT ON A CLIENT-DRIVEN SESSION. The gateway model alias this session runs on. There is no deployment default: a session that does not name a model is refused at 422, because a result produced on a model you did not choose cannot be reproduced, compared or published. Validated against the gateway's served list at create \u2014 the refusal NAMES the served models in both cases (absent and unknown), and GET /v1/models lists them too.\n\n\ud83d\udd34 WITH `doer: client` THIS IS NOT ASKED FOR, AND IS NOT RECORDED IF SENT (#4121). Cohort spawns nothing on that path and no turn is answered by a model of ours, so a name given here could never be confirmed by anything that observed the work. Send it anyway and the create returns a warning saying it was not recorded.\n\nThe seal does not merely omit it: it STATES that your own side answered the turns, and the artifact carries that as a sentence. An omission alone would read the same as a model line that went missing, which leaves whoever you show the record to asking you which it was.",
            "title": "Model",
            "type": "string"
          },
          "pack_digests": {
            "description": "Knowledge-pack references bound with the charter at open. Each must be a pack reference this workspace holds; if the workspace sets signed_packs_required, each must carry a sign-off \u2014 an edited pack mints a NEW digest with no sign-offs, so re-review is enforced by absence, not revocation. Ignored when charter_digest is empty.",
            "items": {
              "type": "string"
            },
            "title": "Pack Digests",
            "type": "array"
          },
          "record_writer": {
            "default": "agent",
            "description": "Who holds the record-writing surface of a CHARTERED session (#1621). \"agent\" (default): Cohort's worker gets the record-keeping tools and writes the record as it converses \u2014 today's behaviour, untouched. \"client\": YOUR caller writes the record through the HTTP grounding surface (/submit, /claims, /evidence); the worker still converses and its retrievals are still grounded as evidence, but it gets NO record-keeping tools \u2014 the record is yours to author. Only meaningful with charter_digest: on a charterless session there is no record either way and the value is stored but inert. Anything other than these two values is refused at 422. ORTHOGONAL to `doer` with ONE incoherent pair: doer=\"client\" (however spelled) with an explicit record_writer=\"agent\" is refused at 422 \u2014 naming the client as the doer spawns no worker of ours, so there is no agent here to hold the record-keeping tools. Naming the client as the doer and saying nothing about the writer resolves to \"client\", which is the only writer such a session can have.",
            "title": "Record Writer",
            "type": "string"
          },
          "seed": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Decoding seed for THIS session, where the upstream honours it. Temperature 0 lowers variance; a seed is what makes a run repeatable.\n\nNOT RECORDED ON A CLIENT-DRIVEN SESSION (`doer: client`). Cohort decodes nothing there - your own side samples - so a value pinned here could never be confirmed by anything that observed the work, and the seal states no sampling rather than an unverified one. Sending one is accepted and returns a warning naming what was dropped.",
            "title": "Seed"
          },
          "system_prompt": {
            "default": "",
            "description": "The agent's instructions for this session.",
            "title": "System Prompt",
            "type": "string"
          },
          "temperature": {
            "anyOf": [
              {
                "maximum": 2.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Decoding temperature for THIS session. Omit to leave the deployment default exactly as it is \u2014 omitted means 'not asked', never a value we picked for you. Set 0 for a reproducible run: without it the same prompt can return a different answer on every attempt, which makes any benchmark, A/B or regression gate taken here unciteable.\n\nNOT RECORDED ON A CLIENT-DRIVEN SESSION (`doer: client`). Cohort decodes nothing there - your own side samples - so a value pinned here could never be confirmed by anything that observed the work, and the seal states no sampling rather than an unverified one. Sending one is accepted and returns a warning naming what was dropped.",
            "title": "Temperature"
          },
          "top_p": {
            "anyOf": [
              {
                "maximum": 1.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Nucleus sampling for THIS session. Omit to leave unchanged.\n\nNOT RECORDED ON A CLIENT-DRIVEN SESSION (`doer: client`). Cohort decodes nothing there - your own side samples - so a value pinned here could never be confirmed by anything that observed the work, and the seal states no sampling rather than an unverified one. Sending one is accepted and returns a warning naming what was dropped.",
            "title": "Top P"
          },
          "workspace": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WorkspaceSpec"
              },
              {
                "type": "null"
              }
            ],
            "description": "#4426 a repository for the agent to work in: a git bundle you uploaded (POST /v1/workspace-bundles) and the ref to use. The agent gets file and shell tools on that tree, in an isolated sandbox with no credentials. Its changes come back from GET /v1/sessions/{id}/patch. Not with doer=client (nothing of Cohort's runs there)."
          }
        },
        "title": "CreateSessionRequest",
        "type": "object"
      },
      "CreateSessionResponse": {
        "example": {
          "accepted": true,
          "job": {
            "queued_at": "2026-09-17T15:04:05+00:00",
            "reason": "",
            "retryable": false,
            "stage": "",
            "state": "queued"
          },
          "metadata": {
            "tenant": "acme",
            "trace_id": "d41f9c02"
          },
          "phase": "queued",
          "session_id": "sess-2f9a4c1e",
          "workspace": "acme"
        },
        "properties": {
          "accepted": {
            "default": true,
            "title": "Accepted",
            "type": "boolean"
          },
          "job": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/JobStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "The session's place in the queue, in the vocabulary every queued job shares."
          },
          "metadata": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Echoed back verbatim so a caller can confirm what was stored before relying on it arriving in a webhook.",
            "title": "Metadata",
            "type": "object"
          },
          "phase": {
            "default": "starting",
            "description": "`starting` when the session was admitted at once, `queued` when your account is already running as many sessions as it may at once. A queued session exists, is listed, can be ended, and starts by itself, in the order it was created, when one of your running sessions ends. Queued time is not billed. Poll GET /v1/sessions/{id} for its `phase`.",
            "title": "Phase",
            "type": "string"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          },
          "warnings": {
            "description": "Conditions this session was ACCEPTED WITH that will make it behave in a way the request probably did not intend. Empty on a coherent session, and a caller may ignore it \u2014 the session is created either way.\n\n\ud83d\udd34 THE ONE THAT EXISTS TODAY (#2066): a chartered session created with `record_writer=\"client\"` has NO doer holding the record tools, while its charter expects a doer to author some of its fields. Nothing will write those fields unless your own surface does, every criterion graded on them stays `unknown`, and an exit set requiring one can never be satisfied. The warning NAMES THE FIELDS, because naming the setting only repeats what you sent.\n\nThis was added after that exact combination read as a model ignoring its instructions for four days on a live pilot \u2014 the agent converses normally and even SAYS it recorded, so the conversation looks correct while the record stays empty. A warning here is the earliest point at which anything could have said otherwise.",
            "items": {
              "type": "string"
            },
            "title": "Warnings",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "workspace"
        ],
        "title": "CreateSessionResponse",
        "type": "object"
      },
      "CredentialPropagation": {
        "additionalProperties": true,
        "description": "When a rotated credential takes effect, and what happens to sessions already running. (#1201)\n\nPublished because the answer is \"not until the next session\", and that is exactly the kind of\nthing a customer otherwise learns during an incident - at the moment they have just revoked a\ncredential and need to know whether it is actually gone.\n\nGenerated from the same environment the boot check asserts, so the published window cannot\ndrift from the configured one.",
        "example": {
          "bounded_by": "A session's worker lives at most 86400 seconds, so a rotated credential is fully in effect everywhere within that window at the latest.",
          "bounded_by_seconds": 86400,
          "credential_classes": [
            "llm_gateway_key",
            "mcp_tokens",
            "forge_git_credential"
          ],
          "rotation_takes_effect": "on the NEXT session. Credentials are delivered once, when a session's worker starts, and are fixed for that worker's life.",
          "sessions_already_running": "keep the credentials they were started with. Rotating or revoking a credential does NOT reach a session that is already running.",
          "to_rotate_immediately": "end the running sessions yourself after rotating."
        },
        "properties": {
          "bounded_by_seconds": {
            "title": "Bounded By Seconds",
            "type": "integer"
          },
          "rotation_takes_effect": {
            "title": "Rotation Takes Effect",
            "type": "string"
          },
          "sessions_already_running": {
            "title": "Sessions Already Running",
            "type": "string"
          }
        },
        "required": [
          "rotation_takes_effect",
          "sessions_already_running",
          "bounded_by_seconds"
        ],
        "title": "CredentialPropagation",
        "type": "object"
      },
      "DiagnosticsResponse": {
        "description": "#1167 Session Console: ONE merged diagnostics read. Verdict and field projection are\nfetched together server-side so a console never renders a verdict and a fill state from two\ndifferent instants - two independent client-side reads showing different chain seqs reads as\nan engine bug. `verdict_error` distinguishes \"the charter engine answered with a refusal\" from \"not\nchartered / nothing to grade\" (verdict simply null): a console must render ' - ', never guess.\nOne grounding-meter count per call regardless of how many charter reads ride it.",
        "example": {
          "session": {
            "charter_digest": "b41cd1e08a67",
            "ended": false,
            "live": true,
            "session_id": "api-2f9a4c1e",
            "workspace": "acme"
          },
          "state": {
            "fields": {
              "answer": {
                "written": true
              }
            }
          },
          "verdict": {
            "blocks": [],
            "steers": []
          }
        },
        "properties": {
          "session": {
            "additionalProperties": true,
            "description": "Durable + live status, same resolution as GET /sessions/{id}.",
            "title": "Session",
            "type": "object"
          },
          "state": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "Field projection from the charter state door; null until that door ships.",
            "title": "State"
          },
          "verdict": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "Charter check verdict; null when charterless.",
            "title": "Verdict"
          },
          "verdict_error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Refusal detail when the verdict read was not ok.",
            "title": "Verdict Error"
          },
          "warnings": {
            "description": "#2066 \u2014 conditions that explain a verdict rather than describing it. Empty on a coherent session.\n\nThe one that exists today: a chartered session created with `record_writer=\"client\"` has no doer holding the record tools, so criteria graded on fields the charter expects a DOER to author will read `unknown` forever. The verdict alone says `not yet written`, and the word 'yet' is wrong here \u2014 waiting cannot help. This field is where that difference is stated, because the person reading diagnostics is the one already stuck.",
            "items": {
              "type": "string"
            },
            "title": "Warnings",
            "type": "array"
          }
        },
        "required": [
          "session"
        ],
        "title": "DiagnosticsResponse",
        "type": "object"
      },
      "EvidenceIngestRequest": {
        "properties": {
          "content_type": {
            "default": "application/json",
            "description": "Content-Type of `raw`.",
            "title": "Content Type",
            "type": "string"
          },
          "raw": {
            "description": "The retrieval's response body, VERBATIM. Cohort atomizes THIS (the doer never atomizes \u2014 that is what keeps a claim from anchoring against the doer's own assertion).",
            "title": "Raw",
            "type": "string"
          },
          "strategy": {
            "default": "combined",
            "description": "Atomization strategy the workspace selects for this tool: one of auto / json-values / boundary-tokens / combined / free-text. `combined` is anchor_in-faithful.",
            "title": "Strategy",
            "type": "string"
          }
        },
        "required": [
          "raw"
        ],
        "title": "EvidenceIngestRequest",
        "type": "object"
      },
      "EvidenceIngestResponse": {
        "example": {
          "atoms": 37,
          "detail": {
            "evidence_ref": 2,
            "recorded": 37
          },
          "recorded": true
        },
        "properties": {
          "atoms": {
            "description": "Count of citeable atoms recorded from this retrieval.",
            "title": "Atoms",
            "type": "integer"
          },
          "detail": {
            "additionalProperties": true,
            "description": "The charter engine's evidence receipt, verbatim.",
            "title": "Detail",
            "type": "object"
          },
          "recorded": {
            "title": "Recorded",
            "type": "boolean"
          }
        },
        "required": [
          "recorded",
          "atoms"
        ],
        "title": "EvidenceIngestResponse",
        "type": "object"
      },
      "EvidenceList": {
        "additionalProperties": true,
        "example": {
          "count": 1,
          "platform": "acme",
          "records": [
            {
              "attempt": 1,
              "condition_id": "cond-1a2b3c4d5e6f",
              "definition_epoch": "2026-08-07",
              "evidence_id": "ev-5d8f01ac3b72",
              "platform": "acme",
              "predicate_hash": "sha256:44ab\u2026",
              "prev_hash": "sha256:0000\u2026",
              "response_hash": "sha256:9c1f\u2026",
              "run_id": "run-77c2e5a10bd4",
              "schema_hash": "",
              "verdict": "confirmed"
            }
          ]
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "records": {
            "items": {
              "$ref": "#/components/schemas/EvidenceRecord"
            },
            "title": "Records",
            "type": "array"
          }
        },
        "required": [
          "platform",
          "count"
        ],
        "title": "EvidenceList",
        "type": "object"
      },
      "EvidenceRecord": {
        "additionalProperties": true,
        "description": "\u26a0\ufe0f `raw_response` is WITHHELD from every tenant read - it can carry verifier payloads.\n\n`response_hash` is returned so you can prove two records saw the same response without the\nresponse itself. Absence is by design, not emptiness.",
        "properties": {
          "attempt": {
            "default": 0,
            "title": "Attempt",
            "type": "integer"
          },
          "condition_id": {
            "default": "",
            "title": "Condition Id",
            "type": "string"
          },
          "definition_epoch": {
            "default": "",
            "title": "Definition Epoch",
            "type": "string"
          },
          "evidence_id": {
            "title": "Evidence Id",
            "type": "string"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "predicate_hash": {
            "default": "",
            "title": "Predicate Hash",
            "type": "string"
          },
          "prev_hash": {
            "default": "",
            "description": "Hash-chain link to the previous record.",
            "title": "Prev Hash",
            "type": "string"
          },
          "response_hash": {
            "default": "",
            "title": "Response Hash",
            "type": "string"
          },
          "run_id": {
            "default": "",
            "title": "Run Id",
            "type": "string"
          },
          "schema_hash": {
            "default": "",
            "title": "Schema Hash",
            "type": "string"
          },
          "verdict": {
            "default": "",
            "description": "confirmed | disconfirmed | unverified. UNVERIFIED is not a failure \u2014 it means the predicate could not decide.",
            "title": "Verdict",
            "type": "string"
          }
        },
        "required": [
          "evidence_id",
          "platform"
        ],
        "title": "EvidenceRecord",
        "type": "object"
      },
      "EvidenceRefusal": {
        "additionalProperties": true,
        "properties": {
          "detail": {
            "title": "Detail",
            "type": "string"
          },
          "reason": {
            "description": "`too_large`, `unavailable`, or `refused`.",
            "title": "Reason",
            "type": "string"
          },
          "status": {
            "description": "HTTP-equivalent status; 0 when the record was unreachable.",
            "title": "Status",
            "type": "integer"
          }
        },
        "required": [
          "reason",
          "status",
          "detail"
        ],
        "title": "EvidenceRefusal",
        "type": "object"
      },
      "ExitCondition": {
        "additionalProperties": true,
        "properties": {
          "access_class": {
            "default": "effectful",
            "title": "Access Class",
            "type": "string"
          },
          "approved_by": {
            "default": "",
            "title": "Approved By",
            "type": "string"
          },
          "condition_id": {
            "title": "Condition Id",
            "type": "string"
          },
          "drafted_by": {
            "default": "",
            "title": "Drafted By",
            "type": "string"
          },
          "freshness": {
            "default": "revalidate",
            "title": "Freshness",
            "type": "string"
          },
          "goal_id": {
            "title": "Goal Id",
            "type": "string"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "predicate": {
            "additionalProperties": true,
            "description": "{lang, expr, ...}. A substring predicate must carry non-empty negative_guards.",
            "title": "Predicate",
            "type": "object"
          },
          "predicate_hash": {
            "default": "",
            "title": "Predicate Hash",
            "type": "string"
          },
          "schema_hash": {
            "default": "",
            "title": "Schema Hash",
            "type": "string"
          },
          "verifier_server": {
            "default": "",
            "title": "Verifier Server",
            "type": "string"
          },
          "verifier_tool": {
            "default": "",
            "title": "Verifier Tool",
            "type": "string"
          },
          "verify": {
            "additionalProperties": true,
            "title": "Verify",
            "type": "object"
          }
        },
        "required": [
          "condition_id",
          "goal_id",
          "platform",
          "predicate"
        ],
        "title": "ExitCondition",
        "type": "object"
      },
      "FloorFact": {
        "additionalProperties": false,
        "description": "ONE DETERMINISTIC READING submitted alongside a batch - an observation, not a write.\n\nA write records what the record SAYS. A floor fact records what something MEASURED: a rule your\nown code ran, a check that fired or did not. The charter can then hold a gate neither the\nconversation nor the model can talk its way out of - most usefully an escalation flag that a\nfired floor decides even when the model's own reading was never taken.\n\n\u26a0\ufe0f THE POLARITY IS NOT WHAT IT LOOKS LIKE, and it is the single easiest thing to get wrong\nhere. `state` describes whether the RULE FIRED, not whether the world is well:\n\n    ok        the rule affirmatively FIRED - the condition it watches for is present\n    violated  the rule affirmatively did NOT fire\n    unknown   unread; the same as sending nothing at all\n\nSo a red flag that has TRIPPED is `ok`. Reading it the intuitive way round inverts every gate\nbuilt on it, and inverts it silently, because both values are legal.\n\nTHE KEY IS YOURS. Cohort does not interpret it - it is your charter's vocabulary, exactly as\nwith a write's field name. Namespaces reserved for attested instrument readings are refused by\nthe engine, not by us, and that refusal now reaches you (#1880) instead of looking like success.",
        "example": {
          "key": "floor.red_flag",
          "state": "ok"
        },
        "properties": {
          "edition": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional. The edition of the reference the instrument read against (for example a code-list edition). Recorded with the reading, so the record says not only which measurement ruled but against what.",
            "title": "Edition"
          },
          "key": {
            "description": "Which reading this is. YOUR vocabulary \u2014 Cohort does not interpret it. A key in a namespace reserved for attested instrument readings is refused by the engine (`submit.reserved_observation`), because a reading the graded party could author is not a measurement.",
            "title": "Key",
            "type": "string"
          },
          "quote": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WriteAnchor"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional. Where in the turn text the reading came from, when it came from text at all."
          },
          "source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The instrument that produced this reading, as you name it (a checker and its version). REQUIRED for a reading in a namespace reserved for attested instrument readings: an attested reading that names no source is refused (`submit.unattributed_observation`), because the record must be able to say which measurement ruled. Recorded with the reading.",
            "title": "Source"
          },
          "state": {
            "description": "WHETHER THE RULE FIRED \u2014 not whether the news is good. `ok` = it affirmatively FIRED (the watched condition is present), `violated` = it affirmatively did NOT fire, `unknown` = unread, identical to omitting the fact. A tripped red flag is `ok`. Constrained here rather than left to the engine on purpose: the engine reads this value as an opaque tri-state and cannot refuse an unrecognised one, so anything else would be accepted and then silently count as unread \u2014 which is exactly how this door lost a safety reading before.",
            "enum": [
              "unknown",
              "ok",
              "violated"
            ],
            "title": "State",
            "type": "string"
          }
        },
        "required": [
          "key",
          "state"
        ],
        "title": "FloorFact",
        "type": "object"
      },
      "Goal": {
        "additionalProperties": true,
        "properties": {
          "budgets": {
            "additionalProperties": true,
            "title": "Budgets",
            "type": "object"
          },
          "completion_contract": {
            "default": "predicate_satisfaction",
            "title": "Completion Contract",
            "type": "string"
          },
          "created_by": {
            "default": "",
            "title": "Created By",
            "type": "string"
          },
          "description": {
            "default": "",
            "title": "Description",
            "type": "string"
          },
          "goal_id": {
            "description": "`goal-<12 hex>` unless you supplied one.",
            "title": "Goal Id",
            "type": "string"
          },
          "platform": {
            "description": "Your workspace. Server-injected from the credential.",
            "title": "Platform",
            "type": "string"
          },
          "state": {
            "default": "draft",
            "description": "draft | active | done \u2014 free-form, not enforced.",
            "title": "State",
            "type": "string"
          }
        },
        "required": [
          "goal_id",
          "platform"
        ],
        "title": "Goal",
        "type": "object"
      },
      "GoalCreate": {
        "additionalProperties": false,
        "description": "POST /v1/goals. Every field optional: an empty body creates a draft goal with a fresh id.",
        "properties": {
          "budgets": {
            "additionalProperties": true,
            "title": "Budgets",
            "type": "object"
          },
          "created_by": {
            "default": "",
            "title": "Created By",
            "type": "string"
          },
          "description": {
            "default": "",
            "title": "Description",
            "type": "string"
          },
          "goal_id": {
            "default": "",
            "description": "Omit for a server-generated `goal-<12 hex>`. Supplying one upserts YOUR OWN row; the store refuses ids belonging to another workspace.",
            "title": "Goal Id",
            "type": "string"
          },
          "state": {
            "default": "draft",
            "description": "Empty string is treated as 'draft'.",
            "title": "State",
            "type": "string"
          }
        },
        "title": "GoalCreate",
        "type": "object"
      },
      "GoalEnvelope": {
        "additionalProperties": true,
        "example": {
          "goal": {
            "budgets": {
              "gpu_seconds": 1800
            },
            "completion_contract": "predicate_satisfaction",
            "created_by": "applejack",
            "description": "Migrate acme to the /v1 session API",
            "goal_id": "goal-4f2a91c07b3e",
            "platform": "acme",
            "state": "active"
          }
        },
        "properties": {
          "goal": {
            "$ref": "#/components/schemas/Goal"
          }
        },
        "required": [
          "goal"
        ],
        "title": "GoalEnvelope",
        "type": "object"
      },
      "GoalList": {
        "additionalProperties": true,
        "example": {
          "count": 1,
          "goals": [
            {
              "budgets": {
                "gpu_seconds": 1800
              },
              "completion_contract": "predicate_satisfaction",
              "created_by": "applejack",
              "description": "Migrate acme to the /v1 session API",
              "goal_id": "goal-4f2a91c07b3e",
              "platform": "acme",
              "state": "active"
            }
          ],
          "platform": "acme"
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "goals": {
            "items": {
              "$ref": "#/components/schemas/Goal"
            },
            "title": "Goals",
            "type": "array"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          }
        },
        "required": [
          "platform",
          "count"
        ],
        "title": "GoalList",
        "type": "object"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "title": "HTTPValidationError",
        "type": "object"
      },
      "Identity": {
        "additionalProperties": true,
        "description": "Who the presented credential resolves to.\n\n`extra=\"allow\"` because this is a published response a consumer generates against: adding a\nfield must not be a breaking change for a client that pins the model.",
        "example": {
          "attributed": "customer",
          "credential": "api_key",
          "grants": {
            "acme": [
              "charters.read",
              "sessions.drive"
            ]
          },
          "label": "acme production",
          "org_id": "",
          "prefix": "ck_acme_",
          "workspace": "acme",
          "workspaces": []
        },
        "properties": {
          "attributed": {
            "default": "customer",
            "description": "How this call was RECORDED: 'probe' (the caller passed ?probe=true, so it landed in last_probed_at), 'customer' (ordinary traffic, last_used_at), or 'none' (a human token, which has no key row to stamp). Reported so a platform can verify its own attribution actually took effect rather than assuming it.",
            "title": "Attributed",
            "type": "string"
          },
          "credential": {
            "description": "Which credential kind was presented: 'api_key' or 'user'.",
            "title": "Credential",
            "type": "string"
          },
          "grants": {
            "anyOf": [
              {
                "additionalProperties": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "#1427 \u2014 this credential's EFFECTIVE permissions matrix: {workspace|'*': [verb]}. Derived, never the raw stored column: a key minted before the matrix existed reports the authority it actually has rather than `null`. `'*'` means every workspace of this key's org, resolved live. null (not {}) for a human token, which has no matrix \u2014 a human's authority is the account itself. \u26a0\ufe0f This reports what the credential HOLDS, not what any given call will do: quota, an archived workspace or the goal engine can still refuse a request whose verb is held.",
            "title": "Grants"
          },
          "label": {
            "default": "",
            "description": "The key's human label, if it has one.",
            "title": "Label",
            "type": "string"
          },
          "org_id": {
            "default": "",
            "description": "Account the human caller belongs to.",
            "title": "Org Id",
            "type": "string"
          },
          "prefix": {
            "default": "",
            "description": "Non-secret display prefix of the presented key, the same span a key listing shows. Lets an integrator confirm WHICH key their config actually presents.",
            "title": "Prefix",
            "type": "string"
          },
          "workspace": {
            "description": "The ONE workspace an API key is bound to. Empty for a human token, which carries several \u2014 see `workspaces`. A key that authenticates but names an unexpected workspace is accepted and still misconfigured, which is why this is reported.",
            "title": "Workspace",
            "type": "string"
          },
          "workspaces": {
            "description": "Every workspace a human caller's account owns. Empty for an API key.",
            "items": {
              "type": "string"
            },
            "title": "Workspaces",
            "type": "array"
          }
        },
        "required": [
          "credential",
          "workspace"
        ],
        "title": "Identity",
        "type": "object"
      },
      "InputAccepted": {
        "additionalProperties": true,
        "example": {
          "accepted": true,
          "session_id": "sess-2f9a4c1e"
        },
        "properties": {
          "accepted": {
            "description": "ACCEPTED, not processed \u2014 202. The turn runs asynchronously; watch the SSE stream for its result.",
            "title": "Accepted",
            "type": "boolean"
          },
          "evidence_refused": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/EvidenceRefusal"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present only when a message was delivered but could NOT be recorded as evidence, so values quoted only from it cannot be verified (#2154). Each entry is also published on the event stream as `evidence_refused`. null when everything was recorded.",
            "title": "Evidence Refused"
          },
          "note": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present when delivery is not the warm ~1s path \u2014 e.g. the worker is still booting and the input will be delivered the moment it connects (#1637). Absent on the warm path, so existing consumers see the exact response they always did.",
            "title": "Note"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "accepted"
        ],
        "title": "InputAccepted",
        "type": "object"
      },
      "InputMessage": {
        "additionalProperties": true,
        "description": "One turn you SEND. The mirror of Choice, and it had the same defect.\n\napplejack reported that `choices` was an untyped blob - the schema said the answer was in there\nsomewhere but not where. `messages` was the same thing on the REQUEST side: an array of\nundescribed objects, on the field a consumer has to CONSTRUCT rather than merely read. Typing\nthe response and leaving the request untyped fixes the easier half.\n\nPermissive on purpose. `content` is a string for ordinary text and a LIST for multimodal parts,\nand extra=\"allow\" lets `name`, `tool_call_id` and anything OpenAI adds through untouched -\ndescribing the common shape must not forbid the rest.",
        "properties": {
          "content": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "items": {},
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Text, or a list of content parts for multimodal input.",
            "title": "Content"
          },
          "role": {
            "description": "system | user | assistant | tool.",
            "title": "Role",
            "type": "string"
          }
        },
        "required": [
          "role"
        ],
        "title": "InputMessage",
        "type": "object"
      },
      "JobProgress": {
        "additionalProperties": true,
        "description": "Work done in the current stage, as a count of real things. Never an estimate and never\nderived from elapsed time.",
        "properties": {
          "done": {
            "description": "Units of work done so far in this stage.",
            "title": "Done",
            "type": "integer"
          },
          "total": {
            "description": "Units of work this stage has in all.",
            "title": "Total",
            "type": "integer"
          },
          "unit": {
            "description": "What is counted: `bytes` (a file received or sent), `rows` (rows checked), `entries` (rows and relations stored).",
            "title": "Unit",
            "type": "string"
          }
        },
        "required": [
          "done",
          "total",
          "unit"
        ],
        "title": "JobProgress",
        "type": "object"
      },
      "JobStatus": {
        "additionalProperties": true,
        "description": "Where a piece of queued work stands. ONE vocabulary for every kind of work that can wait, so a\nclient that understands it for sessions understands it for anything that queues later.\n\nDeliberately carries no queue position and no capacity figure: those describe the platform's\nload, not your work.",
        "example": {
          "progress": {
            "done": 41200,
            "total": 100000,
            "unit": "rows"
          },
          "queued_at": "2026-09-17T15:04:05+00:00",
          "reason": "",
          "retryable": false,
          "stage": "validating",
          "state": "running"
        },
        "properties": {
          "progress": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/JobProgress"
              },
              {
                "type": "null"
              }
            ],
            "description": "Work done in the current stage, when the kind of work reports it; the percentage is done / total. Null for work that does not report progress (sessions)."
          },
          "queued_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "When the work was accepted.",
            "title": "Queued At"
          },
          "reason": {
            "default": "",
            "description": "Why it ended or failed. Empty otherwise.",
            "title": "Reason",
            "type": "string"
          },
          "result": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "What `ready` work produced, or for `failed` work the details behind `reason` (for a pack upload: a code, the offending rows with their positions, and how many there are in all). Null when there is nothing to add.",
            "title": "Result"
          },
          "retryable": {
            "default": false,
            "description": "For `failed` only: true when trying the same request again can succeed without changing anything, false when something must change first (for example, credit).",
            "title": "Retryable",
            "type": "boolean"
          },
          "stage": {
            "default": "",
            "description": "The named step while `running`, e.g. `spawning`.",
            "title": "Stage",
            "type": "string"
          },
          "state": {
            "description": "`queued`: waiting for capacity; nothing has started and nothing is billed. `running`: being started; `stage` names the step. `ready`: started and running. `ended`: finished or cancelled; `reason` says which. `failed`: could not be done; `reason` says why, and `retryable` says whether the same request can succeed later.",
            "enum": [
              "queued",
              "running",
              "ready",
              "ended",
              "failed"
            ],
            "title": "State",
            "type": "string"
          }
        },
        "required": [
          "state"
        ],
        "title": "JobStatus",
        "type": "object"
      },
      "KeyAllowance": {
        "additionalProperties": true,
        "description": "What the CALLING key may spend, and nothing else (#4113).\n\nDeliberately says nothing about the workspace's balance, the account's position, or any other\nkey. A key asking what it may spend is not asking, and must not be told, how much money sits\none rung up.",
        "example": {
          "key_id": "key-3c7f10ab92de",
          "state": "never_funded",
          "would_refuse_with": "key_wallet_absent"
        },
        "properties": {
          "allowance_usd": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "The spendable figure, or null. \ud83d\udd34 null IS NOT ZERO and is not one state: it is never_funded, not_finite or unknown, and `state` is what tells you which. Branch on `state`, never on this being null. `0.0` is a real number and means exhausted.",
            "title": "Allowance Usd"
          },
          "key_id": {
            "description": "The key that made this request. You cannot ask about another.",
            "title": "Key Id",
            "type": "string"
          },
          "state": {
            "description": "WHAT THIS KEY'S OWN MONEY RUNG SAYS, as one of five. The first four mirror the gate's own branches exactly, so this read and the refusal you would get cannot disagree:\n\n  never_funded  no allowance was ever reserved to this key. It cannot start a run. The remedy is for an account holder to appoint some.\n  funded        there is a spendable allowance here. `allowance_usd` is it.\n  exhausted     an allowance WAS reserved and is now at or below zero. The remedy is a top-up, which is a different act from the first.\n  not_finite    the stored balance is not a number the gate may reason about. It fails CLOSED, so the key is refused. Report it; do not treat it as any amount.\n  unknown       the wallet could not be read. \ud83d\udd34 THIS IS NOT A CLAIM ABOUT THE KEY. Do not act on it as zero and do not reserve against it.\n\n\ud83d\udd11 never_funded and exhausted are kept apart on purpose. They refuse alike at the gate and mean opposite things to a person: one says a reservation never happened, the other says refill.",
            "title": "State",
            "type": "string"
          },
          "would_refuse_with": {
            "default": "",
            "description": "The exact reason the credit gate would give for THIS rung right now, or \"\" when this rung is clear: `key_wallet_absent`, `key_budget_exhausted`, `key_balance_not_finite`. Given so a caller can match what they read here against what they would be told at the door, instead of inferring one from the other.\n\n\u26a0\ufe0f \"\" HERE DOES NOT MEAN A RUN WILL START. It means the KEY's money rung is clear. The workspace rung, the account rung and the meters are checked too and are not reported here. A clear key rung is necessary, never sufficient.",
            "title": "Would Refuse With",
            "type": "string"
          }
        },
        "required": [
          "key_id",
          "state"
        ],
        "title": "KeyAllowance",
        "type": "object"
      },
      "KeyCredit": {
        "additionalProperties": true,
        "description": "What one reservation moved, and where both sides stand afterwards.",
        "example": {
          "key_balance_usd": 40.0,
          "key_id": "key_01J8ZQ4W",
          "moved": true,
          "workspace": "acme",
          "workspace_balance_usd": 210.0
        },
        "properties": {
          "key_balance_usd": {
            "description": "What this key can now spend. It is the ONLY wallet a session of this key reads (#1876), so this number alone decides whether it can run.",
            "title": "Key Balance Usd",
            "type": "number"
          },
          "key_id": {
            "title": "Key Id",
            "type": "string"
          },
          "moved": {
            "description": "FALSE means this idempotency key was already applied \u2014 the transfer is not repeated and the balances below are the settled ones. It is not a failure.",
            "title": "Moved",
            "type": "boolean"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          },
          "workspace_balance_usd": {
            "description": "What is left in the workspace to reserve to this or any other key in it.",
            "title": "Workspace Balance Usd",
            "type": "number"
          }
        },
        "required": [
          "workspace",
          "key_id",
          "moved",
          "key_balance_usd",
          "workspace_balance_usd"
        ],
        "title": "KeyCredit",
        "type": "object"
      },
      "KeyCreditRequest": {
        "additionalProperties": false,
        "description": "Reserve credit from a workspace's wallet to ONE key in it, or take it back. (#1876)\n\nTHE SECOND HALF OF \"MONEY TRAVELS DOWN BY EXPLICIT ACT\". The account appoints to a workspace\n(WorkspaceCreditRequest); this reserves from a workspace to a key, and a key is the only rung a\nsession can spend. There is no third step and no way to skip one - a workspace with no wallet\ncannot reserve, and the refusal says to appoint to the workspace first.\n\n\u26a0\ufe0f `extra=\"forbid\"`, deliberately and not by convention. A money write that silently DISCARDED\na field a caller believed it sent is the worst place in the product for that failure: the\ncaller has been told the opposite of the truth about where money went. Measured elsewhere in\nthis API (#1882) - a dropped field returned 200 and vanished, and the caller could not tell\naccepted from discarded.",
        "properties": {
          "amount_usd": {
            "description": "POSITIVE reserves money from this key's WORKSPACE wallet to the key; NEGATIVE returns it to the workspace. One signed operation, same as the workspace appointment \u2014 how an owner divides their workspace's money between their own keys is theirs to change. 0 is refused: it moves nothing, and recording a movement that did not happen makes a ledger harder to read. The ACCOUNT is never the source here; if the workspace has no wallet the transfer is refused rather than reaching a rung up.",
            "title": "Amount Usd",
            "type": "number"
          },
          "idempotency_key": {
            "description": "YOURS, not ours, and REQUIRED. A money write is the one place where 'applied twice' is unrecoverable, so a retry must carry the SAME key to be a retry rather than a second transfer.",
            "maxLength": 200,
            "minLength": 8,
            "title": "Idempotency Key",
            "type": "string"
          },
          "internal_note": {
            "default": "",
            "description": "Operator-only note. NEVER shown to the holder of the key.",
            "maxLength": 500,
            "title": "Internal Note",
            "type": "string"
          }
        },
        "required": [
          "amount_usd",
          "idempotency_key"
        ],
        "title": "KeyCreditRequest",
        "type": "object"
      },
      "KeyList": {
        "additionalProperties": true,
        "example": {
          "count": 1,
          "keys": [
            {
              "created_at": "2026-08-07T09:14:22Z",
              "created_by": "applejack",
              "key_id": "key-3c7f10ab92de",
              "label": "acme-prod",
              "last_used_at": "2026-08-07T11:02:40Z",
              "prefix": "coh_live_8Kq2",
              "revoked": false,
              "webhook_disabled": false,
              "webhook_failures": 0,
              "webhook_url": "https://api.acme.example/hooks/cohort",
              "workspace": "acme"
            }
          ],
          "workspace": "acme"
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "keys": {
            "items": {
              "$ref": "#/components/schemas/ApiKeyRecord"
            },
            "title": "Keys",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "workspace",
          "count"
        ],
        "title": "KeyList",
        "type": "object"
      },
      "KeyRevoked": {
        "additionalProperties": true,
        "example": {
          "key_id": "key-3c7f10ab92de",
          "revoked": true
        },
        "properties": {
          "key_id": {
            "title": "Key Id",
            "type": "string"
          },
          "revoked": {
            "description": "Always true \u2014 revocation takes effect on the key's NEXT request; there is no cached-credential window.",
            "title": "Revoked",
            "type": "boolean"
          }
        },
        "required": [
          "key_id",
          "revoked"
        ],
        "title": "KeyRevoked",
        "type": "object"
      },
      "LiveTokenResponse": {
        "description": "The live-stream credential handed to a signed-in browser (#1405).\n\n`token` is EMPTY when the deployment has no live path configured - a 200 with no token, not an\nerror, because a dashboard on its polling floor is a working dashboard. The client reads the\nempty string as \"stay on polling and say so\" rather than as a failure to retry.",
        "example": {
          "hub": "https://mercure.projexlabs.com/.well-known/mercure",
          "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNvaG9ydC1vcmNoZXN0cmF0b3IifQ...",
          "topics": [
            "cohort/acme/sessions",
            "cohort/acme/runs"
          ],
          "ttl_s": 900
        },
        "properties": {
          "hub": {
            "default": "",
            "description": "The hub to subscribe to. Empty = no live path.",
            "title": "Hub",
            "type": "string"
          },
          "token": {
            "default": "",
            "description": "Subscribe-only JWT, minutes-long. Empty = no live path.",
            "title": "Token",
            "type": "string"
          },
          "topics": {
            "description": "Exactly this workspace's topics \u2014 the same set the token authorizes.",
            "items": {
              "type": "string"
            },
            "title": "Topics",
            "type": "array"
          },
          "ttl_s": {
            "default": 0,
            "description": "Token lifetime. The client renews ahead of it, never after.",
            "title": "Ttl S",
            "type": "integer"
          }
        },
        "title": "LiveTokenResponse",
        "type": "object"
      },
      "ManagementKeyList": {
        "additionalProperties": true,
        "description": "This ACCOUNT's management keys. (#1191)\n\nKeyed by org, not workspace, because that is the scope a management key actually has. The\n`workspace` field on each record carries the account sentinel rather than a real slug - a\nmanagement key belongs to no workspace, and showing one would suggest it could spend there.",
        "example": {
          "count": 1,
          "keys": [
            {
              "created_at": "2026-08-18T20:31:05Z",
              "created_by": "a1b2c3d4-user-sub",
              "key_id": "key-9b2e44c1a077",
              "kind": "management",
              "label": "the customer onboarding automation",
              "last_used_at": "2026-08-18T20:44:12Z",
              "prefix": "cm_account_7Xd1",
              "revoked": false,
              "workspace": "*account*"
            }
          ],
          "org_id": "org-4a1f9c"
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "keys": {
            "items": {
              "$ref": "#/components/schemas/ApiKeyRecord"
            },
            "title": "Keys",
            "type": "array"
          },
          "org_id": {
            "title": "Org Id",
            "type": "string"
          }
        },
        "required": [
          "org_id",
          "count"
        ],
        "title": "ManagementKeyList",
        "type": "object"
      },
      "McpServerSpec": {
        "description": "One MCP server to wire into the session. `name` namespaces its tools in the worker.",
        "properties": {
          "name": {
            "default": "",
            "title": "Name",
            "type": "string"
          },
          "token": {
            "default": "",
            "title": "Token",
            "type": "string"
          },
          "url": {
            "title": "Url",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "title": "McpServerSpec",
        "type": "object"
      },
      "MintKeyRequest": {
        "description": "Deliberately absent: `workspace` (taken from the caller's verified claim - a body field\nwould be a way to mint into someone else's workspace) and any expiry knob (not built yet; a\nfield that silently does nothing is worse than no field). The scope knob this docstring used\nto name as unbuilt is now `scopes` below (#1625).",
        "properties": {
          "grants": {
            "anyOf": [
              {
                "additionalProperties": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "#1427 permissions matrix: {workspace|'*': [verb]}. Workspace verbs are charters.read, charters.write, credit.reserve, keys.mint, sessions.drive, sessions.read, usage.read. '*' means every workspace of THIS key's org \u2014 resolved as a live query, so workspaces created later are included. There is also an ACCOUNT scope, keyed '@account', carrying account.billing.read \u2014 the account's own balance and financial history, which is DELIBERATELY not reachable through any workspace verb (a key scoped to one workspace must not read the account's money). \u26a0\ufe0f Holding a verb on a WORKSPACE is not holding it on the ACCOUNT: an account-rung read refuses a workspace-scoped key even when the verb names match. OMITTING this field is not the same as sending {}: omitted means 'today's default authority for this kind of key', while {} mints a key that can do nothing. Those defaults today are \u2014 usage key: charters.read, charters.write, sessions.drive, sessions.read, usage.read; management key: charters.read, keys.mint, usage.read. They are DERIVED and can widen when a verb is added, so send an explicit map if you want to inherit nothing. A mint may only grant a SUBSET of what the minting credential holds; anything beyond it is refused 403 naming exactly which (workspace, verb) pairs exceeded. \ud83d\udd34 `credit.reserve` is stricter than the subset rule: it can only be delegated by a credential that ACTUALLY HOLDS it (a signed-in human is exempt), because minting a money-mover is using it one call later. No key kind carries it by default.",
            "title": "Grants"
          },
          "label": {
            "default": "",
            "description": "Human-readable name, e.g. 'acme production'. Shown in the key list.",
            "maxLength": 120,
            "title": "Label",
            "type": "string"
          },
          "scopes": {
            "description": "#1625 opt-in powers, DEFAULT NONE (use only: open and drive sessions). Two grantable scopes exist. 'governance:author': saving charters, retiring them, and registering packs \u2014 a key minted without it can never change the rules its sessions are graded by, even if it leaks. 'session:facts': attesting reserved-writer substrate facts via POST /v1/sessions/{id}/submit, only on the key's own workspace's sessions, whoever writes the record (worker-driven sessions included, ruled 2026-09-23) \u2014 lets your own harness vouch for what a task is, never lets it author rules. Unknown scopes are refused with 422 at mint, never stored and never silently dropped. Humans authoring from the dashboard need no scope; pack sign-off remains a person's act that no scope grants.",
            "items": {
              "type": "string"
            },
            "title": "Scopes",
            "type": "array"
          },
          "webhook_url": {
            "default": "",
            "description": "Optional https endpoint Cohort POSTs terminal session events to (session.ended, session.turn_ended). Set HERE, at mint, and immutable afterwards: there is no endpoint to register or change a webhook, so a leaked key cannot redirect your events, and revoking the key disables its webhook with it. Must resolve to a public address on port 443/8443.",
            "title": "Webhook Url",
            "type": "string"
          }
        },
        "title": "MintKeyRequest",
        "type": "object"
      },
      "MintKeyResponse": {
        "properties": {
          "api_key": {
            "description": "THE PLAINTEXT KEY. Returned exactly once and never recoverable \u2014 only its hash is stored. Save it now; if it is lost, revoke this key and mint another.",
            "title": "Api Key",
            "type": "string"
          },
          "grants": {
            "anyOf": [
              {
                "additionalProperties": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Grants"
          },
          "key_id": {
            "title": "Key Id",
            "type": "string"
          },
          "label": {
            "title": "Label",
            "type": "string"
          },
          "prefix": {
            "title": "Prefix",
            "type": "string"
          },
          "scopes": {
            "items": {
              "type": "string"
            },
            "title": "Scopes",
            "type": "array"
          },
          "webhook_secret": {
            "default": "",
            "description": "HMAC-SHA256 signing secret for this key's webhook. Like the key itself, returned ONCE. Verify deliveries with: sha256_hmac(secret, f'{X-Cohort-Timestamp}.{raw_body}') == X-Cohort-Signature.",
            "title": "Webhook Secret",
            "type": "string"
          },
          "webhook_url": {
            "default": "",
            "title": "Webhook Url",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "key_id",
          "workspace",
          "label",
          "prefix",
          "api_key"
        ],
        "title": "MintKeyResponse",
        "type": "object"
      },
      "MintManagementKeyRequest": {
        "description": "`label` only - the same deliberate absences as MintKeyRequest, plus one more: there is no\n`org_id` field. The account comes from the signed-in human's own membership, so there is nothing\nto send that would mint a credential administering somebody else's account.",
        "properties": {
          "label": {
            "default": "",
            "description": "Human-readable name, e.g. 'the customer onboarding automation'.",
            "maxLength": 120,
            "title": "Label",
            "type": "string"
          }
        },
        "title": "MintManagementKeyRequest",
        "type": "object"
      },
      "MintManagementKeyResponse": {
        "properties": {
          "api_key": {
            "description": "THE PLAINTEXT MANAGEMENT KEY (cm_). Returned exactly once. It can create and archive workspaces in this account and mint usage keys inside them \u2014 it CANNOT start sessions or spend, and it cannot mint or revoke another management key.",
            "title": "Api Key",
            "type": "string"
          },
          "key_id": {
            "title": "Key Id",
            "type": "string"
          },
          "label": {
            "title": "Label",
            "type": "string"
          },
          "org_id": {
            "title": "Org Id",
            "type": "string"
          },
          "prefix": {
            "title": "Prefix",
            "type": "string"
          }
        },
        "required": [
          "key_id",
          "org_id",
          "label",
          "prefix",
          "api_key"
        ],
        "title": "MintManagementKeyResponse",
        "type": "object"
      },
      "ModelsResponse": {
        "description": "The pickable aliases, what each one can do, and what it will accept.\n\n\u26a0\ufe0f THIS USED TO BE \"DELIBERATELY MINIMAL\" AND THAT WAS THE DEFECT (#2072). It published\n`{alias, mode, vision}` on the reasoning that the gateway owns model metadata - true, and it\nleft a caller with no published quantity that bounds a single request, so one sized their cost\nmodel on `max_tokens` instead, which is a per-session total checked between turns. The aliases\nare also the only place the NAME SHAPE is visible: this catalogue is plain names, so the\nfamiliar `gemma4:31b` form does not resolve and is refused at 422 - a trap that cost one\nintegrator a working feature.\n\nRates are still not here: GET /v1/pricing owns those, including peak windows and whether a\nrate is a placeholder. This surface answers what you may run and what it will take.",
        "example": {
          "default": "",
          "models": [
            {
              "alias": "gemma4-31b",
              "basis": "tokens",
              "context_length": 262144,
              "context_length_source": "catalog",
              "mode": "chat",
              "provider": "ollama-cloud",
              "vision": true
            }
          ]
        },
        "properties": {
          "default": {
            "deprecated": true,
            "description": "Always empty. There is no default model: every session names the model it runs on, and a session that names none is refused (422). Kept only so existing readers of this field do not break.",
            "title": "Default",
            "type": "string"
          },
          "models": {
            "description": "The served aliases, curated by the deployment's allowlist when one is set.",
            "items": {
              "$ref": "#/components/schemas/ServedModel"
            },
            "title": "Models",
            "type": "array"
          }
        },
        "required": [
          "models",
          "default"
        ],
        "title": "ModelsResponse",
        "type": "object"
      },
      "MoneyMoverAttestation": {
        "additionalProperties": true,
        "description": "The answer to \"who says this key may move money\" after a POST or DELETE (#4112).",
        "example": {
          "key_id": "key-3c7f10ab92de",
          "money_mover_by": "user:auth0|61f2c"
        },
        "properties": {
          "key_id": {
            "title": "Key Id",
            "type": "string"
          },
          "money_mover_by": {
            "description": "Who attested, or \"\" after a withdrawal. The same value the key listing shows.",
            "title": "Money Mover By",
            "type": "string"
          }
        },
        "required": [
          "key_id",
          "money_mover_by"
        ],
        "title": "MoneyMoverAttestation",
        "type": "object"
      },
      "OpenApiDocument": {
        "additionalProperties": true,
        "description": "This API's published contract: the committed docs/openapi.json, served byte for byte. (#4242)\n\nThe document a consumer generates a client from. It is the file in the repository, captured\nfrom the commit tree at deploy and refused by CI when stale, and the route serves the same\nbytes, never a live render of the running process. The shape below is OpenAPI 3.1; the `openapi`, `info`,\n`paths` and `components` members are the ones every reader relies on, and everything else the\ndocument carries rides through untouched.",
        "example": {
          "components": {
            "schemas": {
              "CreateSessionRequest": {
                "title": "CreateSessionRequest"
              }
            }
          },
          "info": {
            "title": "Cohort",
            "version": "0.1.0"
          },
          "openapi": "3.1.0",
          "paths": {
            "/v1/sessions": {
              "post": {
                "summary": "Start a session"
              }
            }
          }
        },
        "properties": {
          "components": {
            "additionalProperties": true,
            "title": "Components",
            "type": "object"
          },
          "info": {
            "additionalProperties": true,
            "title": "Info",
            "type": "object"
          },
          "openapi": {
            "title": "Openapi",
            "type": "string"
          },
          "paths": {
            "additionalProperties": true,
            "title": "Paths",
            "type": "object"
          }
        },
        "required": [
          "openapi"
        ],
        "title": "OpenApiDocument",
        "type": "object"
      },
      "PackBody": {
        "additionalProperties": true,
        "description": "A pack fetched back - from the one body store.\n\nBefore this door there was no pack store, so pack contents were write-only from every\nsurface: registered, referenced, signed off, and never readable again.",
        "example": {
          "digest": "p1:9f1e77ab\u2026",
          "pack": {
            "display_name": "intake-2026-08",
            "entries": [
              {
                "id": "doc-1"
              }
            ]
          },
          "workspace": "acme"
        },
        "properties": {
          "digest": {
            "title": "Digest",
            "type": "string"
          },
          "pack": {
            "additionalProperties": true,
            "title": "Pack",
            "type": "object"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "digest",
          "workspace"
        ],
        "title": "PackBody",
        "type": "object"
      },
      "PackDocument": {
        "additionalProperties": true,
        "description": "An evidence pack. Free-form for the same reason a charter is.",
        "example": {
          "documents": [
            {
              "id": "doc-1",
              "sha": "\u2026"
            }
          ],
          "name": "intake-2026-08"
        },
        "properties": {},
        "title": "PackDocument",
        "type": "object"
      },
      "PackList": {
        "additionalProperties": true,
        "example": {
          "packs": [
            {
              "digest": "sha256:9f1e77ab",
              "signed": true,
              "workspace": "acme"
            }
          ]
        },
        "properties": {
          "packs": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Packs",
            "type": "array"
          }
        },
        "title": "PackList",
        "type": "object"
      },
      "PackSaved": {
        "additionalProperties": true,
        "example": {
          "digest": "sha256:9f1e77ab",
          "signed": false,
          "workspace": "acme"
        },
        "properties": {
          "digest": {
            "title": "Digest",
            "type": "string"
          },
          "signed": {
            "default": false,
            "title": "Signed",
            "type": "boolean"
          },
          "signer": {
            "default": "",
            "title": "Signer",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "digest",
          "workspace"
        ],
        "title": "PackSaved",
        "type": "object"
      },
      "PackUpload": {
        "example": {
          "bytes_total": 31457280,
          "dismissed": false,
          "filename": "catalogue-2026-q3.json",
          "max_bytes": 67108864,
          "missing_parts": [],
          "part_bytes": 4194304,
          "parts_total": 8,
          "received_bytes": 31457280,
          "status": {
            "progress": {
              "done": 41200,
              "total": 100000,
              "unit": "rows"
            },
            "queued_at": "2026-09-17T09:12:44.913220+00:00",
            "reason": "",
            "retryable": false,
            "stage": "validating",
            "state": "running"
          },
          "upload_id": "upl_6c1f9a20b7d44e83a95c",
          "workspace": "acme"
        },
        "properties": {
          "bytes_total": {
            "title": "Bytes Total",
            "type": "integer"
          },
          "dismissed": {
            "default": false,
            "description": "Taken off the list by a person. Kept, and readable by id.",
            "title": "Dismissed",
            "type": "boolean"
          },
          "filename": {
            "title": "Filename",
            "type": "string"
          },
          "max_bytes": {
            "description": "The largest file this deployment accepts.",
            "title": "Max Bytes",
            "type": "integer"
          },
          "missing_parts": {
            "description": "While uploading: part numbers not yet received (first 100). Send only these to resume after a dropped connection.",
            "items": {
              "type": "integer"
            },
            "title": "Missing Parts",
            "type": "array"
          },
          "part_bytes": {
            "description": "Every part is exactly this size except the last.",
            "title": "Part Bytes",
            "type": "integer"
          },
          "parts_total": {
            "title": "Parts Total",
            "type": "integer"
          },
          "received_bytes": {
            "description": "Bytes stored so far: what `uploading` progress counts.",
            "title": "Received Bytes",
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "upload_id": {
            "title": "Upload Id",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "upload_id",
          "workspace",
          "filename",
          "bytes_total",
          "part_bytes",
          "parts_total",
          "received_bytes",
          "max_bytes",
          "status"
        ],
        "title": "PackUpload",
        "type": "object"
      },
      "PackUploadCreate": {
        "properties": {
          "bytes": {
            "description": "The file's exact size in bytes.",
            "title": "Bytes",
            "type": "integer"
          },
          "filename": {
            "description": "The file's name, shown in the console. No path.",
            "title": "Filename",
            "type": "string"
          },
          "sha256": {
            "default": "",
            "description": "Optional: the file's sha256 as 64 lowercase hex. When given, completion refuses bytes that do not match it.",
            "title": "Sha256",
            "type": "string"
          }
        },
        "required": [
          "filename",
          "bytes"
        ],
        "title": "PackUploadCreate",
        "type": "object"
      },
      "PackUploadList": {
        "example": {
          "max_bytes": 67108864,
          "uploads": [
            {
              "bytes_total": 31457280,
              "dismissed": false,
              "filename": "catalogue-2026-q3.json",
              "max_bytes": 67108864,
              "missing_parts": [],
              "part_bytes": 4194304,
              "parts_total": 8,
              "received_bytes": 31457280,
              "status": {
                "progress": {
                  "done": 41200,
                  "total": 100000,
                  "unit": "rows"
                },
                "queued_at": "2026-09-17T09:12:44.913220+00:00",
                "reason": "",
                "retryable": false,
                "stage": "validating",
                "state": "running"
              },
              "upload_id": "upl_6c1f9a20b7d44e83a95c",
              "workspace": "acme"
            }
          ],
          "workspace": "acme"
        },
        "properties": {
          "max_bytes": {
            "description": "The largest file this deployment accepts, so a client can refuse a larger file before sending any of it.",
            "title": "Max Bytes",
            "type": "integer"
          },
          "uploads": {
            "description": "Newest first. Dismissed uploads are left out.",
            "items": {
              "$ref": "#/components/schemas/PackUpload"
            },
            "title": "Uploads",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "workspace",
          "max_bytes",
          "uploads"
        ],
        "title": "PackUploadList",
        "type": "object"
      },
      "PricingCard": {
        "additionalProperties": true,
        "description": "The rate card a workspace is billed against.\n\n\u26a0\ufe0f `estimated_models` WAS `list[str]` WHILE THE HANDLER RETURNED A DICT, so response-model\nvalidation rejected every single response - including the empty case, because pydantic v2 does\nnot coerce `{}` to `[]` either. The endpoint could not have returned 200 since it shipped. It\nis a dict now, keyed by model, valued by how many times that model was billed at the fallback:\nthe count is the point, since one stray call and a million are different problems.",
        "example": {
          "default_rate": 0.0004,
          "estimated_models": {
            "some-new-model": 12
          },
          "meters": {
            "usd_per_gib_second": 7e-08,
            "usd_per_worker_instance_second": 1.9e-05
          },
          "placeholder_models": [
            "glm-5"
          ],
          "rates": {
            "glm-5": {
              "basis": "tokens",
              "is_placeholder": true,
              "usd_per_mtok_in": 0.87,
              "usd_per_mtok_out": 3.41
            }
          },
          "unit": "usd_per_meter"
        },
        "properties": {
          "default_rate": {
            "description": "Legacy USD per GPU-second, for unpriced GPU models.",
            "title": "Default Rate",
            "type": "number"
          },
          "estimated_models": {
            "additionalProperties": true,
            "description": "Models with NO configured price, billed at the fallback, and how many times. Your invoice for these is an estimate.",
            "title": "Estimated Models",
            "type": "object"
          },
          "meter_caveats": {
            "additionalProperties": true,
            "description": "Per-meter measurement defects that make its cost figure unreliable \u2014 an overstated column, a missing billing subject, an under-count against the gateway. A rate is only as good as what it multiplies, and these are published rather than hidden.",
            "title": "Meter Caveats",
            "type": "object"
          },
          "meters": {
            "additionalProperties": true,
            "description": "What each metered dimension costs: token I/O per million, worker instance time and resident storage per second.",
            "title": "Meters",
            "type": "object"
          },
          "placeholder_models": {
            "description": "Models that ARE in the rate card but whose rate is a placeholder rather than a published quote. Distinct from estimated_models: we named these and still owe them a real number.",
            "items": {
              "type": "string"
            },
            "title": "Placeholder Models",
            "type": "array"
          },
          "rates": {
            "additionalProperties": true,
            "description": "Per model: its basis (tokens or gpu), its rates, and where the number came from. A model priced on `tokens` bills its I/O; one priced on `gpu` bills its time.",
            "title": "Rates",
            "type": "object"
          },
          "unit": {
            "default": "usd_per_meter",
            "description": "Cost is per-meter now, not a single unit \u2014 see `meters`. Kept for readers written against the GPU-second-only card.",
            "title": "Unit",
            "type": "string"
          }
        },
        "required": [
          "default_rate"
        ],
        "title": "PricingCard",
        "type": "object"
      },
      "RetentionPolicy": {
        "additionalProperties": true,
        "description": "What Cohort keeps, for how long, and what you can have removed. (#1200)\n\nGenerated from the same table the automatic sweep enforces, so the published answer cannot\ndrift from the behaviour - a policy document and the code that enforces it are two statements\nof one commitment, and the drifted document is a promise the system is not keeping.",
        "example": {
          "automatic_expiry": "Content in a windowed class is swept automatically once it is older than 90 days. You do not have to ask.",
          "caveat_deletion_cannot_unsend": "Metadata you supply reaches the model's context and is echoed to your webhook endpoint. Deleting our copy retracts neither.",
          "classes": [
            {
              "customer_deletable": true,
              "name": "session_record",
              "retained_for": "90 days",
              "what": "Per-session record: charter binding, end reason, seal verdict, and the opaque metadata you supplied at create.",
              "why": "This is the content you sent us."
            },
            {
              "customer_deletable": false,
              "name": "usage_rows",
              "retained_for": "the life of the workspace",
              "what": "The billing ledger.",
              "why": "Financial records \u2014 your invoice reconciles against them. NOT a durability promise; the ledger is capped fleet-wide."
            }
          ],
          "on_request_deletion": "DELETE /v1/sessions/{session_id}/content removes the stored content for one session immediately. Usage rows are KEPT.",
          "retention_days": 90
        },
        "properties": {
          "classes": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Classes",
            "type": "array"
          },
          "retention_days": {
            "title": "Retention Days",
            "type": "integer"
          }
        },
        "required": [
          "retention_days"
        ],
        "title": "RetentionPolicy",
        "type": "object"
      },
      "Run": {
        "additionalProperties": true,
        "description": "\u26a0\ufe0f `vkey_current` is WITHHELD from every tenant read - it is a live verifier credential.\n\nIts absence is deliberate, not an empty value. `vkey_attempt` and `vkey_spend_recorded` are\nreturned; the key itself never is.",
        "properties": {
          "goal_id": {
            "default": "",
            "title": "Goal Id",
            "type": "string"
          },
          "granted_gpu_seconds": {
            "default": 0,
            "title": "Granted Gpu Seconds",
            "type": "number"
          },
          "granted_usd": {
            "default": 0,
            "title": "Granted Usd",
            "type": "number"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "projex_user_id": {
            "default": "",
            "title": "Projex User Id",
            "type": "string"
          },
          "run_id": {
            "title": "Run Id",
            "type": "string"
          },
          "session_id": {
            "default": "",
            "title": "Session Id",
            "type": "string"
          },
          "spend_unknown": {
            "default": false,
            "description": "TRUE when spend could not be determined \u2014 treat the run's cost as UNKNOWN, not zero.",
            "title": "Spend Unknown",
            "type": "boolean"
          },
          "state": {
            "default": "",
            "title": "State",
            "type": "string"
          },
          "state_reason": {
            "default": "",
            "title": "State Reason",
            "type": "string"
          },
          "task_id": {
            "default": "",
            "title": "Task Id",
            "type": "string"
          },
          "vkey_attempt": {
            "default": 0,
            "title": "Vkey Attempt",
            "type": "integer"
          },
          "vkey_spend_recorded": {
            "default": false,
            "title": "Vkey Spend Recorded",
            "type": "boolean"
          }
        },
        "required": [
          "run_id",
          "platform"
        ],
        "title": "Run",
        "type": "object"
      },
      "RunsView": {
        "additionalProperties": true,
        "example": {
          "by_state": {
            "running": 1
          },
          "count": 1,
          "platform": "acme",
          "runs": [
            {
              "goal_id": "goal-4f2a91c07b3e",
              "granted_gpu_seconds": 1800,
              "granted_usd": 4.5,
              "platform": "acme",
              "projex_user_id": "usr-91b3",
              "run_id": "run-77c2e5a10bd4",
              "session_id": "sess-2f9a4c1e",
              "spend_unknown": false,
              "state": "running",
              "state_reason": "",
              "task_id": "task-8b1d33e0a95c",
              "vkey_attempt": 1,
              "vkey_spend_recorded": true
            }
          ]
        },
        "properties": {
          "by_state": {
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Run counts keyed by state.",
            "title": "By State",
            "type": "object"
          },
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "runs": {
            "items": {
              "$ref": "#/components/schemas/Run"
            },
            "title": "Runs",
            "type": "array"
          }
        },
        "required": [
          "platform",
          "count"
        ],
        "title": "RunsView",
        "type": "object"
      },
      "ServedModel": {
        "properties": {
          "alias": {
            "description": "A gateway model alias \u2014 what POST /v1/sessions `model` accepts.",
            "title": "Alias",
            "type": "string"
          },
          "basis": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Which meter dominates this model's price: 'tokens' or 'gpu'. It VARIES ACROSS THE CATALOGUE, so a cost model derived from one alias does not transfer to another \u2014 read it per model. null when the alias has no configured rate of its own and is billed at the fallback, in which case there is no per-model basis to report. Full rates, including any peak windows and whether a rate is still a placeholder, are on GET /v1/pricing.",
            "title": "Basis"
          },
          "context_length": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "EFFECTIVE input context window in tokens \u2014 what this deployment will accept in one request, not what the model markets. Those differ in practice, which is why the word is 'effective': one served model here advertises a 1M window and serves 976K. \ud83d\udd34 null means NOT DECLARED \u2014 by the gateway or by Cohort's catalogue \u2014 and is never a claim that the model has no window. Do not substitute a default for it; ask, or size against something you do know. \u26a0\ufe0f This is the only published quantity that bounds ONE request. `max_tokens` on session create does NOT: it is a per-session total checked between turns.",
            "title": "Context Length"
          },
          "context_length_source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Where `context_length` came from: 'gateway' (the serving deployment declared it \u2014 authoritative) or 'catalog' (Cohort's published-spec table for the served variant, which carries a source and a read date). null when no figure is published at all. Reported so a number that is a table lookup is never mistaken for one the deployment vouched for.",
            "title": "Context Length Source"
          },
          "max_output_tokens": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Per-alias output ceiling, when the gateway declares one. \ud83d\udd34 null TODAY FOR EVERY ALIAS, and that is an honest report rather than a gap we forgot to fill: no published source states a maximum-output figure for any served model, so Cohort declares none. null means UNDECLARED \u2014 never unlimited, never zero. Fall back to your own ceiling rather than inferring one from this silence.",
            "title": "Max Output Tokens"
          },
          "mode": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "chat | embedding | rerank, as the gateway declares it. Only chat aliases may be a session's model.",
            "title": "Mode"
          },
          "provider": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The upstream this alias draws on, as the gateway's catalogue declares it (#1996) \u2014 observed, never inferred from an alias prefix. Empty string when the catalogue declares none.",
            "title": "Provider"
          },
          "vision": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "description": "Whether the alias is DECLARED (and was measured) able to see image parts. Media sent to a session whose model does not declare vision is refused at the door \u2014 never silently hallucinated over.",
            "title": "Vision"
          }
        },
        "required": [
          "alias"
        ],
        "title": "ServedModel",
        "type": "object"
      },
      "SessionContentPart": {
        "additionalProperties": true,
        "description": "One entry of the standard content union: {\"type\":\"text\",\"text\":...} or\n{\"type\":\"image_url\",\"image_url\":{\"url\":\"data:image/png;base64,...\"}}.",
        "properties": {
          "image_url": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "{\"url\": \"data:<mime>;base64,...\"} when type is image_url.",
            "title": "Image Url"
          },
          "text": {
            "default": "",
            "description": "The text, when type is text.",
            "title": "Text",
            "type": "string"
          },
          "type": {
            "default": "text",
            "description": "\"text\" or \"image_url\".",
            "title": "Type",
            "type": "string"
          }
        },
        "title": "SessionContentPart",
        "type": "object"
      },
      "SessionEnded": {
        "additionalProperties": true,
        "example": {
          "ended": true,
          "session_id": "sess-2f9a4c1e"
        },
        "properties": {
          "ended": {
            "title": "Ended",
            "type": "boolean"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "ended"
        ],
        "title": "SessionEnded",
        "type": "object"
      },
      "SessionEventDeclarationRefused": {
        "additionalProperties": true,
        "description": "The agent declared a message to the person clean and the record measured it prohibited.\nNOT terminal: the message was withheld, the agent was told why, and the session continues.\n\nThis is the one event a self-declaration exists to surface (#1999). The declaration is a\nsteer, never a substitute for the measurement; what you can count here is how often the two\ndisagreed on this session - a rate worth watching per charter and per model.",
        "properties": {
          "criterion": {
            "description": "The prohibited_content criterion that refused it.",
            "title": "Criterion",
            "type": "string"
          },
          "detail": {
            "description": "The record's reason, naming the declaration and the layer.",
            "title": "Detail",
            "type": "string"
          },
          "field": {
            "description": "The charter field the withheld message was written to.",
            "title": "Field",
            "type": "string"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "field",
          "criterion",
          "detail"
        ],
        "title": "SessionEventDeclarationRefused",
        "type": "object"
      },
      "SessionEventEvidenceRefused": {
        "additionalProperties": true,
        "description": "Text was DELIVERED but could not be recorded as evidence. NOT terminal: the session continues.\n\nValues quoted only from that text cannot be verified, so a later claim citing them will be\nrefused as unanchored. This event is the explanation, delivered before that happens (#2154).",
        "properties": {
          "detail": {
            "title": "Detail",
            "type": "string"
          },
          "reason": {
            "description": "`too_large` (send long text in smaller parts), `unavailable` (the record could not be reached), or `refused`.",
            "title": "Reason",
            "type": "string"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "source": {
            "description": "`input` for your own POST /input message, `agent` for the agent's retrieval.",
            "title": "Source",
            "type": "string"
          },
          "status": {
            "description": "The refusal's HTTP-equivalent status; 0 when unreachable.",
            "title": "Status",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "source",
          "reason",
          "status",
          "detail"
        ],
        "title": "SessionEventEvidenceRefused",
        "type": "object"
      },
      "SessionEventGap": {
        "additionalProperties": true,
        "description": "History between your last-seen id and `first_available_seq` is GONE and unrecoverable here.\n\nEmitted BEFORE the events it precedes, so you learn you are missing history before you start\nreconciling from what survived. Treat it as \"stop reconciling from the stream\" and read the\nsession's durable record instead. A reader that ignores it concludes the session was quieter\nthan it was.",
        "properties": {
          "detail": {
            "default": "",
            "title": "Detail",
            "type": "string"
          },
          "first_available_seq": {
            "title": "First Available Seq",
            "type": "integer"
          },
          "reason": {
            "description": "Why history is missing.",
            "title": "Reason",
            "type": "string"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "reason",
          "first_available_seq"
        ],
        "title": "SessionEventGap",
        "type": "object"
      },
      "SessionEventInputRefused": {
        "additionalProperties": true,
        "description": "\ud83d\udd34 THE TURN YOU ARE WAITING FOR WILL NEVER ARRIVE.\n\nYour `POST /input` was accepted for delivery (202) and then refused downstream. The SESSION IS\nSTILL OPEN - `session_ended` is false - so nothing else tells you to stop waiting. A client\nthat waits only for `turn_ended` hangs here, forever, on a turn nobody accepted.",
        "properties": {
          "detail": {
            "title": "Detail",
            "type": "string"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "session_ended": {
            "default": false,
            "description": "Always false: the input was refused, the session was not ended.",
            "title": "Session Ended",
            "type": "boolean"
          },
          "status": {
            "description": "The refusal's HTTP-equivalent status.",
            "title": "Status",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "status",
          "detail"
        ],
        "title": "SessionEventInputRefused",
        "type": "object"
      },
      "SessionEventOutput": {
        "additionalProperties": true,
        "description": "Streaming text as it is produced - what a chat UI renders.\n\n\u26a0\ufe0f THIS IS NOT THE RECORD. Reassembling `output` gives you what was displayed; `turn_content`\nis what was durably stored. For a transcript you must keep, read `turn_content` - it arrives\nBEFORE `turn_ended`, so an ordinary turn loop already has it. But it is conditional and\nbest-effort, so it is the better record when it comes and it is not guaranteed to come. Keep\nthese deltas as the fallback rather than discarding them, and record which of the two a given\ntranscript came from. After the fact, sealed sessions included, the stored turns are readable\nat `GET /v1/sessions/{id}/turns` (#4197) until the content is deleted or retention removes it.\n\n\ud83d\udd11 `channel` (#4255) SAYS WHO THIS TEXT IS FOR. A tool-using agent writes text between its\ntool calls (a preamble, a note to itself, a reaction to a tool result) and then the answer\nafter the last one. `working` is the former: the agent's commentary, for an operator's eyes.\n`spoken` is the latter: what the agent said to the person. Show a person `spoken` only.\nEmpty = a runtime that predates channels; treat it as `spoken`, which is what every consumer\ndid before the field existed.",
        "properties": {
          "channel": {
            "default": "",
            "description": "`working` (the agent's commentary, between tool calls) or `spoken` (what it said to the person, after the last tool call). Empty: a runtime that predates channels; treat as spoken.",
            "title": "Channel",
            "type": "string"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "text": {
            "title": "Text",
            "type": "string"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "text"
        ],
        "title": "SessionEventOutput",
        "type": "object"
      },
      "SessionEventQueued": {
        "additionalProperties": true,
        "description": "The session is WAITING for capacity and has not started (the create answered `phase:\nqueued`). Nothing is running and nothing is charged. Input is refused with 409\n`session_queued` until `starting` arrives; keep the stream open and wait. Carries no position\nand no count.",
        "properties": {
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq"
        ],
        "title": "SessionEventQueued",
        "type": "object"
      },
      "SessionEventSessionDoomed": {
        "additionalProperties": true,
        "description": "The session cannot continue and will not recover. Terminal.\n\n`code` is a short diagnostic tag for support conversations; its set is not published and must\nnot be branched on. `detail` is the human-readable reason.",
        "properties": {
          "code": {
            "title": "Code",
            "type": "string"
          },
          "detail": {
            "title": "Detail",
            "type": "string"
          },
          "permanent": {
            "description": "Per-criterion detail, for display rather than logic.",
            "items": {},
            "title": "Permanent",
            "type": "array"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "code",
          "detail"
        ],
        "title": "SessionEventSessionDoomed",
        "type": "object"
      },
      "SessionEventSessionEnded": {
        "additionalProperties": true,
        "description": "\ud83d\udd11 THE STREAM IS OVER. The server closes the connection immediately after this frame.\n\nDistinct from `turn_ended`: that ends one turn and invites another; this ends the session.\nConfusing the two is the difference between a working turn loop and one that hangs.",
        "properties": {
          "denied": {
            "default": false,
            "description": "True when the session was refused rather than run to an end.",
            "title": "Denied",
            "type": "boolean"
          },
          "reason": {
            "title": "Reason",
            "type": "string"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "reason"
        ],
        "title": "SessionEventSessionEnded",
        "type": "object"
      },
      "SessionEventStarting": {
        "additionalProperties": true,
        "description": "The session that was `queued` has been admitted and is starting. The ordinary `state`,\n`output`, `turn_ended` and `turn_content` frames follow; input is accepted from here on.\nSent only to a session that waited: one admitted at once never sees `queued` or `starting`.",
        "properties": {
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq"
        ],
        "title": "SessionEventStarting",
        "type": "object"
      },
      "SessionEventState": {
        "additionalProperties": true,
        "description": "ADVISORY progress. A client may ignore this frame entirely and lose nothing.\n\n\u26a0\ufe0f THE VALUES ARE DELIBERATELY NOT ENUMERATED. They describe what a worker is doing at a\nmoment in time, and pinning that vocabulary into a published contract would freeze an\nimplementation detail into a customer's expectations. Render it if you find it useful; never\nbranch a control decision on it.",
        "properties": {
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "state": {
            "title": "State",
            "type": "string"
          },
          "tool": {
            "default": "",
            "description": "Free-form; empty when not applicable.",
            "title": "Tool",
            "type": "string"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "state"
        ],
        "title": "SessionEventState",
        "type": "object"
      },
      "SessionEventStreamTimeout": {
        "additionalProperties": true,
        "description": "\ud83d\udd34 NOT AN ENDING. The stream reached its own maximum duration; the SESSION IS UNAFFECTED.\n\nRECONNECT and keep waiting for your turn. Treating this as terminal truncates a reply that is\nstill being produced.\n\nIt carries `seq: 0` on purpose, so it can never be mistaken for a resume point - resume from\nthe last REAL event you saw.",
        "properties": {
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq"
        ],
        "title": "SessionEventStreamTimeout",
        "type": "object"
      },
      "SessionEventTurnContent": {
        "additionalProperties": true,
        "description": "The durable structured record of a turn - tool-use arguments and tool-result blocks, which\nthe lossy `output` deltas cannot carry.\n\nIT ARRIVES BEFORE THIS TURN'S `turn_ended`. A loop that terminates on `turn_ended` already\nholds the record by the time it stops, so nothing special is required of you: process frames in\narrival order and take this one when it appears. The closing order is `stats`, then\n`turn_content` (when there is one), then `turn_ended`.\n\n\u26a0\ufe0f IT WAS THE OTHER WAY ROUND UNTIL #1878, and a client that terminated on `turn_ended` - the\ncorrect thing to do - got an empty transcript with no error. If you built a bounded read PAST\n`turn_ended` to work around that, it is now dead code rather than broken code: the frame simply\narrives earlier. The ordering will not move back.\n\n\u26a0\ufe0f AND IT MAY NOT COME AT ALL. CONDITIONAL - a turn that stored no new messages produces none -\nand BEST-EFFORT: a failure to build it is logged server-side and is invisible on the stream. So\nthree different situations (nothing to record, the record failed, you stopped listening early)\nare one indistinguishable silence, and absence is NOT evidence that the turn was empty. Keep\nthe reassembled `output` deltas as a fallback and record which source you actually got.",
        "properties": {
          "messages": {
            "description": "Ordered `{role, content}` messages for this turn.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Messages",
            "type": "array"
          },
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "spoken": {
            "default": "",
            "description": "#4255: what the agent said to the person this turn (the text after its last tool call). Deliver this; `messages` is the audit record and carries the agent's working commentary too. Empty when the turn ended on a tool call, or from a runtime that predates channels.",
            "title": "Spoken",
            "type": "string"
          },
          "turn_index": {
            "title": "Turn Index",
            "type": "integer"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "turn_index",
          "messages"
        ],
        "title": "SessionEventTurnContent",
        "type": "object"
      },
      "SessionEventTurnEnded": {
        "additionalProperties": true,
        "description": "\ud83d\udd11 THE TURN IS FINISHED. This is a turn loop's termination condition.\n\nIt ends the TURN, not the stream - the session stays open and you may send another input.\n`stop_reason` is empty when the runtime did not report one.\n\n\ud83d\udd11 THE GUARD CODES, PUBLISHED (#4281). A turn can also end because a GUARD stopped it, and a\nguard stop is the one case where `stop_reason` is a code you may branch on. Every guard ends\nthe turn the same way: the session stays LIVE, nothing is rolled back, and your next input\nresumes it. The codes:\n\n  guard_refusal_storm    writes to ONE field were refused over and over in this turn, so the\n                         turn was ended rather than billed further. The agent was not acting\n                         on what the record told it. Almost always the charter and the field\n                         disagree about shape - a field that holds one answer being written\n                         once per message, a part named on a field that is not a set - so read\n                         the working note, which names the field and the last refusal code.\n  guard_tool_loop        the agent called one tool repeatedly with identical arguments.\n  guard_degenerate_text  the agent's text collapsed into a repeated run.\n  guard_context_ceiling  the conversation exceeds the model's window even after compaction.\n                         THE ONLY ONE THE PERSON IS TOLD ABOUT: it also arrives as spoken\n                         text, because continuing in a new session is theirs to act on. The\n                         other three are for an operator; say nothing to the person about them.\n\n\ud83d\udd11 A FAILED TURN (#4294). Two more codes say the turn did not run to an answer at all, because\nthe model call failed:\n\n  turn_failed_upstream   the model upstream refused the call. The turn's record on /turns and\n                         the session's `last_turn_failure` carry the HTTP status it answered\n                         (e.g. 410: the model the session names was retired upstream).\n  turn_failed            the turn failed for any other reason.\n\nUnlike a guard stop, these are not the agent's doing and resending the same input may fail\nthe same way; the `ref` on the record is what to quote to support.\n\n\ud83d\udd11 NO MODEL NAMED (#4304). One more code, from the worker's own door:\n\n  model_required         the session names no model, and there is no default to run it on.\n                         Nothing ran. Start a new session that names one (GET /v1/models).\n\n\u26a0\ufe0f WHAT IS NOT PROMISED. This list is not closed and is not an enum: a newer runtime may send\na code that is not here, and an older one sends none of them. Treat an unrecognised\n`stop_reason` as \"the turn ended, reason not understood\" and never as an error - matching it\nagainst a fixed set and failing is the one reading that will break. Any code beginning\n`guard_` is a guard stop with the properties above, whether or not you recognise the rest.",
        "properties": {
          "seq": {
            "description": "Monotonic sequence, also sent as the SSE `id:`. Resume with `Last-Event-ID` or `?after_seq=` to receive only what you missed. NOTE: `stream_timeout` carries seq 0 and must never be used as a resume point.",
            "title": "Seq",
            "type": "integer"
          },
          "stop_reason": {
            "description": "Why the turn stopped; empty when the runtime did not report one. A value beginning `guard_` means a guard ended the turn and the session is still live: `guard_refusal_storm` (one field refused repeatedly), `guard_tool_loop` (identical calls repeated), `guard_degenerate_text` (text collapsed into a repeated run), `guard_context_ceiling` (over the model's window even after compaction, and the only one also spoken to the person). `turn_failed_upstream` / `turn_failed` mean the model call itself failed (#4294); the turn's record and the session's `last_turn_failure` say how. `model_required` means the session names no model and nothing ran (#4304). The set is open - treat an unknown value as a reason you do not understand, never as an error.",
            "title": "Stop Reason",
            "type": "string"
          },
          "type": {
            "description": "Which frame this is. The vocabulary is CLOSED (see below).",
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "stop_reason"
        ],
        "title": "SessionEventTurnEnded",
        "type": "object"
      },
      "SessionInputSource": {
        "additionalProperties": false,
        "description": "One session this session is created FROM (#4204).",
        "properties": {
          "include": {
            "description": "What the platform reads and hands the agent: `transcript` = the stored conversation oldest first (the same turns `GET /sessions/{id}/turns` publishes), `record` = the session's artifact (the graded fields and the seal). Both by default.",
            "items": {
              "enum": [
                "transcript",
                "record"
              ],
              "type": "string"
            },
            "title": "Include",
            "type": "array"
          },
          "session_id": {
            "description": "A session of YOUR workspace that has ended (sealed or otherwise).",
            "title": "Session Id",
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "SessionInputSource",
        "type": "object"
      },
      "SessionInputs": {
        "additionalProperties": false,
        "description": "What this session is created FROM (#4204). The platform reads the inputs itself, hands \"\n\"them to the agent ahead of your own `messages`, and pins their digests on the session so the \"\n\"seal cites exactly what was read.",
        "properties": {
          "sessions": {
            "items": {
              "$ref": "#/components/schemas/SessionInputSource"
            },
            "maxItems": 8,
            "title": "Sessions",
            "type": "array"
          }
        },
        "title": "SessionInputs",
        "type": "object"
      },
      "SessionList": {
        "additionalProperties": true,
        "example": {
          "active_sessions": 1,
          "sessions": [
            {
              "kind": "coding",
              "session_id": "sess-2f9a4c1e",
              "started_at": 1786100000
            }
          ],
          "workspace": "acme"
        },
        "properties": {
          "active_sessions": {
            "title": "Active Sessions",
            "type": "integer"
          },
          "sessions": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Sessions",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "workspace",
          "active_sessions"
        ],
        "title": "SessionList",
        "type": "object"
      },
      "SessionLocale": {
        "additionalProperties": false,
        "description": "The subject's locale for a chartered session (#1998): a BCP-47-style primary tag and an\noptional dialect. Forwarded to the charter engine at open, where it is pinned on the session\nand readable back; subject-register renderings resolve by it. Cohort does not interpret it.",
        "properties": {
          "dialect": {
            "default": "",
            "description": "Optional dialect label agreed with the charter, e.g. 'najdi'.",
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9_-]*$",
            "title": "Dialect",
            "type": "string"
          },
          "tag": {
            "description": "Language tag, e.g. 'ar' or 'en-GB'.",
            "maxLength": 16,
            "minLength": 2,
            "pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
            "title": "Tag",
            "type": "string"
          }
        },
        "required": [
          "tag"
        ],
        "title": "SessionLocale",
        "type": "object"
      },
      "SessionMessage": {
        "additionalProperties": true,
        "description": "One {role, content} turn. Typed rather than an undescribed object array.\n\napplejack reported that shape on the completions surface: a schema saying a field exists and\nnothing about what goes in it, so a consumer must know it from outside the document. This was\nthe same defect twice more on the SESSION path - `messages` here carried the shape only in\nEnglish prose, and SubmitInputRequest's carried nothing at all.\n\n`content` is the standard completions union (#1647): a plain string, or a parts[] list of\n`{\"type\":\"text\",\"text\":...}` and `{\"type\":\"image_url\",\"image_url\":{\"url\":\"data:...\"}}` -\nthe exact shape every OpenAI-style client already emits, because inventing a private ingress\nwas ruled out. Image parts are DATA URIs only (the platform deliberately fetches nothing),\nand the attachment is TURN-SCOPED: it reaches the model on this turn and is never stored -\ndurable surfaces carry a marker; a second look means resending. The worker-seam proto grew a\ntransit-only media field, so the union is now a promise the transport keeps.",
        "properties": {
          "content": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "items": {
                  "$ref": "#/components/schemas/SessionContentPart"
                },
                "type": "array"
              }
            ],
            "default": "",
            "description": "Text, or the standard parts[] union (text + image_url data-URI parts). Attachments are turn-scoped: seen this turn, never stored, resend for a second look.",
            "title": "Content"
          },
          "role": {
            "default": "user",
            "description": "user | assistant. A system-role or tool-role message is refused at 422: the system prompt is `system_prompt`, and tool turns are produced by the session's own tools.",
            "enum": [
              "user",
              "assistant"
            ],
            "title": "Role",
            "type": "string"
          }
        },
        "title": "SessionMessage",
        "type": "object"
      },
      "SessionPatch": {
        "description": "#4426 the agent's changes to its workspace.",
        "example": {
          "base_commit": "4c1a9e2f7b3d5a6c8e0f1b2d3a4c5e6f7a8b9c0d",
          "bytes": 172,
          "commands": [
            "pytest -q tests/test_invoice.py",
            "ruff check src"
          ],
          "diff": "diff --git a/src/invoice.py b/src/invoice.py\n--- a/src/invoice.py\n+++ b/src/invoice.py\n@@ -12 +12 @@\n-    return total - tax\n+    return total + tax\n",
          "final": true,
          "judge": {
            "bundle_commit": "4c1a9e2f7b3d5a6c8e0f1b2d3a4c5e6f7a8b9c0d",
            "checks": [
              {
                "command": "pytest -q tests/test_invoice.py",
                "duration_ms": 2140,
                "exit_code": 0,
                "output_sha256": "a3f1c2e4b5d6978812ab34cd56ef7890a1b2c3d4e5f60718293a4b5c6d7e8f90",
                "output_tail": "4 passed in 0.31s\n",
                "timed_out": false
              }
            ],
            "checks_passed": true,
            "doer_model": "glm-5.3",
            "image": "cohort-runtime@sha256:35baf8d2",
            "patch_sha256": "5e2c8a1f0b9d4e7a3c6f8b2d1e0a9c4f7b5d3e1a8c6f2b0d9e4a7c1f3b5d8e2a",
            "paths_ok": true,
            "reasons": [],
            "review": {
              "concerns": [],
              "verdict": "ok"
            },
            "status": "passed",
            "touched_protected": []
          },
          "session_id": "api-7d1e4b0c9a2f4e61b3c85d0a6f1e2b93",
          "sha256": "5e2c8a1f0b9d4e7a3c6f8b2d1e0a9c4f7b5d3e1a8c6f2b0d9e4a7c1f3b5d8e2a",
          "source_commit": "9b2e6f4a1c0d8e7f3a5b6c7d8e9f0a1b2c3d4e5f"
        },
        "properties": {
          "base_commit": {
            "description": "The normalized commit the agent worked on (same tree as source_commit).",
            "title": "Base Commit",
            "type": "string"
          },
          "bytes": {
            "title": "Bytes",
            "type": "integer"
          },
          "commands": {
            "description": "The commands the agent ran, in order.",
            "items": {
              "type": "string"
            },
            "title": "Commands",
            "type": "array"
          },
          "diff": {
            "description": "`git diff --binary` output (ASCII); apply with `git apply`.",
            "title": "Diff",
            "type": "string"
          },
          "final": {
            "description": "True when the worker sent it at session end; false for an interim patch.",
            "title": "Final",
            "type": "boolean"
          },
          "judge": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "#4428 the platform's verdict on THIS patch, once the session has ended and it was judged; null before. `status` is `passed`, `failed` or `no_change`; `reasons` says why a patch failed (a protected path it touched, a check that failed, a judge error); `checks` lists each done_check command as a fresh sandbox ran it (exit code, a digest of its full output, the end of that output); `review` is an independent model's reading of the change (`ok` or `flag`), which never turns a pass into a fail.",
            "title": "Judge"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          },
          "sha256": {
            "description": "sha256 of `diff`'s bytes.",
            "title": "Sha256",
            "type": "string"
          },
          "source_commit": {
            "description": "The commit your ref resolved to in the bundle you uploaded.",
            "title": "Source Commit",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "source_commit",
          "base_commit",
          "final",
          "sha256",
          "commands",
          "diff",
          "bytes"
        ],
        "title": "SessionPatch",
        "type": "object"
      },
      "SessionStatus": {
        "additionalProperties": true,
        "example": {
          "ended": false,
          "event_count": 42,
          "kind": "coding",
          "last_seq": 42,
          "live": true,
          "metadata": {
            "tenant": "acme",
            "trace_id": "d41f9c02"
          },
          "session_id": "sess-2f9a4c1e",
          "workspace": "acme"
        },
        "properties": {
          "derived": {
            "description": "LINEAGE (#4204): the sessions created FROM this one, oldest first, within your workspace. A read over the derived sessions' own records, never a second copy.",
            "items": {
              "type": "string"
            },
            "title": "Derived",
            "type": "array"
          },
          "derived_from": {
            "description": "LINEAGE (#4204): the sessions this one was created FROM through `inputs.sessions`, each `{session_id, transcript_sha256, record_sha256}` with the sha256 of exactly what the platform read and handed the agent (an empty digest = that part was not included). The same three values were pinned on this session's open entry as the context inputs `source_session`, `source_transcript_sha256` and `source_record_sha256`, so the seal cites them. `[]` for a root session. Kept on this session's own record: deleting the source's content does not change it.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Derived From",
            "type": "array"
          },
          "ended": {
            "default": false,
            "title": "Ended",
            "type": "boolean"
          },
          "event_count": {
            "default": 0,
            "title": "Event Count",
            "type": "integer"
          },
          "job": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/JobStatus"
              },
              {
                "type": "null"
              }
            ],
            "description": "The session's job, in the shared queue vocabulary. Absent for a session created before sessions were queued."
          },
          "kind": {
            "default": "",
            "title": "Kind",
            "type": "string"
          },
          "last_seq": {
            "default": 0,
            "description": "Highest event seq seen. Pass to the SSE stream to resume without replaying.",
            "title": "Last Seq",
            "type": "integer"
          },
          "last_turn_failure": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TurnFailure"
              },
              {
                "type": "null"
              }
            ],
            "description": "#4294: set when this session's MOST RECENT stored turn failed, null otherwise. A session can read `phase: live` with a healthy job and still be failing every turn - for example when the model it names has been retired upstream - and this is the field that says so without a connected stream. Cleared by the next turn that completes."
          },
          "live": {
            "description": "Whether a worker is currently attached.",
            "title": "Live",
            "type": "boolean"
          },
          "metadata": {
            "additionalProperties": true,
            "description": "The opaque metadata you supplied at create, echoed verbatim (#997). Served from the durable record, so it is still here after the session ends \u2014 which is when a caller reconciling a `session.ended` webhook actually reads it. `{}` for a session created without any. Cohort never reads or branches on a key; do not put credentials in it, as it is also echoed to your webhook target and visible to the agent.",
            "title": "Metadata",
            "type": "object"
          },
          "phase": {
            "default": "",
            "description": "Where the session is in its life: `queued` (accepted and waiting, because your account is already running as many sessions as it may at once; it starts by itself in creation order, and queued time is not billed), `starting` (accepted, worker still booting \u2014 this window is tens of seconds), `live` (a worker is attached), `ended` (finished, with `outcome` beside it), or `unknown` (this instance is not holding it and no end was recorded). Read this rather than inferring from `live`: `live=false` is true both BEFORE a worker attaches and AFTER it is gone, and those are opposite facts. `unknown` never means ended.",
            "title": "Phase",
            "type": "string"
          },
          "sampling": {
            "additionalProperties": true,
            "description": "The decoding parameters this session actually ran under (#1617) \u2014 e.g. `{\"temperature\": 0.0}`. Read back from the CreateSession the worker was given, not from a separate copy that could drift from it. `{}` means none were pinned and the deployment default applied. Cite this beside any number you publish from a session: a result whose sampling is unknown cannot be reproduced or compared.",
            "title": "Sampling",
            "type": "object"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "workspace",
          "live"
        ],
        "title": "SessionStatus",
        "type": "object"
      },
      "SessionTurn": {
        "additionalProperties": true,
        "description": "One stored turn: the same `messages` list the `turn_content` stream frame carries, and\n`spoken`, what the agent said to the person that turn (#4255).",
        "example": {
          "messages": [
            {
              "content": "Hi. I currently live in Riyadh.",
              "role": "user"
            },
            {
              "content": [
                {
                  "text": "Recorded. Anything else?"
                }
              ],
              "role": "assistant"
            }
          ],
          "spoken": "Recorded. Anything else?",
          "turn_index": 1
        },
        "properties": {
          "failure": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TurnFailure"
              },
              {
                "type": "null"
              }
            ],
            "description": "#4294: present when this turn FAILED (the model call failed and nothing more ran), and ABSENT otherwise - not null, so a turn that did not fail reads exactly as it did before this field existed. `spoken` then holds the short message the person was shown, quoting the same `ref`."
          },
          "messages": {
            "description": "The turn's messages, `[{role, content}]`, exactly as `turn_content` delivered them on the stream - tool-use arguments and tool-result blocks included.",
            "items": {},
            "title": "Messages",
            "type": "array"
          },
          "spoken": {
            "default": "",
            "description": "What the agent said to the person this turn: its text after the last tool call. Empty when the turn ended on a tool call or the runtime predates channels.",
            "title": "Spoken",
            "type": "string"
          },
          "turn_index": {
            "title": "Turn Index",
            "type": "integer"
          }
        },
        "required": [
          "turn_index"
        ],
        "title": "SessionTurn",
        "type": "object"
      },
      "SessionTurns": {
        "additionalProperties": true,
        "description": "THE DURABLE TRANSCRIPT (#4197). What the stream's `turn_content` frames delivered, read back\nfrom the record store after the fact, sealed sessions included.\n\nBefore this the conversation of an ended session existed only in the in-process event ring\n(best-effort, gone on restart) and the stored turns were dropped at end. Now the stored turns\nstay until `DELETE /sessions/{id}/content` or the retention sweep removes them with the record.\n\n`complete` is the honest flag: false while the session is live (more turns may come) and false\nwhen the stored turn indexes have a gap. A deleted or unknown session is 404 here as on every\nother read, and a session from another workspace is indistinguishable from none.",
        "example": {
          "complete": true,
          "ended": true,
          "session_id": "api-5dd1c8f73bfa4ba098c87bc3ea2f5dfc",
          "turns": [
            {
              "messages": [
                {
                  "content": "Hi. I currently live in Riyadh.",
                  "role": "user"
                },
                {
                  "content": [
                    {
                      "text": "Recorded. Anything else?"
                    }
                  ],
                  "role": "assistant"
                }
              ],
              "turn_index": 1
            }
          ],
          "workspace": "tests"
        },
        "properties": {
          "complete": {
            "description": "True only when the session has ended AND the stored turns run 1..n with no gap. False while live, or when a turn is missing (a turn_content frame is best-effort on the wire and its storage rides the same path).",
            "title": "Complete",
            "type": "boolean"
          },
          "ended": {
            "description": "Whether the session has ended; the record's ended_at.",
            "title": "Ended",
            "type": "boolean"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          },
          "turns": {
            "items": {
              "$ref": "#/components/schemas/SessionTurn"
            },
            "title": "Turns",
            "type": "array"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "workspace",
          "complete",
          "ended"
        ],
        "title": "SessionTurns",
        "type": "object"
      },
      "SessionUsage": {
        "additionalProperties": true,
        "description": "#1117: the LINE-ITEM bill for one session - every ledger row it produced plus a rollup.\nThe unit a customer reasons about (one conversation, itemized): per-turn token/compute rows,\nthe grounding-call accumulator, the worker-lifetime row.\n\n\ud83d\udd11 THIS IS THE PER-SESSION METERING INSTRUMENT. For \"what is THIS session costing\", read the\n`rollup` here - not GET /v1/usage/windows, which answers the different question of how a KEY is\nspending across everything and is the dashboard's source rather than an in-flight one.",
        "example": {
          "platform": "acme",
          "rollup": {
            "cost_usd": 0.41,
            "first_at": "2026-09-12T08:16:45Z",
            "grounding_calls": 18,
            "last_at": "2026-09-12T08:22:11Z",
            "price_estimated_rows": 0,
            "token_cached": 47180,
            "token_input": 61240,
            "token_output": 1890,
            "turn_seconds": 141.7,
            "turns": 6,
            "worker_instance_seconds": 173.2
          },
          "rows": [
            {
              "gpu_seconds": 28.4,
              "meter_id": "mtr-000000000042-api-2f9a4c1e",
              "model": "claude-sonnet-5",
              "token_input": 14210,
              "token_output": 312
            },
            {
              "grounding_calls": 18,
              "meter_id": "gr-api-2f9a4c1e"
            },
            {
              "meter_id": "lt-api-2f9a4c1e",
              "worker_instance_seconds": 173.2
            }
          ],
          "session_id": "api-2f9a4c1e"
        },
        "properties": {
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "rollup": {
            "$ref": "#/components/schemas/SessionUsageRollup",
            "description": "Every row above, totalled \u2014 including the freshness flag that says whether the total can be trusted yet."
          },
          "rows": {
            "description": "One row per metered event: per-turn token/compute rows (`token_input`, `token_output`, `token_cached`, `gpu_seconds`, `model`, `key_id`), the grounding accumulator, and the worker-lifetime row. Rows are the itemisation; `rollup` is the total.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Rows",
            "type": "array"
          },
          "session_id": {
            "title": "Session Id",
            "type": "string"
          }
        },
        "required": [
          "platform",
          "session_id"
        ],
        "title": "SessionUsage",
        "type": "object"
      },
      "SessionUsageRollup": {
        "additionalProperties": true,
        "description": "What ONE session has cost so far, totalled. (#2072)\n\n\ud83d\udd34 WHY THIS IS A MODEL AND NOT A BARE `dict`. It was a bare dict until 2026-09-12, which\ngenerates `additionalProperties: true` and describes NOTHING - so the two fields an integrator\nmost needs were invisible in the published contract. `price_estimated_rows` decides whether\n`cost_usd` can be trusted at all and a reader could not learn it existed; nothing said\n`cost_usd` was cumulative. Two people building a credit product on this API concluded the\nper-session instrument did not exist and designed around its absence. It was here the whole\ntime, undescribed. An undescribed field is not a published one.\n\n`extra=\"allow\"` so the ledger may add a meter without breaking a client that pins this model.",
        "properties": {
          "cost_usd": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "What this session has cost SO FAR, in USD. \ud83d\udd34 CUMULATIVE, NOT PER TURN \u2014 it is the running total, so settle against it by SETTING your recorded amount to this number, never by adding a difference. That distinction is what makes settlement idempotent: webhook deliveries retry and can arrive twice or out of order, and adding deltas double-charges a real person where setting a total does not. \u26a0\ufe0f Trust it only when `price_estimated_rows` is 0 \u2014 see that field.",
            "title": "Cost Usd"
          },
          "first_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Timestamp of the earliest row.",
            "title": "First At"
          },
          "grounding_calls": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Grounding calls made.",
            "title": "Grounding Calls"
          },
          "last_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Timestamp of the most recent row.",
            "title": "Last At"
          },
          "price_estimated_rows": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "How many of this session's ledger rows are still priced by ESTIMATE rather than settled. 0 means `cost_usd` is final and safe to bill or sweep against; anything above 0 means it can still move. \ud83d\udd34 READ THIS FLAG RATHER THAN TIMING THE LAG. It is the same discipline as `credit_state` before `credit_usd` and `is_placeholder` before a rate: where Cohort does not yet know, it says so, instead of handing you a number that looks settled. A timeout guessed from observed lag is a guess; this is the answer.",
            "title": "Price Estimated Rows"
          },
          "storage_gib_seconds": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Storage held, GiB-seconds.",
            "title": "Storage Gib Seconds"
          },
          "token_cached": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "The portion of `token_input` served from cache, and billed at the cached rate. Re-sent conversation context is what caches, so a long multi-turn session trends toward this cheaper rate rather than away from it.",
            "title": "Token Cached"
          },
          "token_input": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Input tokens, summed across turns.",
            "title": "Token Input"
          },
          "token_output": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Output tokens, summed across turns.",
            "title": "Token Output"
          },
          "turn_seconds": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Billable compute seconds, summed across turns.",
            "title": "Turn Seconds"
          },
          "turns": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Metered turns this session has completed so far.",
            "title": "Turns"
          }
        },
        "title": "SessionUsageRollup",
        "type": "object"
      },
      "SessionWrite": {
        "additionalProperties": true,
        "description": "ONE write in a /submit batch - the ENVELOPE around a value, not the vocabulary inside it.\n\nTHE LINE THIS DRAWS. The NAME in `field` and the meaning of `value` belong to YOUR charter;\nCohort interprets neither, and it does not know which of your fields are askable slots. The\nkeys around them are structural - the charter engine requires parts of the envelope or the\nwrite is refused - and those are what this publishes. Your vocabulary inside, our envelope\noutside.\n\nWHY IT IS PUBLISHED AT ALL (#1880). Until now this body was an untyped list of untyped objects,\nso a requirement the engine enforces - a write to a slot must carry `status` - appeared nowhere\na client could read. The only way to learn it was to break it, and a broken write answered HTTP\n200 with ok:true while recording nothing. A rule you can only discover from a refusal that\nlooks like success is not a published contract.\n\nA KEY THIS SCHEMA DOES NOT NAME IS STILL FORWARDED, byte for byte, and a key you omit is not\nsent on as an explicit null - absent and null are different answers to the engine (see\n`declared_clean`). Publishing the envelope does not narrow what you may send.",
        "example": {
          "field": "onset",
          "status": "answered",
          "value": "three days ago"
        },
        "properties": {
          "anchor": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WriteAnchor"
              },
              {
                "type": "null"
              }
            ],
            "description": "Where the value was said in the turn text, when the field is gated on it."
          },
          "declared_clean": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "description": "The doer's declaration that this candidate is free of what the charter prohibits on the field. Three-valued and absence is meaningful: absent is UNDECLARED (withheld under require_declared), false is an honest self-block. Never trusted \u2014 it is cross-checked against the armed layers, and the cross-check is the countable event.",
            "title": "Declared Clean"
          },
          "declared_lang": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The doer's claim about an outbound candidate's language. Never trusted \u2014 the span is measured independently and the declared-vs-measured cross-check is recorded.",
            "title": "Declared Lang"
          },
          "element": {
            "default": "",
            "description": "COLLECTION FIELDS: your own id for the element this write addresses. One write discipline (#4201): what a field written under it HOLDS and what a second write does. `register` says the field carries a register; `elements` says the field is a set of elements with typed parts (it declares elements and element_key, is written one part of one element at a time with element + sub, and an element is withdrawn with remove); `element_part` says the discipline may be a part's own. The closed set: collection, computed, mono, narrative, once, slot.",
            "title": "Element",
            "type": "string"
          },
          "field": {
            "description": "The charter field this write targets. YOUR vocabulary \u2014 Cohort does not interpret it, and a name your charter does not declare is refused by the charter engine (`submit.unknown_field`), not by Cohort.",
            "title": "Field",
            "type": "string"
          },
          "remove": {
            "default": false,
            "description": "COLLECTION FIELDS: withdraw the element `element` names. No value rides a removal; the facts that built the element stay on the record and the projection stops counting it. An element written again under the same id comes back as it was.",
            "title": "Remove",
            "type": "boolean"
          },
          "status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "REQUIRED ON A WRITE TO A SLOT, and the whole reason this schema exists. Legal values are exactly: answered | declined | not_applicable | not_obtained. A slot write carrying anything else \u2014 including nothing \u2014 is refused with `submit.slot_status_required` and the batch records nothing. Whether a given field IS a slot is charter semantics that Cohort does not know and will not guess: send the status for the fields your charter declares as askable slots, and read the returned block if you get it wrong.",
            "title": "Status"
          },
          "status_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "REQUIRED WHEN status IS not_obtained, refused as `submit.not_obtained_reason` without it. Absence never passes silently here on purpose: not_obtained is a positive record that the answer was not obtained, so it must say why.",
            "title": "Status Reason"
          },
          "sub": {
            "default": "",
            "description": "COLLECTION FIELDS: the part of the element `value` is for, one of the charter's `elements` names. Requires `element`.",
            "title": "Sub",
            "type": "string"
          },
          "value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The value being written. Omit it where the field kind does not take one \u2014 a gated subject-register field is refused if it carries a verbatim value, and a slot may record a status with no value at all.",
            "title": "Value"
          }
        },
        "required": [
          "field"
        ],
        "title": "SessionWrite",
        "type": "object"
      },
      "SignInConfig": {
        "description": "Everything the browser needs to build a correct authorization request, and nothing else.",
        "example": {
          "client_id": "123456789012345678",
          "end_session_endpoint": "https://authz.example.com/oidc/v1/end_session",
          "idp_hints": {
            "social:google": "urn:zitadel:iam:org:idp:id:123456789012345678"
          },
          "issuer": "https://authz.example.com",
          "post_logout_redirect_uri": "",
          "scopes": [
            "urn:zitadel:iam:org:id:123456789012345678",
            "openid",
            "profile",
            "email",
            "offline_access"
          ]
        },
        "properties": {
          "client_id": {
            "description": "PUBLIC client (PKCE, no secret)",
            "title": "Client Id",
            "type": "string"
          },
          "end_session_endpoint": {
            "default": "",
            "description": "Where to send a person to actually END their session",
            "title": "End Session Endpoint",
            "type": "string"
          },
          "idp_hints": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "method -> extra scope, for when a person picks one provider from its own button. Sending it skips the provider's chooser; omitting it makes them answer twice.",
            "title": "Idp Hints",
            "type": "object"
          },
          "issuer": {
            "description": "OIDC issuer; the SPA discovers its endpoints from this",
            "title": "Issuer",
            "type": "string"
          },
          "post_logout_redirect_uri": {
            "default": "",
            "description": "Where a person lands AFTER signing out. Empty means 'this app's own origin' \u2014 the SPA substitutes that, since it is always registered. Any other value MUST already be an approved return address at the provider or it is silently ignored.",
            "title": "Post Logout Redirect Uri",
            "type": "string"
          },
          "scopes": {
            "description": "Scopes to request, org scope INCLUDED \u2014 see module docs",
            "items": {
              "type": "string"
            },
            "title": "Scopes",
            "type": "array"
          }
        },
        "required": [
          "issuer",
          "client_id",
          "scopes"
        ],
        "title": "SignInConfig",
        "type": "object"
      },
      "StreamOptions": {
        "additionalProperties": true,
        "properties": {
          "include_usage": {
            "default": false,
            "description": "Append a final chunk carrying token counts. Cohort always requests this upstream so a streamed call can be billed; you get the chunk either way.",
            "title": "Include Usage",
            "type": "boolean"
          }
        },
        "title": "StreamOptions",
        "type": "object"
      },
      "SubmitInputRequest": {
        "description": "A turn sent into a live session. Same {role, content} shape as hydration messages.",
        "properties": {
          "messages": {
            "items": {
              "$ref": "#/components/schemas/SessionMessage"
            },
            "title": "Messages",
            "type": "array"
          }
        },
        "title": "SubmitInputRequest",
        "type": "object"
      },
      "Task": {
        "additionalProperties": true,
        "properties": {
          "after_ids": {
            "description": "Tasks that must finish first.",
            "items": {
              "type": "string"
            },
            "title": "After Ids",
            "type": "array"
          },
          "budgets": {
            "additionalProperties": true,
            "title": "Budgets",
            "type": "object"
          },
          "checkpoint_ref": {
            "default": "",
            "title": "Checkpoint Ref",
            "type": "string"
          },
          "definition_epoch": {
            "default": "",
            "title": "Definition Epoch",
            "type": "string"
          },
          "exit_condition_ids": {
            "items": {
              "type": "string"
            },
            "title": "Exit Condition Ids",
            "type": "array"
          },
          "goal_id": {
            "description": "Parent goal. Taken from the PATH on create, never the body.",
            "title": "Goal Id",
            "type": "string"
          },
          "origin": {
            "default": "customer",
            "title": "Origin",
            "type": "string"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "state": {
            "default": "pending",
            "title": "State",
            "type": "string"
          },
          "task_id": {
            "title": "Task Id",
            "type": "string"
          },
          "verification_grade": {
            "default": "two_leg",
            "title": "Verification Grade",
            "type": "string"
          }
        },
        "required": [
          "task_id",
          "goal_id",
          "platform"
        ],
        "title": "Task",
        "type": "object"
      },
      "TaskCreate": {
        "additionalProperties": false,
        "description": "POST /v1/goals/{goal_id}/tasks. The parent goal must already be yours or this 404s.",
        "properties": {
          "after_ids": {
            "description": "Task ids that must finish first.",
            "items": {
              "type": "string"
            },
            "title": "After Ids",
            "type": "array"
          },
          "budgets": {
            "additionalProperties": true,
            "title": "Budgets",
            "type": "object"
          },
          "checkpoint_ref": {
            "default": "",
            "title": "Checkpoint Ref",
            "type": "string"
          },
          "definition_epoch": {
            "default": "",
            "title": "Definition Epoch",
            "type": "string"
          },
          "exit_condition_ids": {
            "items": {
              "type": "string"
            },
            "title": "Exit Condition Ids",
            "type": "array"
          },
          "origin": {
            "default": "customer",
            "description": "Empty string is treated as 'customer'.",
            "title": "Origin",
            "type": "string"
          },
          "state": {
            "default": "pending",
            "description": "Empty string is treated as 'pending'.",
            "title": "State",
            "type": "string"
          },
          "task_id": {
            "default": "",
            "description": "Omit for a server-generated `task-<12 hex>`.",
            "title": "Task Id",
            "type": "string"
          }
        },
        "title": "TaskCreate",
        "type": "object"
      },
      "TaskEnvelope": {
        "additionalProperties": true,
        "example": {
          "task": {
            "after_ids": [],
            "budgets": {},
            "checkpoint_ref": "",
            "definition_epoch": "2026-08-07",
            "exit_condition_ids": [],
            "goal_id": "goal-4f2a91c07b3e",
            "origin": "customer",
            "platform": "acme",
            "state": "pending",
            "task_id": "task-8b1d33e0a95c",
            "verification_grade": "two_leg"
          }
        },
        "properties": {
          "task": {
            "$ref": "#/components/schemas/Task"
          }
        },
        "required": [
          "task"
        ],
        "title": "TaskEnvelope",
        "type": "object"
      },
      "TaskList": {
        "additionalProperties": true,
        "example": {
          "count": 1,
          "goal_id": "goal-4f2a91c07b3e",
          "platform": "acme",
          "tasks": [
            {
              "after_ids": [],
              "budgets": {},
              "checkpoint_ref": "",
              "definition_epoch": "2026-08-07",
              "exit_condition_ids": [
                "cond-1a2b3c4d5e6f"
              ],
              "goal_id": "goal-4f2a91c07b3e",
              "origin": "customer",
              "platform": "acme",
              "state": "pending",
              "task_id": "task-8b1d33e0a95c",
              "verification_grade": "two_leg"
            }
          ]
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "goal_id": {
            "title": "Goal Id",
            "type": "string"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "tasks": {
            "items": {
              "$ref": "#/components/schemas/Task"
            },
            "title": "Tasks",
            "type": "array"
          }
        },
        "required": [
          "platform",
          "goal_id",
          "count"
        ],
        "title": "TaskList",
        "type": "object"
      },
      "TurnFailure": {
        "additionalProperties": true,
        "description": "#4294 why a turn failed, in terms you may act on and quote. Never the upstream's own text.",
        "properties": {
          "code": {
            "description": "`turn_failed_upstream` (the model upstream refused the call; `upstream_status` says how) or `turn_failed` (anything else). An open set: treat a code you do not recognise as a failure you do not understand, never as success.",
            "title": "Code",
            "type": "string"
          },
          "ref": {
            "default": "",
            "description": "The support reference - the same one the stream's text quoted. Give it to support and they can find the full cause; it carries nothing by itself.",
            "title": "Ref",
            "type": "string"
          },
          "upstream_status": {
            "default": 0,
            "description": "The HTTP status the model upstream answered, e.g. 410 when the model the session names has been retired upstream, 429 when it is rate-limited. 0 when the failure was not an upstream refusal.",
            "title": "Upstream Status",
            "type": "integer"
          }
        },
        "required": [
          "code"
        ],
        "title": "TurnFailure",
        "type": "object"
      },
      "UnitEconomics": {
        "additionalProperties": true,
        "description": "#1117: per-session distribution + DRIVER view - avg/p50/p95 cost and tokens per session,\nturn-count distribution, and which dimension dominates the bill. What justifies a turn cap or\na token ceiling with data instead of vibes.",
        "example": {
          "cost_usd": {
            "avg": 0.41,
            "p50": 0.38,
            "p95": 0.71,
            "total": 87.4
          },
          "driver": {
            "grounding_calls": 3852,
            "token_input_share": 0.97,
            "token_output_share": 0.03,
            "worker_instance_seconds": 37064.8
          },
          "platform": "acme",
          "sessions": 214,
          "tokens_per_session": {
            "avg": 63130.0,
            "p50": 58200,
            "p95": 104000
          },
          "turns_per_session": {
            "avg": 6.1,
            "max": 14,
            "p50": 6,
            "p95": 9
          }
        },
        "properties": {
          "cost_usd": {
            "additionalProperties": true,
            "title": "Cost Usd",
            "type": "object"
          },
          "driver": {
            "additionalProperties": true,
            "title": "Driver",
            "type": "object"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "sessions": {
            "default": 0,
            "title": "Sessions",
            "type": "integer"
          },
          "tokens_per_session": {
            "additionalProperties": true,
            "title": "Tokens Per Session",
            "type": "object"
          },
          "turns_per_session": {
            "additionalProperties": true,
            "title": "Turns Per Session",
            "type": "object"
          }
        },
        "required": [
          "platform"
        ],
        "title": "UnitEconomics",
        "type": "object"
      },
      "UsageByKey": {
        "additionalProperties": true,
        "description": "Per-key consumption over an explicit window - the read an INVOICE is cut from. (#1194)\n\nThe keyless bucket is PRESENT AND NAMED rather than filtered out: a human's dashboard session\ncarries no ck_ key and that spend is real. Dropping it would leave every per-key line looking\ncorrect while the parts failed to sum to the whole.\n\n`reconciliation` states the arithmetic instead of asserting it. the customer invoices its customers\nfrom this and we invoice the customer; if those disagree, the difference is money someone eats, so it\nis a named field rather than a silence.\n\n#1236 adds three things, all additive: `last_used` and a nested `by_model` on every key bucket\n(which key spent it, ON WHICH MODEL, and when it last did), and a workspace-level `by_model` for\nthe same window. DECLARED here rather than left to `extra=\"allow\"` - an undeclared field still\nreaches the consumer but does not reach the published contract, and a number nobody can find in\nthe spec is a number nobody will use.",
        "example": {
          "by_key": [
            {
              "by_model": [
                {
                  "cost_usd": 11.904,
                  "gpu_seconds": 1602.11,
                  "label": "",
                  "last_used": "2026-08-31T18:04:11Z",
                  "model": "claude-opus-5",
                  "modelless": false,
                  "token_input": 880100,
                  "token_output": 201440,
                  "turns": 341
                },
                {
                  "cost_usd": 0.514033,
                  "gpu_seconds": 239.11,
                  "label": "",
                  "last_used": "2026-08-30T09:12:47Z",
                  "model": "qwen3-coder-480b",
                  "modelless": false,
                  "token_input": 38104,
                  "token_output": 13441,
                  "turns": 47
                }
              ],
              "cost_usd": 12.418033,
              "estimated_rows": 3,
              "gpu_seconds": 1841.22,
              "key_id": "key-3c7f10ab92de",
              "key_prefix": "ck_acme_8Kq2",
              "keyless": false,
              "label": "",
              "last_used": "2026-08-31T18:04:11Z",
              "sessions": 61,
              "token_input": 918204,
              "token_output": 214881,
              "turns": 388
            },
            {
              "by_model": [
                {
                  "cost_usd": 0.712004,
                  "gpu_seconds": 96.4,
                  "label": "",
                  "last_used": "2026-08-29T21:40:02Z",
                  "model": "claude-opus-5",
                  "modelless": false,
                  "token_input": 40112,
                  "token_output": 9004,
                  "turns": 24
                }
              ],
              "cost_usd": 0.712004,
              "estimated_rows": 0,
              "gpu_seconds": 96.4,
              "key_id": "",
              "key_prefix": "",
              "keyless": true,
              "label": "keyless",
              "last_used": "2026-08-29T21:40:02Z",
              "sessions": 4,
              "token_input": 40112,
              "token_output": 9004,
              "turns": 24
            }
          ],
          "by_model": [
            {
              "cost_usd": 12.616004,
              "gpu_seconds": 1698.51,
              "label": "",
              "last_used": "2026-08-31T18:04:11Z",
              "model": "claude-opus-5",
              "modelless": false,
              "token_input": 920212,
              "token_output": 210444,
              "turns": 365
            },
            {
              "cost_usd": 0.514033,
              "gpu_seconds": 239.11,
              "label": "",
              "last_used": "2026-08-30T09:12:47Z",
              "model": "qwen3-coder-480b",
              "modelless": false,
              "token_input": 38104,
              "token_output": 13441,
              "turns": 47
            }
          ],
          "platform": "acme",
          "reconciliation": {
            "balanced": true,
            "difference_gpu_seconds": 0.0,
            "difference_models_usd": 0.0,
            "difference_usd": 0.0,
            "note": "sum_of_keys includes the keyless bucket. A non-zero difference means rows exist that the grouping could not place \u2014 investigate rather than reconcile by adjustment. sum_of_models includes the modelless bucket (storage / worker-lifetime rows carry cost and no model); both partitions must reach the same workspace total.",
            "sum_of_keys_gpu_seconds": 1937.62,
            "sum_of_keys_usd": 13.130037,
            "sum_of_models_usd": 13.130037,
            "workspace_total_gpu_seconds": 1937.62,
            "workspace_total_usd": 13.130037
          },
          "unit": "cost_usd is USD; gpu_seconds is the underlying measure",
          "window": {
            "complete": true,
            "cost_caveat": "cost_usd is EXACT except for rows priced at the fallback rate (price_estimated). 3 row(s) in this window are estimated.",
            "cost_is_approximate": true,
            "estimated_rows": 3,
            "retention_floor": "2026-07-04T11:02:00Z",
            "rows_considered": 412,
            "semantics": "[since, until) \u2014 since is INCLUSIVE, until is EXCLUSIVE, so adjacent windows tile without double-counting a row on the boundary",
            "since": "2026-08-01T00:00:00Z",
            "until": "2026-09-01T00:00:00Z"
          }
        },
        "properties": {
          "by_key": {
            "description": "One bucket per key_id, plus the keyless bucket. Each carries `last_used` and a nested `by_model`.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "By Key",
            "type": "array"
          },
          "by_model": {
            "description": "Workspace totals per model over the SAME window. Includes a `modelless` bucket for storage/worker-lifetime rows, so the split sums to the workspace total.",
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "By Model",
            "type": "array"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "reconciliation": {
            "additionalProperties": true,
            "title": "Reconciliation",
            "type": "object"
          },
          "unit": {
            "default": "",
            "title": "Unit",
            "type": "string"
          },
          "window": {
            "additionalProperties": true,
            "title": "Window",
            "type": "object"
          }
        },
        "required": [
          "platform"
        ],
        "title": "UsageByKey",
        "type": "object"
      },
      "UsageRecent": {
        "additionalProperties": true,
        "example": {
          "count": 1,
          "platform": "acme",
          "rows": [
            {
              "cost_usd": 0.128,
              "ended_at": 1786100041,
              "gpu_seconds": 41.2,
              "model": "claude-opus-5",
              "price_estimated": false,
              "session_id": "sess-2f9a4c1e",
              "started_at": 1786100000
            }
          ]
        },
        "properties": {
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "rows": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "Rows",
            "type": "array"
          },
          "total_matching": {
            "default": 0,
            "title": "Total Matching",
            "type": "integer"
          },
          "truncated": {
            "default": false,
            "title": "Truncated",
            "type": "boolean"
          }
        },
        "required": [
          "platform",
          "count"
        ],
        "title": "UsageRecent",
        "type": "object"
      },
      "UsageSummary": {
        "additionalProperties": true,
        "example": {
          "by_model": [
            {
              "gpu": 3100.2,
              "model": "claude-opus-5",
              "usd": 9.61
            }
          ],
          "by_provider": [
            {
              "gpu": 4820.5,
              "provider": "anthropic",
              "usd": 12.05
            }
          ],
          "platform": "acme",
          "totals": {
            "est": 0,
            "gpu": 4820.5,
            "sessions": 7,
            "turns": 63,
            "usd": 12.05
          }
        },
        "properties": {
          "by_model": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "By Model",
            "type": "array"
          },
          "by_provider": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "title": "By Provider",
            "type": "array"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "totals": {
            "additionalProperties": true,
            "title": "Totals",
            "type": "object"
          }
        },
        "required": [
          "platform"
        ],
        "title": "UsageSummary",
        "type": "object"
      },
      "UsageWindows": {
        "additionalProperties": true,
        "description": "The same per-key/per-model read, over EVERY canonical window, in one call. (#1236)\n\nWHY ONE CALL AND NOT FIVE. \"Is this key spending right now, and how does that compare to today\"\nis one question. Answered by five round-trips against a live ledger it becomes five answers from\nfive slightly different ledgers - a turn landing between the calls appears in the 10-minute\nwindow and not in the 24-hour window that contains it. Cohort snapshots the rows once and slices\nthem, so the narrow windows are subsets of the wide one by construction.\n\n`windows` is keyed by STABLE MACHINE IDS - `10m`, `1h`, `6h`, `24h`, `all`. Read the id; render\nthe `label`. Each block carries the same `window` / `by_key` / `by_model` / `reconciliation`\nshape as GET /v1/usage/by-key, so anything that renders one renders the other.",
        "example": {
          "generated_at": "2026-08-31T18:05:00Z",
          "platform": "acme",
          "unit": "cost_usd is USD; gpu_seconds is the underlying measure",
          "windows": {
            "10m": {
              "by_key": [
                {
                  "by_model": [
                    {
                      "cost_usd": 0.1402,
                      "gpu_seconds": 18.4,
                      "last_used": "2026-08-31T18:04:11Z",
                      "model": "claude-opus-5",
                      "turns": 4
                    }
                  ],
                  "cost_usd": 0.1402,
                  "gpu_seconds": 18.4,
                  "key_id": "key-3c7f10ab92de",
                  "key_prefix": "ck_acme_8Kq2",
                  "keyless": false,
                  "last_used": "2026-08-31T18:04:11Z",
                  "turns": 4
                }
              ],
              "by_model": [
                {
                  "cost_usd": 0.1402,
                  "gpu_seconds": 18.4,
                  "last_used": "2026-08-31T18:04:11Z",
                  "model": "claude-opus-5",
                  "turns": 4
                }
              ],
              "id": "10m",
              "label": "last 10 minutes",
              "reconciliation": {
                "balanced": true,
                "difference_usd": 0.0
              },
              "seconds": 600,
              "window": {
                "complete": true,
                "estimated_rows": 0,
                "retention_floor": "2026-07-04T11:02:00Z",
                "rows_considered": 4,
                "since": "2026-08-31T17:55:00Z"
              }
            },
            "all": {
              "id": "all",
              "label": "all time",
              "retention_caveat": "all-time covers every row still retained. The ledger is trimmed, so for a workspace older than the retention_floor above this is a FLOOR, not a lifetime total.",
              "seconds": 0
            }
          }
        },
        "properties": {
          "generated_at": {
            "default": "",
            "description": "The instant the windows were measured FROM. Every `since` below is this minus the window length, so a client can reproduce the bounds exactly.",
            "title": "Generated At",
            "type": "string"
          },
          "platform": {
            "title": "Platform",
            "type": "string"
          },
          "unit": {
            "default": "",
            "title": "Unit",
            "type": "string"
          },
          "windows": {
            "additionalProperties": true,
            "description": "Keyed by window id: 10m, 1h, 6h, 24h, all. `all` is bounded by retention and says so.",
            "title": "Windows",
            "type": "object"
          }
        },
        "required": [
          "platform"
        ],
        "title": "UsageWindows",
        "type": "object"
      },
      "ValidationError": {
        "properties": {
          "ctx": {
            "title": "Context",
            "type": "object"
          },
          "input": {
            "title": "Input"
          },
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "title": "Location",
            "type": "array"
          },
          "msg": {
            "title": "Message",
            "type": "string"
          },
          "type": {
            "title": "Error Type",
            "type": "string"
          }
        },
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError",
        "type": "object"
      },
      "Workspace": {
        "additionalProperties": true,
        "description": "A workspace inside YOUR account. You create and archive these yourself.",
        "properties": {
          "archived_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Archived At"
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At"
          },
          "created_by": {
            "default": "",
            "title": "Created By",
            "type": "string"
          },
          "credit_state": {
            "default": "unknown",
            "description": "\"funded\" (credit_usd is a number) | \"none\" (no wallet has been appointed \u2014 since #1876 nothing in this workspace can run until one is) | \"unknown\" (the wallet could not be read, OR this response did not populate it; either way it is NOT a claim that there is no wallet). Branch on this, not on credit_usd being null.",
            "title": "Credit State",
            "type": "string"
          },
          "credit_usd": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Credit appointed to this workspace, spendable only here. null when there is no wallet OR when the wallet could not be read \u2014 see credit_state, which distinguishes them. Do not treat null as zero.",
            "title": "Credit Usd"
          },
          "display_name": {
            "default": "",
            "title": "Display Name",
            "type": "string"
          },
          "org_id": {
            "default": "",
            "title": "Org Id",
            "type": "string"
          },
          "slug": {
            "default": "",
            "description": "Your human handle, and the billing key. Unique across ALL of Cohort \u2014 usage rows are attributed by slug, so it identifies exactly one workspace forever and is never renamed or reused.",
            "title": "Slug",
            "type": "string"
          },
          "state": {
            "default": "active",
            "description": "active | suspended | archived",
            "title": "State",
            "type": "string"
          },
          "workspace_id": {
            "default": "",
            "description": "Immutable internal handle. Stable even if the slug's meaning changes.",
            "title": "Workspace Id",
            "type": "string"
          }
        },
        "title": "Workspace",
        "type": "object"
      },
      "WorkspaceArchived": {
        "additionalProperties": true,
        "example": {
          "keys": {
            "detail": "",
            "failed": 0,
            "revoked": 2
          },
          "retained": "usage, budgets, goals and runs are KEPT \u2014 archiving removes access, not billing history",
          "workspace": {
            "archived_at": "2026-08-07T17:02:44Z",
            "slug": "acme",
            "state": "archived",
            "workspace_id": "ws_3a17c9e4b210"
          }
        },
        "properties": {
          "keys": {
            "additionalProperties": true,
            "description": "How many API keys were revoked. `detail` is non-empty when the key store could not be reached \u2014 in which case KEYS WERE NOT REVOKED and the workspace can still act.",
            "title": "Keys",
            "type": "object"
          },
          "retained": {
            "default": "",
            "title": "Retained",
            "type": "string"
          },
          "workspace": {
            "$ref": "#/components/schemas/Workspace"
          }
        },
        "required": [
          "workspace"
        ],
        "title": "WorkspaceArchived",
        "type": "object"
      },
      "WorkspaceBundle": {
        "example": {
          "bytes_total": 9437184,
          "filename": "billing-service.bundle",
          "max_bytes": 268435456,
          "missing_parts": [],
          "part_bytes": 4194304,
          "parts_total": 3,
          "received_bytes": 9437184,
          "state": "ready",
          "upload_id": "upl_3f9c1e0a7b2d44c8a1e5"
        },
        "properties": {
          "bytes_total": {
            "title": "Bytes Total",
            "type": "integer"
          },
          "filename": {
            "title": "Filename",
            "type": "string"
          },
          "max_bytes": {
            "description": "The largest bundle this deployment accepts.",
            "title": "Max Bytes",
            "type": "integer"
          },
          "missing_parts": {
            "description": "While uploading: part numbers not yet received (first 100).",
            "items": {
              "type": "integer"
            },
            "title": "Missing Parts",
            "type": "array"
          },
          "part_bytes": {
            "description": "Every part is exactly this size except the last.",
            "title": "Part Bytes",
            "type": "integer"
          },
          "parts_total": {
            "title": "Parts Total",
            "type": "integer"
          },
          "received_bytes": {
            "title": "Received Bytes",
            "type": "integer"
          },
          "state": {
            "description": "`uploading`, `ready` (usable in POST /v1/sessions) or `cancelled`.",
            "title": "State",
            "type": "string"
          },
          "upload_id": {
            "title": "Upload Id",
            "type": "string"
          }
        },
        "required": [
          "upload_id",
          "filename",
          "bytes_total",
          "part_bytes",
          "parts_total",
          "received_bytes",
          "max_bytes",
          "state"
        ],
        "title": "WorkspaceBundle",
        "type": "object"
      },
      "WorkspaceBundleCreate": {
        "properties": {
          "bytes": {
            "description": "The bundle's exact size in bytes.",
            "title": "Bytes",
            "type": "integer"
          },
          "filename": {
            "description": "The bundle's file name, shown in the console. No path.",
            "title": "Filename",
            "type": "string"
          },
          "sha256": {
            "default": "",
            "description": "Optional: the bundle's sha256 as 64 lowercase hex. When given, completion refuses bytes that do not match it.",
            "title": "Sha256",
            "type": "string"
          }
        },
        "required": [
          "filename",
          "bytes"
        ],
        "title": "WorkspaceBundleCreate",
        "type": "object"
      },
      "WorkspaceCreate": {
        "additionalProperties": false,
        "properties": {
          "adopt": {
            "default": false,
            "description": "Explicit confirmation for ADOPTING a slug that already holds API keys and billing data but belongs to no account (a pre-registry orphan). Adoption attaches that existing history to YOUR account \u2014 visibility moves, data does not. Ignored for ordinary slugs; refused when another account owns the slug.",
            "title": "Adopt",
            "type": "boolean"
          },
          "display_name": {
            "default": "",
            "description": "Human label. Defaults to the slug.",
            "title": "Display Name",
            "type": "string"
          },
          "slug": {
            "description": "Lowercase letters, digits and hyphens, 3-40 chars. Unique across ALL of Cohort, not just your account: the slug is written into billing rows and API-key prefixes, so it must identify exactly one workspace forever. A name already taken by anyone is refused with 409. Treat it as permanent \u2014 it cannot be renamed or reused, including after the workspace is archived. `default` and any `default-\u2026` name are reserved (422): every account already owns a default workspace, created with the account, whose slug is `default-<account id>` and which cannot be archived.",
            "title": "Slug",
            "type": "string"
          }
        },
        "required": [
          "slug"
        ],
        "title": "WorkspaceCreate",
        "type": "object"
      },
      "WorkspaceCredit": {
        "additionalProperties": true,
        "example": {
          "account_available_usd": 750.0,
          "moved": true,
          "workspace": "acme",
          "workspace_balance_usd": 250.0
        },
        "properties": {
          "account_available_usd": {
            "description": "What the ACCOUNT has left to appoint elsewhere \u2014 the money that has not been given to any workspace yet.",
            "title": "Account Available Usd",
            "type": "number"
          },
          "moved": {
            "description": "False means this idempotency key was ALREADY APPLIED and nothing moved now \u2014 the balances below are still correct. It is not an error and not a second transfer.",
            "title": "Moved",
            "type": "boolean"
          },
          "workspace": {
            "title": "Workspace",
            "type": "string"
          },
          "workspace_balance_usd": {
            "description": "What this workspace holds after the call. Money here can be spent ONLY by this workspace; no other platform on the account can reach it.",
            "title": "Workspace Balance Usd",
            "type": "number"
          }
        },
        "required": [
          "workspace",
          "moved",
          "workspace_balance_usd",
          "account_available_usd"
        ],
        "title": "WorkspaceCredit",
        "type": "object"
      },
      "WorkspaceCreditRequest": {
        "additionalProperties": false,
        "description": "Appoint credit to a workspace, or return it. (#1835)",
        "properties": {
          "amount_usd": {
            "description": "POSITIVE appoints money from the account to this workspace; NEGATIVE returns it. One signed operation rather than two endpoints \u2014 how an owner divides their own money between their own platforms is theirs to change freely. 0 is refused: it moves nothing, and recording a movement that did not happen makes the ledger harder to read.",
            "title": "Amount Usd",
            "type": "number"
          },
          "idempotency_key": {
            "description": "YOURS, not ours, and REQUIRED. This is a money write \u2014 the one place where 'applied twice' is unrecoverable \u2014 so a retry must carry the SAME key to be a retry rather than a second transfer. A key we generated would make every retry a fresh movement.",
            "maxLength": 200,
            "minLength": 8,
            "title": "Idempotency Key",
            "type": "string"
          },
          "internal_note": {
            "default": "",
            "description": "Operator-only note. NEVER shown to the customer whose workspace this is.",
            "maxLength": 500,
            "title": "Internal Note",
            "type": "string"
          }
        },
        "required": [
          "amount_usd",
          "idempotency_key"
        ],
        "title": "WorkspaceCreditRequest",
        "type": "object"
      },
      "WorkspaceEnvelope": {
        "additionalProperties": true,
        "example": {
          "workspace": {
            "created_at": "2026-08-07T16:40:11Z",
            "created_by": "bdfecdf5-a5a4-4135-aa4b-46c8d467145f",
            "display_name": "Acme",
            "org_id": "org_9c1f4a20bd77",
            "slug": "acme",
            "state": "active",
            "workspace_id": "ws_3a17c9e4b210"
          }
        },
        "properties": {
          "workspace": {
            "$ref": "#/components/schemas/Workspace"
          }
        },
        "required": [
          "workspace"
        ],
        "title": "WorkspaceEnvelope",
        "type": "object"
      },
      "WorkspaceList": {
        "additionalProperties": true,
        "example": {
          "count": 2,
          "items": [
            {
              "created_at": "2026-08-07T16:40:11Z",
              "created_by": "bdfecdf5-a5a4-4135-aa4b-46c8d467145f",
              "display_name": "Acme",
              "org_id": "org_9c1f4a20bd77",
              "slug": "acme",
              "state": "active",
              "workspace_id": "ws_3a17c9e4b210"
            }
          ],
          "org_id": "org_9c1f4a20bd77",
          "workspaces": [
            "acme",
            "acme-eu"
          ]
        },
        "properties": {
          "count": {
            "default": 0,
            "title": "Count",
            "type": "integer"
          },
          "items": {
            "description": "Full rows INCLUDING archived, for management UI. Empty for an API key, which has no account view.",
            "items": {
              "$ref": "#/components/schemas/Workspace"
            },
            "title": "Items",
            "type": "array"
          },
          "org_id": {
            "default": "",
            "title": "Org Id",
            "type": "string"
          },
          "workspaces": {
            "description": "ACTIVE slugs \u2014 the flat list the switcher and integrations read. An API key gets exactly one: its own.",
            "items": {
              "type": "string"
            },
            "title": "Workspaces",
            "type": "array"
          }
        },
        "title": "WorkspaceList",
        "type": "object"
      },
      "WorkspaceReopened": {
        "additionalProperties": true,
        "example": {
          "keys": "NOT reissued \u2014 revocation is permanent; mint a new key to reconnect",
          "workspace": {
            "slug": "acme",
            "state": "active",
            "workspace_id": "ws_3a17c9e4b210"
          }
        },
        "properties": {
          "keys": {
            "default": "",
            "description": "Revocation is PERMANENT \u2014 reopening does not reissue keys.",
            "title": "Keys",
            "type": "string"
          },
          "workspace": {
            "$ref": "#/components/schemas/Workspace"
          }
        },
        "required": [
          "workspace"
        ],
        "title": "WorkspaceReopened",
        "type": "object"
      },
      "WorkspaceSpec": {
        "additionalProperties": false,
        "description": "#4426 a repository for the agent to work in, as a git bundle you uploaded.",
        "properties": {
          "allowed_paths": {
            "description": "Optional: path prefixes the change is expected to stay within. Recorded with the session; checking a patch against it is a later stage.",
            "items": {
              "type": "string"
            },
            "title": "Allowed Paths",
            "type": "array"
          },
          "bundle_upload_id": {
            "description": "A `ready` upload from POST /v1/workspace-bundles, in this workspace.",
            "title": "Bundle Upload Id",
            "type": "string"
          },
          "done_check": {
            "description": "Optional: the commands that decide the work is done (tests, type checks). Recorded with the session and given to the agent.",
            "items": {
              "type": "string"
            },
            "title": "Done Check",
            "type": "array"
          },
          "ref": {
            "description": "The branch, tag or full commit id in the bundle to work on. The session gets that ref's tree as ONE commit: no history, other branches, tags or remotes reach it.",
            "title": "Ref",
            "type": "string"
          }
        },
        "required": [
          "bundle_upload_id",
          "ref"
        ],
        "title": "WorkspaceSpec",
        "type": "object"
      },
      "WorkspaceUpdate": {
        "additionalProperties": false,
        "description": "What can be corrected after the fact. (#1777)\n\nA customer could archive a workspace - the destructive act - and could not fix a typo in its\nlabel. That asymmetry is what this model closes, and it closes it for exactly one field.",
        "properties": {
          "display_name": {
            "anyOf": [
              {
                "maxLength": 120,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The human label, shown wherever a person reads the workspace. Omit to leave it as it is; send \"\" to clear it, which resets it to the slug (a workspace always reads as SOMETHING). This is the only mutable field on a workspace.",
            "title": "Display Name"
          },
          "slug": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "REFUSED \u2014 never accepted, and named here only so an attempt gets an explanation instead of a generic unknown-field error. The slug is the workspace's identity, not a name: usage rows are attributed by it, API-key prefixes contain it, grants are keyed on it and charter references bind to it. Moving it would strand all four silently, so it cannot be moved at all. To use a different slug, create a workspace under it and archive this one \u2014 archiving keeps the billing history that the old slug still identifies.",
            "title": "Slug"
          }
        },
        "title": "WorkspaceUpdate",
        "type": "object"
      },
      "WriteAnchor": {
        "additionalProperties": true,
        "description": "Where in the turn's text a write's value was actually said.\n\nBoth members are OPTIONAL here and required by the charter engine ON PURPOSE. Cohort's job is\nto forward an anchor, not to rule on whether it is sufficient: that ruling belongs to the\nengine and comes back as a block you can read and fix. Declaring them required here would turn\na readable verdict into a 422 from a layer that does not decide it.",
        "example": {
          "length": 9,
          "offset": 12
        },
        "properties": {
          "length": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Length in runes of the anchored span.",
            "title": "Length"
          },
          "offset": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Rune offset into the turn text.",
            "title": "Offset"
          }
        },
        "title": "WriteAnchor",
        "type": "object"
      },
      "WritesRequest": {
        "additionalProperties": false,
        "example": {
          "floor_facts": [
            {
              "key": "floor.red_flag",
              "state": "ok"
            }
          ],
          "writer": "",
          "writes": [
            {
              "field": "task_shape",
              "value": "single-read"
            },
            {
              "field": "onset",
              "status": "answered",
              "value": "three days ago"
            }
          ]
        },
        "properties": {
          "floor_facts": {
            "description": "Deterministic READINGS to record alongside this batch \u2014 what your own rules measured, as distinct from what the record says. A charter can gate on these, so a fired rule can decide an escalation flag even when the model's own reading was never taken. Send them alone or with writes. Mind the polarity documented on FloorFact: `ok` means the rule FIRED.",
            "items": {
              "$ref": "#/components/schemas/FloorFact"
            },
            "title": "Floor Facts",
            "type": "array"
          },
          "writer": {
            "default": "",
            "description": "Batch attribution (substrate vs doer), charter wire format.",
            "title": "Writer",
            "type": "string"
          },
          "writes": {
            "description": "Session-setup writes forwarded to the charter record VERBATIM \u2014 Cohort does NOT interpret the field names (that is the workload's charter vocabulary). Use /claims for the answer (it carries the type discipline); this carries the neutral shape/subject writes a chartered session records at open. The ENVELOPE around each write is published (see SessionWrite) because parts of it are structurally required and a write missing them is refused; anything beyond those keys is passed through untouched rather than dropped.\n\nOPTIONAL SINCE #1882: a batch may carry only `floor_facts`. A deterministic rule fires on its own schedule, not when the record happens to have something to say, so requiring a write here forced callers to either invent one nobody wanted or hold a safety reading back until one existed.",
            "items": {
              "$ref": "#/components/schemas/SessionWrite"
            },
            "title": "Writes",
            "type": "array"
          }
        },
        "title": "WritesRequest",
        "type": "object"
      },
      "WritesResponse": {
        "example": {
          "detail": {
            "blocks": [],
            "disposition": "accepted"
          },
          "ok": true
        },
        "properties": {
          "detail": {
            "additionalProperties": true,
            "description": "The charter verdict VERBATIM \u2014 `disposition`, plus `blocks` naming each refusal with a stable `code`. Steer from the codes; the human-readable text may be reworded.",
            "title": "Detail",
            "type": "object"
          },
          "ok": {
            "description": "DID THIS BATCH LAND. True only when the writes were recorded; false when the door refused them and nothing was recorded, with the reason carried in `detail`.\n\n\u26a0\ufe0f CHANGED IN #1880, and a client that predates it may hold the old meaning. It used to answer 'did the request reach the grading door', which is a question nobody asks and which the HTTP status already carries \u2014 so it was TRUE on a refusal, and callers read that as their facts having been recorded when the record held none of them. A refused batch still answers HTTP 200 (the charter verdict IS the answer and you need to read it), so `ok` is the field that tells you whether anything was written. Branch on `ok`, not on the status code, and never on the wording of a reason.\n\nThe ONE exception, unchanged because it was already published and clients may key on it: a refused reserved writer answers HTTP 403 with `reserved_writer_refused: true`.",
            "title": "Ok",
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "WritesResponse",
        "type": "object"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "The Cohort customer API. Cohort is a metered, multi-tenant cloud agent harness: you create a\nsession, stream turns into it, and are billed for what the workers consume.\n\nSCOPE. Every endpoint Cohort offers consumers is here. Two surfaces are deliberately excluded, and\nthey are named so you do not have to infer them from silence:\n\n  /api/*      an operator-only admin console, behind a separate admin credential\n  /v1/quota   the Projex Core service-to-service seam -- HMAC-signed over the raw body, and\n              restricted by network policy to Core itself\n\nNeither is callable with a customer credential and neither is part of any contract; both may change\nwithout notice. If you find a path in either, it is not for you and will not be kept stable.\n\nAUTHENTICATION. Bearer Keycloak JWT (staff, workspace from the `workspaces` claim) or a\nworkspace-scoped API key. The workspace is ALWAYS taken from the credential and never from a\nrequest parameter -- a workspace key in a body or query string is not read.\n\nUNKNOWN FIELDS ARE REJECTED. POST /v1/sessions answers 422 on any field it does not declare,\nrather than accepting and ignoring it. If you are porting from the cohort.v1 gRPC surface, the\nerror names the field and its /v1 equivalent.\n",
    "title": "Cohort API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/v1/allowance": {
      "get": {
        "description": "What the CALLING key may spend. Its own allowance and nothing else. (#4113)\n\nA key's own sub-wallet is the only pot a run spends from, so this number alone decides\nwhether this credential can start work. Until now there was no way to ask: a key learned\nits allowance only by being REFUSED, mid-build, and the refusal was the first place the\ndistinction between \"never funded\" and \"spent out\" was ever stated - two states with\nopposite remedies.",
        "operationId": "key_allowance_v1_allowance_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyAllowance"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Key Allowance"
      }
    },
    "/v1/audit": {
      "get": {
        "operationId": "list_audit_v1_audit_get",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "This account's management audit trail, newest first",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/auth/config": {
      "get": {
        "operationId": "signin_config_v1_auth_config_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignInConfig"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Signin Config",
        "tags": [
          "auth"
        ]
      }
    },
    "/v1/balance": {
      "get": {
        "description": "dx-1: the caller's OWN balance. /v1/budgets lists only users with usage rows in a\nplatform, so a new account - the one sealed at zero credit - could read nothing about\nitself from the only surface it could reach. Resolves the caller the same way the credit\ngate does (own sub, else the account owner), then returns their quota row verbatim.\n\n`balance: null` is NOT zero: it means no row is provisioned yet, which is a different\nremedy (wait for the grant) than an empty wallet (top up).",
        "operationId": "tenant_balance_v1_balance_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Balance"
      }
    },
    "/v1/balance/movements": {
      "get": {
        "description": "WHAT MOVED THE CUSTOMER'S BALANCE - purchases, grants, refunds, corrections. (#1738)\n\nRENAMED FROM /balance/top-ups, and the rename is the honest part. \"Top-ups\" was accurate\nwhile only credits existed; the moment a refund can take money out, a path called top-ups\neither lies about its contents or hides them. It had no consumers - it shipped and was\npromoted the same day this changed - so this is the cheapest the rename will ever be.\n\nSIGNED, AND USAGE IS STILL EXCLUDED. Spending is already itemised at /v1/usage,\n/v1/usage/by-key and /v1/sessions/{id}/usage; a second differently-shaped view of the same\ndebits is how two numbers for one fact begin to disagree. This answers \"what did you do to\nmy balance\", and the existing surfaces answer \"what did I spend\".\n\n\u26a0\ufe0f The operator's internal note and the acting admin are NEVER in this response. They are\nseparate columns precisely so a customer-facing serialiser cannot reach them by accident -\nsee quota/wallet.py's allowlist, which both store twins go through.",
        "operationId": "tenant_balance_movements_v1_balance_movements_get",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceMovementList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Balance Movements"
      }
    },
    "/v1/budgets": {
      "get": {
        "operationId": "tenant_budgets_v1_budgets_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Budgets"
      }
    },
    "/v1/charter-disciplines": {
      "get": {
        "description": "The write disciplines a field may declare, and what each does (#4201).\n\nThe third leg of the authoring vocabulary next to /charter-types and /charter-kinds. The\ngap this closes was measured on a customer: the collection discipline was enforced,\nwritten to over the wire, and named in a criterion's help text, and no surface defined\nit, so they asked in a chat room. Served by the charter engine from the same table its\nvalidator accepts disciplines from and NOT reshaped; an outage is 503 and NEVER an empty\nlist, the same doctrine as its siblings.",
        "operationId": "charter_disciplines_v1_charter_disciplines_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterDisciplines"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Charter Disciplines",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charter-kinds": {
      "get": {
        "description": "The criterion kinds a charter may enforce, and how each behaves at the door (#1640).\n\nThe other half of /charter-types: what can be ENFORCED about a field - each kind's\nsemantics, its params, whether it is write-gated, and what on_violation does on it. The\ngap this closes was measured on a customer: enum_membership refused violating writes at\nthe submit door the whole time, no surface said so, and the customer rebuilt membership\nenforcement out of prompt heuristics.\n\nServed by the charter engine and NOT reshaped, same doctrine as /charter-types: the vocabulary is\ngenerated from the engine's own tables, and a copy held here would drift the day a kind\nlands. A charter-engine outage is 503 and NEVER an empty list - an empty vocabulary renders as\n\"nothing can be enforced\", which is the unknown-read-as-fact lie this seam exists to\nprevent.",
        "operationId": "charter_kinds_v1_charter_kinds_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterKinds"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Charter Kinds",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charter-types": {
      "get": {
        "description": "The value types a field may declare, and the options that define COMPLETE for each\n(#1424).\n\nServed by the charter engine and NOT reshaped. The authoring panel generates its controls from this,\nso a console holding its own copy of the option list would be the\none-rule-two-implementations defect: the day a type gains an option, the form silently stops\noffering it and the author's constraint quietly does nothing.\n\nA charter-engine outage is 503 and NEVER an empty list. An empty vocabulary renders as a dropdown\nwith no types - an authoring surface confidently showing that free text is the only thing a\nfield can hold, which is the \"unknown must not read as a fact\" rule this seam applies to\nevery listing.",
        "operationId": "charter_types_v1_charter_types_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterTypes"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Charter Types",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters": {
      "get": {
        "description": "References and their FILING (tags/note/supersedes/created_at), from Cohort's own rows.\n\nStill no bodies: a caller that needs one follows the digest to the body store - one store\n(seam \u00a72), and this endpoint cannot leak what it does not hold.\n\nThe FILING is Cohort's, scoped per workspace, and reading it from the charter engine would mix every\nworkspace's tags together - see CharterRef for the measurement. That is unchanged: the\nrows below are Cohort's own.\n\n#1423 adds the LINT, and only the lint. A start form must not offer a charter that\nwill be refused at open, and it cannot know that from a reference row.\n\nFAIL-SOFT, THREE-VALUED. A charter-engine outage leaves `lint` ABSENT rather than zeroed -\na listing must still render, and \"we could not ask\" must not arrive looking like \"clean\".",
        "operationId": "list_charters_v1_charters_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List Charters",
        "tags": [
          "charters"
        ]
      },
      "post": {
        "operationId": "save_charter_v1_charters_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CharterDocument"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterSaved"
                }
              }
            },
            "description": "Successful Response"
          },
          "403": {
            "description": "The caller is a machine credential without the 'governance:author' scope (#1625 \u2014 authoring is opt-in at mint; use is the default). The refusal detail reads: this key does not hold the 'governance:author' scope, so it cannot author charters or packs. Authoring needs a key minted with scopes=[\"governance:author\"] (POST /v1/keys), or a person signed in on the dashboard. A key without the scope still opens and drives sessions as before."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Save Charter",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters/{digest}": {
      "get": {
        "description": "The charter body - the ONE body store. Scope check FIRST: only digests this\nworkspace holds a reference to are proxied, so the read cannot become a cross-workspace\n(or anonymous) body oracle. Same 404 for absent-here and absent-everywhere.\n\nTHE PATH SEGMENT ALSO RESOLVES `{name}@latest` (URL-encoded; a digest never contains '@'),\nso a platform can ask for 'the current assistant charter' without hardcoding a hash and\npin the digest the answer carries. `latest` skips retired versions.\n\n\u26a0\ufe0f `{name}@{version}` NO LONGER RESOLVES (#1427, Lophie's ruling 4: \"the user gets a digest\nand edit timestamp. and thats the history of the chart.\"). A version label is free text on\nthe body now - it is not unique, so it cannot address anything. Asking for one is a 404\nthat SAYS SO rather than silently resolving whichever row happened to match first, which\nis what a non-unique identifier would do. Pin a DIGEST for a stable readback of history:\ndigests are addresses, labels never were.",
        "operationId": "get_charter_body_v1_charters__digest__get",
        "parameters": [
          {
            "in": "path",
            "name": "digest",
            "required": true,
            "schema": {
              "title": "Digest",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterBody"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Get Charter Body",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters/{digest}/changes": {
      "get": {
        "description": "The prev-to-this changelog, computed here rather than stored. (AC13)\n\nCOMPUTED, NOT RECORDED, and that is deliberate: a stored diff is a THIRD copy of the truth\n(two bodies plus a summary of their difference) and the copy is the one that goes stale.\nBoth bodies are reachable from here, so the diff is derived on read and cannot disagree\nwith them.\n\n\u26a0\ufe0f SCOPE CHECK FIRST, ON BOTH DIGESTS. A `from` override that skipped the check would make\nthis a cross-workspace body oracle by difference - you would not read the other charter,\nbut you would learn its every field name and value from the diff. Same 404 for\nabsent-here and absent-everywhere.\n\n\u26a0\ufe0f WHERE IT LIVES. Cohort computes it because both bodies are reachable here and the SPA is\nthe consumer, and it is GENERIC over values - it reports THAT a param changed, never what\nthe change means. If a changelog must ever explain MEANING, that is the charter engine's knowledge and\nthis endpoint moves to comp 57 rather than Cohort growing a second copy of charter\nsemantics that goes wrong the day a criterion kind is added.",
        "operationId": "charter_changes_v1_charters__digest__changes_get",
        "parameters": [
          {
            "in": "path",
            "name": "digest",
            "required": true,
            "schema": {
              "title": "Digest",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from_digest",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "From Digest"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterChanges"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "The structured diff from this version's ancestor to it",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters/{digest}/retire": {
      "post": {
        "description": "RETIRE, not delete (#1426). The digest is load-bearing history - running sessions pin\nit and sealed artifacts cite it - so nothing is erased. A retired version: refuses NEW\nsessions at the admission gate, leaves default listings, and stops resolving as\nname@latest. Reads by digest and by name@version keep working: an audit trail that goes\n404 is not an audit trail. Idempotent - retiring twice keeps the FIRST retirement time.",
        "operationId": "retire_charter_v1_charters__digest__retire_post",
        "parameters": [
          {
            "in": "path",
            "name": "digest",
            "required": true,
            "schema": {
              "title": "Digest",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterSaved"
                }
              }
            },
            "description": "Successful Response"
          },
          "403": {
            "description": "The caller is a machine credential without the 'governance:author' scope (#1625 \u2014 authoring is opt-in at mint; use is the default). The refusal detail reads: this key does not hold the 'governance:author' scope, so it cannot author charters or packs. Authoring needs a key minted with scopes=[\"governance:author\"] (POST /v1/keys), or a person signed in on the dashboard. A key without the scope still opens and drives sessions as before."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Retire Charter",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters/{name}/history": {
      "get": {
        "description": "THE HISTORY IS: digest + edit timestamp + the author's save comment. (AC12)\n\n\u26a0\ufe0f RETIRED VERSIONS ARE INCLUDED. Retiring moves a lineage on; it does not un-happen a\nversion. Running sessions pin retired digests and sealed artifacts cite them, so a history\nthat hid them would answer \"what did this charter used to say\" with a lie by omission.\n\n\u26a0\ufe0f AN UNKNOWN NAME IS AN EMPTY LIST, NOT A 404 - and only because the SCOPE CHECK already\nhappened: `lineage` is workspace-filtered, so an empty answer means \"nothing here\", never\n\"nothing anywhere\". Distinguishing \"no such charter\" from \"not yours\" would turn this route\ninto a cross-workspace existence oracle for any name a caller cares to guess.",
        "operationId": "charter_history_v1_charters__name__history_get",
        "parameters": [
          {
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "title": "Name",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterHistory"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Every version ever saved under this charter name, newest first",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters/{ref_workspace}/{ref_slug}": {
      "get": {
        "description": "The same body as `GET /v1/charters/{ref}`, addressed the long way.\n\nOne resolver, one grammar: this spelling and the bare one and session create all reach the\nsame digest or refuse for the same reason (#2058).",
        "operationId": "get_charter_body_qualified_v1_charters__ref_workspace___ref_slug__get",
        "parameters": [
          {
            "in": "path",
            "name": "ref_workspace",
            "required": true,
            "schema": {
              "title": "Ref Workspace",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "ref_slug",
            "required": true,
            "schema": {
              "title": "Ref Slug",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterBody"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "A charter body by its qualified reference, '<workspace>/<slug>'",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters:all": {
      "get": {
        "description": "The caller's WHOLE charter library, across every workspace they already hold.\n\nWHY THIS EXISTS (#1381, \"detach charters from workspaces\"). A charter is reusable knowledge\n- the same intake rules serve several projects - so making the workspace the only way to\norganise them meant re-registering a charter just to find it, and tags exist precisely to\norganise ACROSS that boundary.\n\nWHAT IS *NOT* DETACHED, DELIBERATELY. The charter workspace scope is a SECURITY boundary, not a\nfiling convenience, and a session's charter must resolve inside the workspace that opened\nit. Nothing is widened in the engine. This aggregates in Cohort, where the identity is\nknown, and does no more than loop over workspaces the caller could already read one at a\ntime.\n\nVISIBILITY WIDENS ACROSS A CUSTOMER'S OWN WORKSPACES, NEVER ACROSS CUSTOMERS. An API key\nholds exactly one workspace and gets exactly that one back - this is not a way for a\nmachine credential to see past its scope.\n\nEach row names its `workspace`, because two workspaces may legitimately hold the same\ndigest and a library that hid that would be lying about what is already reusable.",
        "operationId": "list_charters_across_workspaces_v1_charters_all_get",
        "parameters": [
          {
            "description": "Set by a PLATFORM verifying this credential on the holder's behalf, so its call is recorded as a probe instead of as the customer's own traffic. Self-declared and advisory: a caller that sets it wrongly only mislabels its own usage record.",
            "in": "query",
            "name": "probe",
            "required": false,
            "schema": {
              "default": false,
              "description": "Set by a PLATFORM verifying this credential on the holder's behalf, so its call is recorded as a probe instead of as the customer's own traffic. Self-declared and advisory: a caller that sets it wrongly only mislabels its own usage record.",
              "title": "Probe",
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List Charters Across Workspaces",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/charters:validate": {
      "post": {
        "description": "Dry run for the authoring surface: the charter verdict + the digest these exact\nrules WOULD get - register nothing. The 422 body is the validator's own, verbatim, because the\nseam writes those errors for inline render and rewording destroys that.",
        "operationId": "validate_charter_v1_charters_validate_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CharterDocument"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharterValidation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Validate Charter",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/chat/completions": {
      "post": {
        "operationId": "complete_v1_chat_completions_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompletionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "OpenAI-compatible chat completion. One shot: no session, no worker, no tools.",
        "tags": [
          "completions"
        ]
      }
    },
    "/v1/completions": {
      "post": {
        "operationId": "complete_v1_completions_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompletionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompletionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Alias of /chat/completions (Cohort extension; kept for existing callers).",
        "tags": [
          "completions"
        ]
      }
    },
    "/v1/credentials/propagation": {
      "get": {
        "description": "The propagation semantics, GENERATED from the same settings the boot check asserts. (#1201)\n\nThe answer is \"on the next session\" - credentials are delivered once, at spawn, and are\nfixed for that worker's life. That is a deliberate decision rather than an oversight, and\npublishing it is the point: a customer who has just revoked a credential needs to know\nwhether it is actually gone, and the worst time to find out is during the incident that\nmade them revoke it.",
        "operationId": "tenant_credential_propagation_v1_credentials_propagation_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CredentialPropagation"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "When a rotated credential takes effect, and what happens to running sessions"
      }
    },
    "/v1/evidence": {
      "get": {
        "operationId": "tenant_evidence_v1_evidence_get",
        "parameters": [
          {
            "in": "query",
            "name": "run_id",
            "required": false,
            "schema": {
              "default": "",
              "title": "Run Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "condition_id",
            "required": false,
            "schema": {
              "default": "",
              "title": "Condition Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvidenceList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Evidence"
      }
    },
    "/v1/goals": {
      "get": {
        "operationId": "tenant_goals_v1_goals_get",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Goals"
      },
      "post": {
        "operationId": "tenant_create_goal_v1_goals_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GoalCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Create Goal"
      }
    },
    "/v1/goals/{goal_id}": {
      "get": {
        "operationId": "tenant_goal_v1_goals__goal_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "goal_id",
            "required": true,
            "schema": {
              "title": "Goal Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoalEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Goal"
      }
    },
    "/v1/goals/{goal_id}/conditions": {
      "get": {
        "operationId": "tenant_conditions_v1_goals__goal_id__conditions_get",
        "parameters": [
          {
            "in": "path",
            "name": "goal_id",
            "required": true,
            "schema": {
              "title": "Goal Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConditionList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Conditions"
      },
      "post": {
        "operationId": "tenant_create_condition_v1_goals__goal_id__conditions_post",
        "parameters": [
          {
            "in": "path",
            "name": "goal_id",
            "required": true,
            "schema": {
              "title": "Goal Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConditionCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConditionEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Create Condition"
      }
    },
    "/v1/goals/{goal_id}/tasks": {
      "get": {
        "operationId": "tenant_tasks_v1_goals__goal_id__tasks_get",
        "parameters": [
          {
            "in": "path",
            "name": "goal_id",
            "required": true,
            "schema": {
              "title": "Goal Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Tasks"
      },
      "post": {
        "operationId": "tenant_create_task_v1_goals__goal_id__tasks_post",
        "parameters": [
          {
            "in": "path",
            "name": "goal_id",
            "required": true,
            "schema": {
              "title": "Goal Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TaskCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TaskEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Create Task"
      }
    },
    "/v1/identity": {
      "get": {
        "description": "Cohort's designated credential-probe target. Safe to call on every deploy.\n\nCosts nothing, changes nothing, and is recorded as a PROBE rather than as customer traffic.",
        "operationId": "identity_v1_identity_get",
        "parameters": [
          {
            "description": "Set by a PLATFORM verifying this credential on the holder's behalf, so its call is recorded as a probe instead of as the customer's own traffic. Self-declared and advisory: a caller that sets it wrongly only mislabels its own usage record.",
            "in": "query",
            "name": "probe",
            "required": false,
            "schema": {
              "default": false,
              "description": "Set by a PLATFORM verifying this credential on the holder's behalf, so its call is recorded as a probe instead of as the customer's own traffic. Self-declared and advisory: a caller that sets it wrongly only mislabels its own usage record.",
              "title": "Probe",
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Identity"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Verify a credential and report who it belongs to"
      }
    },
    "/v1/keys": {
      "get": {
        "operationId": "list_keys_v1_keys_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List this workspace's API keys",
        "tags": [
          "keys"
        ]
      },
      "post": {
        "operationId": "mint_key_v1_keys_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MintKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MintKeyResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Mint an API key for this workspace",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/keys/{key_id}": {
      "delete": {
        "description": "Revocation takes effect on the NEXT request the key makes - the store is consulted per\nrequest, so there is no cached-credential window to wait out.",
        "operationId": "revoke_key_v1_keys__key_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "key_id",
            "required": true,
            "schema": {
              "title": "Key Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRevoked"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Revoke an API key",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/keys/{key_id}/money-mover": {
      "delete": {
        "description": "Takes effect on the key's NEXT credit call. The key keeps its grants - only the\nperson's decision is withdrawn, so `credit.reserve` still shows in the listing and the\nkey is refused at the credit door until somebody attests again.\n\nThis is the route to use on a key that holds `credit.reserve` and should not: it stops\nthe key moving money WITHOUT re-minting it, so nothing else the key does is disturbed.",
        "operationId": "withdraw_money_mover_v1_keys__key_id__money_mover_delete",
        "parameters": [
          {
            "in": "path",
            "name": "key_id",
            "required": true,
            "schema": {
              "title": "Key Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyMoverAttestation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Withdraw a key's money-mover attestation",
        "tags": [
          "keys"
        ]
      },
      "post": {
        "description": "Records that YOU decided this key may call POST /v1/workspaces/*/keys/*/credit.\n\nThe credit door reads BOTH axes: the key must hold `credit.reserve` in its grants AND\ncarry an attestation here. Attesting a key that does not hold the verb is allowed and\ndoes nothing on its own - the grant is still the thing that has to be minted.",
        "operationId": "attest_money_mover_v1_keys__key_id__money_mover_post",
        "parameters": [
          {
            "in": "path",
            "name": "key_id",
            "required": true,
            "schema": {
              "title": "Key Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyMoverAttestation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Attest that a person authorised this key to move money",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/live/token": {
      "get": {
        "description": "Mint a subscriber credential for the caller's OWN workspace (#1405).\n\nWHY THE SERVER MINTS IT. The token grants a read of a workspace's event stream, so the\nonly safe place to decide WHICH workspace is where the caller's identity is already\nchecked. `require_tenant` resolves that from the bearer; the browser never names its own\nscope. A page that could assemble its own claims could read another tenant's activity.\n\nWHY IT IS SHORT-LIVED. The client passes it as a query parameter - EventSource cannot set\nan Authorization header - so it lands in hub access logs and browser history. Minutes, one\nworkspace, subscribe-only: a leak buys a narrow read for a short window.\n\nA NULL TOKEN IS A 200, NOT AN ERROR. An unconfigured live path is a deployment that runs on\nits polling floor, which is a working dashboard - failing this call would turn a missing\nnicety into a broken screen.\n\n\ud83d\udd34 AND IT IS RATE LIMITED, because this route took production down (#2062). A dashboard bug\nminted 2-3 times a second; each mint cost an RSA parse on the event loop plus three Postgres\nqueries through a five-connection pool, and the pod was SIGKILLed for failing its liveness\nprobe eight times in 89 minutes. Both per-call costs are fixed now - the signing key is\nparsed once - but a cheap endpoint called without bound is still a denial of service, and the\none thing a client bug must not be able to do is end other tenants' sessions.\n\nTHE BOUND IS DELIBERATELY FAR ABOVE HONEST USE. A correct client mints on connect and then\nonce per token renewal, about five times an hour. Anything near this ceiling is a loop.",
        "operationId": "live_token_endpoint_v1_live_token_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LiveTokenResponse"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Short-lived credential for this workspace's live event stream",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/management-keys": {
      "get": {
        "operationId": "list_management_keys_v1_management_keys_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManagementKeyList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List this account's management keys",
        "tags": [
          "keys"
        ]
      },
      "post": {
        "operationId": "mint_management_key_v1_management_keys_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MintManagementKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MintManagementKeyResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Mint a management key for this account (human login required)",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/management-keys/{key_id}": {
      "delete": {
        "description": "Top-down revocation, and the reason the ladder is safe: whatever a management key did,\na human can always end it. Takes effect on the key's next request - the store is consulted\nper request, so there is no cached-credential window.",
        "operationId": "revoke_management_key_v1_management_keys__key_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "key_id",
            "required": true,
            "schema": {
              "title": "Key Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRevoked"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Revoke a management key",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/models": {
      "get": {
        "operationId": "list_models_v1_models_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelsResponse"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "The model aliases a session may run on (feeds the `model` field of POST /v1/sessions)",
        "tags": [
          "models"
        ]
      }
    },
    "/v1/openapi.json": {
      "get": {
        "description": "The contract a consumer generates a client from, served as the COMMITTED bytes. (#4242)\n\nThe console's API-reference tab linked this URL while nothing served it (404 by\nconstruction: FastAPI's live /openapi.json is disabled on purpose, and the image ships no\ndocs/ directory). What is served is the generator's second copy of docs/openapi.json under\nthe package, pinned to the repo file by test and by the generator's own --check, so the\nbytes a reader fetches here are the bytes captured from the commit tree at deploy. Keyless\nlike the retention policy: the document describes the platform, never an account.",
        "operationId": "tenant_openapi_document_v1_openapi_json_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDocument"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "This API's published contract: the committed docs/openapi.json, byte for byte"
      }
    },
    "/v1/pack-uploads": {
      "get": {
        "operationId": "list_uploads_v1_pack_uploads_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUploadList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "This workspace's uploads, newest first",
        "tags": [
          "packs"
        ]
      },
      "post": {
        "description": "Declares one pack file by name and exact size, and answers with an upload id, the part size and the number of parts. Send each part with PUT .../parts/{n}, then complete. A file larger than `max_bytes` is refused here, before any byte is sent (413 `upload.too_large`). One upload per account is ingested at a time; a completed upload waits as `queued` behind the one in progress.",
        "operationId": "create_upload_v1_pack_uploads_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PackUploadCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Declare a pack file to upload in parts",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/pack-uploads/{upload_id}": {
      "get": {
        "operationId": "get_upload_v1_pack_uploads__upload_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "An upload's status",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/pack-uploads/{upload_id}/cancel": {
      "post": {
        "description": "Stops the upload and releases its stored bytes. An ingest that is queued never starts; one in progress is stopped. A failed upload keeps its outcome and can no longer be retried. A `ready` upload is answered as it is and nothing changes: the pack stays registered.",
        "operationId": "cancel_upload_v1_pack_uploads__upload_id__cancel_post",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Cancel an upload",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/pack-uploads/{upload_id}/complete": {
      "post": {
        "description": "Checks every part is stored, the total matches the declared size, and the sha256 when one was declared, then queues the ingest. Completing again is harmless. The status then moves through `queued`, `running` with stage `validating` (bytes sent, then rows checked) and `indexing` (entries stored), to `ready` or `failed`.",
        "operationId": "complete_upload_v1_pack_uploads__upload_id__complete_post",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Finish an upload and queue its ingest",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/pack-uploads/{upload_id}/dismiss": {
      "post": {
        "description": "For an upload that is ready, failed or cancelled. It leaves GET /v1/pack-uploads and any bytes it still holds are released; it stays readable by id, and a registered pack stays registered. An upload still sending, queued or ingesting is refused (409 `upload.active`): cancel it first.",
        "operationId": "dismiss_upload_v1_pack_uploads__upload_id__dismiss_post",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Take a finished upload off the list",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/pack-uploads/{upload_id}/parts/{part_no}": {
      "put": {
        "description": "The request body is the part's raw bytes (any content type). Part numbers run from 0. Every part is exactly `part_bytes` long except the last. Sending a part that is already stored with the same bytes changes nothing; with different bytes it is refused (409 `upload.part_changed`).",
        "operationId": "put_part_v1_pack_uploads__upload_id__parts__part_no__put",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "part_no",
            "required": true,
            "schema": {
              "title": "Part No",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Send one part of an upload",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/pack-uploads/{upload_id}/retry": {
      "post": {
        "description": "Only when the last ingest failed with `retryable: true` (for example, the pack service was not reachable). The stored parts are reused; nothing is sent again. Any other state is refused (409 `upload.not_retryable`).",
        "operationId": "retry_upload_v1_pack_uploads__upload_id__retry_post",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackUpload"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Run a failed ingest again",
        "tags": [
          "packs"
        ]
      }
    },
    "/v1/packs": {
      "get": {
        "description": "References + the signed state COMPUTED from sign-off rows - never stored on the ref,\nso it cannot go stale against the rows it derives from.",
        "operationId": "list_packs_v1_packs_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List Packs",
        "tags": [
          "charters"
        ]
      },
      "post": {
        "description": "Same shape as the charter save, without a separate validate: a pack's shape check is\nthe charter engine's register (seam \u00a72 - charters that CITE packs are validated with their refs).",
        "operationId": "save_pack_v1_packs_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PackDocument"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackSaved"
                }
              }
            },
            "description": "Successful Response"
          },
          "403": {
            "description": "The caller is a machine credential without the 'governance:author' scope (#1625 \u2014 authoring is opt-in at mint; use is the default). The refusal detail reads: this key does not hold the 'governance:author' scope, so it cannot author charters or packs. Authoring needs a key minted with scopes=[\"governance:author\"] (POST /v1/keys), or a person signed in on the dashboard. A key without the scope still opens and drives sessions as before."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Save Pack",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/packs/{digest}": {
      "get": {
        "description": "The pack body. Scope check FIRST, exactly as charters do it: only\ndigests this workspace holds a reference to are proxied, so this cannot become a\ncross-workspace body oracle. Same 404 for absent-here and absent-everywhere.\n\nBefore this door there was no pack store at all, and pack contents were\nwrite-only from every surface.",
        "operationId": "get_pack_body_v1_packs__digest__get",
        "parameters": [
          {
            "in": "path",
            "name": "digest",
            "required": true,
            "schema": {
              "title": "Digest",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackBody"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Get Pack Body",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/packs/{digest}/signoff": {
      "post": {
        "description": "Sign-off is a PERSON's act, never a workspace's. An API key is refused outright - the\nsame escalation boundary as key management: a leaked machine key must not be able to bless\na charter pack. The signer recorded is the JWT's stable subject, and this row is what\nthe assurance label and the signed_packs_required policy (enforced at session-open, slice\n(c)) are computed from.",
        "operationId": "sign_pack_v1_packs__digest__signoff_post",
        "parameters": [
          {
            "in": "path",
            "name": "digest",
            "required": true,
            "schema": {
              "title": "Digest",
              "type": "string"
            }
          },
          {
            "description": "Set by a PLATFORM verifying this credential on the holder's behalf, so its call is recorded as a probe instead of as the customer's own traffic. Self-declared and advisory: a caller that sets it wrongly only mislabels its own usage record.",
            "in": "query",
            "name": "probe",
            "required": false,
            "schema": {
              "default": false,
              "description": "Set by a PLATFORM verifying this credential on the holder's behalf, so its call is recorded as a probe instead of as the customer's own traffic. Self-declared and advisory: a caller that sets it wrongly only mislabels its own usage record.",
              "title": "Probe",
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackSaved"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Sign Pack",
        "tags": [
          "charters"
        ]
      }
    },
    "/v1/pricing": {
      "get": {
        "description": "The rate card a workspace is billed against. Auth-gated but NOT workspace-specific -\nrates are fleet-wide today. Exposed so a customer can reconcile an invoice themselves\ninstead of asking what a number meant, and so `estimated_models` makes it visible when a\nmodel is being billed at the fallback rather than a configured price.",
        "operationId": "tenant_pricing_v1_pricing_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricingCard"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Pricing"
      }
    },
    "/v1/retention": {
      "get": {
        "description": "The retention policy, GENERATED from the same table the sweep enforces. (#1200)\n\nPublished rather than left to be asked, because \"how long do you keep our data\" is a\nprocurement question and the previous honest answer was \"forever, and you cannot remove\nit\". Generated rather than written, because a policy document and the code that enforces it\nare two statements of one commitment and they drift - at which point the document is a\npromise the system is not keeping.",
        "operationId": "tenant_retention_v1_retention_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RetentionPolicy"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "What Cohort keeps, for how long, and what you can delete"
      }
    },
    "/v1/runs": {
      "get": {
        "operationId": "tenant_runs_v1_runs_get",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunsView"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Runs"
      }
    },
    "/v1/sessions": {
      "get": {
        "operationId": "list_sessions_v1_sessions_get",
        "parameters": [
          {
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "default": "",
              "title": "Include",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List this workspace's live sessions",
        "tags": [
          "sessions"
        ]
      },
      "post": {
        "description": "Start a session. Pass `charter_digest` (or resolve one via GET /v1/charters/{name}@latest) for a CHARTERED session: the agent gains record/charter/defer/finish tools plus `was_said` (ask whether the person said something, recording nothing), every write is graded by the charter engine, and a clean seal yields a durable artifact at GET /v1/sessions/{id}/artifact.\n\n## Session lifecycle \u2014 read this before integrating\n\nA session is a DURABLE CONVERSATION; its worker is ephemeral compute. Drive it bidirectionally: stream GET /v1/sessions/{id}/events (SSE, `Last-Event-ID` resume) and send turns with POST /v1/sessions/{id}/input. After ~300 seconds idle the worker is reclaimed and the session PAUSES \u2014 it still lists as live, still counts as one of your running sessions, and the next input transparently respawns a worker with the conversation replayed (expect one cold-start delay on that turn).\n\n## When your account is busy\n\nYour account may run a certain number of sessions at once. A create beyond that is never refused for capacity: it answers 202 with `phase: queued`. A queued session exists, is listed, reports its state at GET /v1/sessions/{id}, and can be ended. It starts by itself, in the order sessions were created, when one of your running sessions ends. Queued time is not billed and does not spend the session's budget. Input to a session that is still queued is refused with 409 `session_queued`. If you subscribe to GET /v1/sessions/{id}/events, a queued session emits `{\"type\": \"queued\"}` when it is queued and `{\"type\": \"starting\"}` the moment it is admitted, so a subscriber need not poll to learn that the wait is over.\n\nSessions your own client drives (`doer: client`) run no worker here and never count toward that number, however many you hold open.\n\n\u26a0\ufe0f **END SESSIONS YOU ARE DONE WITH** (POST /v1/sessions/{id}/end). A dangling session counts as running until you end it or the staleness horizon (twice the worker TTL, at least one hour) forgives it, and every session you create meanwhile waits behind it. Sessions that seal cleanly end THEMSELVES; everything else is yours to close.\n\n**Waking a forgiven session.** A session the horizon has forgiven stays resumable, and input to it waits in the same queue when your account is busy, rather than starting past what the account may run.\n\nWhat a dangling session costs, precisely: a place among your running sessions until ended-or-horizon, plus worker compute only until the ~300s idle reclaim (a paused session bills no compute). What clears it: your explicit /end, a clean seal, or the horizon.",
        "operationId": "create_session_v1_sessions_post",
        "parameters": [
          {
            "description": "Optional. Retry-safe creation: the SAME key with the SAME body returns the original session instead of starting a second one. The same key with a DIFFERENT body is refused with 409 \u2014 a key identifies one request, not one caller. Scoped to your workspace, so your keys cannot collide with another customer's. A create that is REFUSED frees its key immediately, so the same key and body can be retried; a reservation whose request died stops blocking after a stated time, named in the 409.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Optional. Retry-safe creation: the SAME key with the SAME body returns the original session instead of starting a second one. The same key with a DIFFERENT body is refused with 409 \u2014 a key identifies one request, not one caller. Scoped to your workspace, so your keys cannot collide with another customer's. A create that is REFUSED frees its key immediately, so the same key and body can be retried; a reservation whose request died stops blocking after a stated time, named in the 409.",
              "title": "Idempotency-Key"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSessionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateSessionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "402": {
            "description": "No key to bill. A HUMAN caller omitted `billing_key_id` (`no_billing_key`), or the named key has no credit reserved to it, or its workspace has none appointed. The detail carries `reason` and `rung` naming WHICH rung is empty, because the remedy differs: `workspace_wallet_absent` means the account owner must appoint credit to the workspace, `key_wallet_absent` means the workspace has money and none is reserved to this key, `key_budget_exhausted` means it was funded and is spent. There is no fallback to the account (#1876)."
          },
          "403": {
            "description": "An API-KEY caller sent `billing_key_id` naming a DIFFERENT key. A key bills its own wallet and cannot nominate another \u2014 a key able to nominate is a key able to spend somebody else's wallet. Omit the field entirely when authenticating with an API key; attribution is already correct, since the key that authenticates is the wallet that pays. The detail reads: `billing_key_not_overridable`."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Start a session (job-submission front door)",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}": {
      "get": {
        "description": "Status without opening a stream - what a job caller polls.\n\n`live` distinguishes a session the manager still holds from one that has finished. A caller\nthat sees live=false and has drained its events knows the job is over; without this it\ncannot tell \"finished\" from \"quiet\", which is the same ambiguity the SSE terminal event\nexists to remove on the streaming path.\n\nOwnership resolves through the bridge for LIVE sessions and falls back to the durable\nrecord for ENDED ones - measured on prod the hour this shipped: the bridge forgets a\nsession at teardown, so without the fallback the one session state this endpoint newly\nserves (ended, with its outcome) answered 404. The record's own workspace check keeps the\nno-oracle rule: another workspace's ended session is indistinguishable from none.",
        "operationId": "get_session_v1_sessions__session_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionStatus"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Session status",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/artifact": {
      "get": {
        "description": "The product of a chartered session, which until now had no door at all.\n\nA caller could grade a run (/check), watch it (/diagnostics) or read the machinery's own\nprojection, and none of those hand back the THING THAT WAS BUILT - so the artifact could\nonly be recovered by reading a diagnostics view, which answers \"is this healthy\", a\ndifferent question from \"what did we make\". A chartered session whose output reads out of\ninstrumentation is one nobody runs twice.\n\nThe charter artifact door owns the shape: charter order and labels, the seal, and per-field\nprovenance - which values were verified against something the subject actually said.\nCohort forwards it VERBATIM and interprets nothing, so no field name or value vocabulary\nreaches this file.\n\nREAD-ONLY AND ALLOWED ON AN ARCHIVED WORKSPACE, deliberately: the artifact outlives the\nwork, and the moment a customer most needs it is after everything is over.\n\nALSO ALLOWED ON AN ENDED SESSION, for the same reason.",
        "operationId": "session_artifact_endpoint_v1_sessions__session_id__artifact_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "What the chartered session BUILT \u2014 the assembled record",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/budget": {
      "patch": {
        "description": "Raise or lower `max_cost_usd` on a session that is already running.\n\nWHY THIS EXISTS AS ITS OWN ROUTE, not a re-POST to /sessions. A customer running several\nconcurrent sessions who notices one is starved of credit needs to redirect budget to the\nothers WHILE THEY ARE STILL RUNNING - waiting for a natural respawn is not \"now\" on a\nwarm-idle session that could sit unrotated for a long time.\n\nWHY THIS UPDATES THE LIVE GATEWAY KEY, not just the ledger. The vkey rides into the worker\nas a spawn-time env var with no refresh path - a re-mint (new key string) is invisible to\nan already-running worker, which keeps calling the gateway under the OLD key at the OLD\nceiling. Updating the SAME key's max_budget in place (llm_key.set_run_budget, via the\ngateway's own /key/update) is the only lever that reaches a worker that is already up.\n\nTHE 404 IS THE SAME NO-ORACLE SHAPE AS EVERY OTHER SESSION ROUTE HERE: a session that\nbelongs to another workspace and a session that does not exist must be indistinguishable,\nor the 404 becomes an enumeration oracle for session ids across tenants.",
        "operationId": "update_session_budget_v1_sessions__session_id__budget_patch",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BudgetUpdateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetUpdateResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Change a LIVE session's declared max cost (W4)",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/check": {
      "get": {
        "operationId": "check_session_endpoint_v1_sessions__session_id__check_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Current charter verdict for a chartered session (no seal attempt)",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/claims": {
      "post": {
        "operationId": "submit_session_claims_v1_sessions__session_id__claims_post",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClaimsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClaimsResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Submit the doer's answer for gating (type-refuse + by-value membership) and seal",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/content": {
      "delete": {
        "description": "Remove the content Cohort stored for one session, on request, immediately.\n\nBefore this, the published surface had exactly ONE HTTP DELETE - the API-key route - and it\nis a REVOKE, not a deletion. \"What happens to our data, and can we get it removed\" had the\nhonest answer \"nothing, and no\".\n\n\u26a0\ufe0f THE MONEY STAYS, and that is not a loophole. Usage rows live in a different table and are\nfinancial records: they are what your invoice reconciles against, and what the Core billing\nseam reads. A customer able to delete the evidence of what they were billed for would be a\nworse feature than no deletion at all - in both directions.\n\n\u26a0\ufe0f DELETION CANNOT UNSEND. Metadata you supplied reached the model's context and was echoed\nto your webhook endpoint. Removing our copy retracts neither.\n\nWHAT IT REMOVES (#4197): the session's record row and its stored turns, the conversation\nthat `GET /sessions/{id}/turns` reads back. The engine's chain of facts and verdicts is a\nseparate record with its own retention and is not touched here.\n\nReachable for an ARCHIVED workspace too, named explicitly - a customer offboarding is\nexactly when they ask for their content to be removed, and archive must not lock that door.",
        "operationId": "delete_session_content_v1_sessions__session_id__content_delete",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentDeleted"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Delete this session's stored content (retention, #1200)",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/diagnostics": {
      "get": {
        "operationId": "session_diagnostics_v1_sessions__session_id__diagnostics_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DiagnosticsResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Merged diagnostics read: durable status + charter verdict + field projection",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/end": {
      "post": {
        "description": "#1390: /end resolves from the SAME set of sessions the GET serves - live through the\nbridge, everything else through the durable record. It used to resolve only the bridge's\nlive map, so a never-spawned session (which the bridge forgets on refusal) answered 404\n\"no such session\" while GET answered 200: visible, pending forever, unkillable. Two\nreaders of one identity must not disagree about its existence.\n\nEnding a session that never ran, or ran and already ended, is an IDEMPOTENT yes - the\ncaller's intent (\"this session is over\") is already true, and a refusal would leave them\nwith nothing to do about a session we still show them. The no-oracle rule holds: the\nrecord lookup is workspace-scoped, so another tenant's session id is indistinguishable\nfrom none.",
        "operationId": "end_session_v1_sessions__session_id__end_post",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "reason",
            "required": false,
            "schema": {
              "default": "api",
              "title": "Reason",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionEnded"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "End a session",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/events": {
      "get": {
        "description": "Server-Sent Events over plain HTTP.\n\nSSE rather than a WebSocket on purpose: it traverses the corporate proxies and CDNs that\nbreak WS upgrades - which is the population this whole inbound direction exists to serve -\nand it carries resume semantics natively. Input is a separate POST, which makes every turn\nan independently authorizable, retryable request instead of an unlogged frame.\n\n\ud83d\udd11 WHICH TEXT TO SHOW A PERSON (#4255). A tool-using agent writes text between its tool\ncalls and then the answer after the last one. `output` frames carry `channel`: show a\nperson only `channel: \"spoken\"`; `channel: \"working\"` is the agent's commentary (tool\npreambles, notes to itself, reactions to refusals) and is for an operator. `turn_content`\ncarries `spoken`, the same text assembled for the turn; `messages` is the audit record and\nholds both. An empty `channel` is a runtime that predates channels: treat it as spoken.\n\n\ud83d\udd11 WHEN THE AGENT'S MEMORY IS REWRITTEN (#4265). A long session compacts its own history\nbefore the model's window fills; a `state` event with `state: \"compaction\"` marks it,\n`tool` saying `proactive` (before a call, by budget) or `overflow` (after the model refused\nthe call), followed by a working note with the sizes. The record is not touched by a\ncompaction; the agent's working memory is. A turn that would exceed the window even\nafter compaction ends with `stop_reason: \"guard_context_ceiling\"` and a spoken message\ntelling the person to continue in a new session.\n\n\ud83d\udd11 WHEN A GUARD ENDS THE TURN (#4281). `guard_context_ceiling` is one of several guard\nstops, all of which leave the session LIVE for your next input. A `state` event with\n`state: \"guard\"` carries the code in `tool_name`, followed by a working note saying what\nhappened - for a refusal storm, which field was refused and how often. The whole code\nvocabulary, and what is NOT promised about it, is on `stop_reason` in the turn_ended event.\nOnly the ceiling is ever spoken to the person; the rest are for an operator.\n\n\ud83d\udd11 WHEN A TURN FAILS (#4294). If the model call itself fails, the turn ends with\n`stop_reason` `turn_failed_upstream` or `turn_failed`, and the failure is ALSO recorded\nwhere you poll: the turn on `GET /sessions/{id}/turns` carries `failure` (code, the\nupstream's HTTP status, and the support `ref`), and `GET /sessions/{id}` carries\n`last_turn_failure`. You do not need a connected stream to learn that a turn failed.\n\n\u26a0\ufe0f THE DURABILITY PROMISE, stated because offering Last-Event-ID resume implies a stronger\none than the implementation makes. HISTORY IS BEST-EFFORT AND IN-PROCESS: a bounded ring of\nthe most recent 500 events per session, held in memory. It does NOT survive an orchestrator\nrestart, and a session busier than 500 events will lose its oldest.\n\nSO GAPS ARE POSSIBLE, AND THEY ARE ANNOUNCED. If you resume from an id whose successors are\nno longer buffered, the FIRST frame you receive is `{\"type\": \"gap\", ...}` carrying\n`first_available_seq` and a reason (`ring_overflow` or `no_history`). It is emitted BEFORE\nthe events it precedes, so you learn you are missing history before you start reconciling\nfrom what survived.\n\nThose events cannot be recovered from this stream. Treat the gap marker as \"stop\nreconciling from the stream\" and read the session's durable record instead. A reader that\nignores it will conclude the session was quieter than it was.\n\n\u26a0\ufe0f RESUME WORKS ON A LIVE SESSION, AND A LIVE STREAM NEVER CLOSES. Both halves matter,\nbecause the second one is how readers conclude the first one is broken. On resume you get\nthe buffered events immediately, then the connection STAYS OPEN and waits for whatever\nhappens next, emitting a `: keepalive` comment line roughly every 15s, until the session\nends or the stream reaches its own ceiling. It closes ONLY on `session_ended`, on\n`stream_timeout`, or when you disconnect.\n\nSO DO NOT READ TO EOF ON A LIVE SESSION. A client that drains the response to completion -\nthe natural shape, and what most HTTP helpers do by default - will block until its own\ntimeout and report NO FRAMES, even though the replayed events were delivered on the wire\nimmediately. Consume frames as they arrive and stop on your own condition. MEASURED\n2026-09-11 (#1879): a pilot integrator read this way, saw \"frames: none, read operation\ntimed out\" on a live session, and the same session replayed correctly once ENDED - because\nending it made the stream close and released her read. Driven over a real socket, a live\nresume returns its buffered frames straight away.\n\n\ud83d\udd11 `seq` IS A GLOBAL ORDINAL, NOT A PER-SESSION OFFSET. One counter is shared across every\nsession on the process, so a single session's ids are NON-CONTIGUOUS by design: 2, 3, then\n57 is an ordinary reading, not evidence of loss. Use the id only as an opaque resume\ncursor, and never infer a gap by arithmetic on the distance between two ids - the gap frame\nabove is the ONLY authority on missing history. Reasoning from id spacing produces a\nconfident wrong conclusion from entirely correct data.\n\nA QUEUED SESSION IS NOT SILENT (#4179). When your account is already running as many\nsessions as it may, a create answers `phase: queued` and this stream emits\n`{\"type\": \"queued\"}`; when the session is admitted it emits `{\"type\": \"starting\"}`, and\nthe ordinary `state`, `output`, `turn_ended` and `turn_content` events follow. Neither\nframe carries a position or a count. A subscriber that sees `queued` and nothing else is\nstill waiting, not disconnected.",
        "operationId": "stream_events_v1_sessions__session_id__events_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/event-stream": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/SessionEventOutput"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventTurnContent"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventTurnEnded"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventState"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventInputRefused"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventSessionEnded"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventSessionDoomed"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventDeclarationRefused"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventEvidenceRefused"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventGap"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventStreamTimeout"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventQueued"
                    },
                    {
                      "$ref": "#/components/schemas/SessionEventStarting"
                    }
                  ]
                }
              }
            },
            "description": "An SSE stream. Each frame is `id: <seq>` / `event: <type>` / `data: <JSON>`, where the JSON is one of the objects below and repeats its own `type`. \n\n**The vocabulary is CLOSED** \u2014 the objects below are everything the stream can emit, and anything else is dropped rather than delivered. So an unrecognised `type` is a defect worth reporting, not data to interpret.\n\n**Ending a turn vs ending the stream.** `turn_ended` finishes a TURN and invites another; `session_ended` and `session_doomed` end the STREAM and the connection closes. A turn loop terminates on `turn_ended`. `declaration_refused` and `evidence_refused` are informational and end nothing: the first means the agent's message was withheld, the second that delivered text could not be recorded as evidence, and in both the session continues.\n\n**`turn_content` ARRIVES BEFORE `turn_ended`.** Turn N's record is sent before that turn's `turn_ended`, so a loop that terminates on `turn_ended` already holds it \u2014 you do not have to read past the terminal frame, and you do not have to remember a caveat to avoid losing data. The closing order of a turn is `stats`, then `turn_content` (when there is one), then `turn_ended`. Process frames in arrival order and take the record when it appears.\n\n**`turn_content` may not come at all.** It is CONDITIONAL \u2014 a turn that stored no new messages produces none \u2014 and BEST-EFFORT: a server-side failure to build it is not signalled on this stream. Its absence is therefore not evidence that the turn was empty, and a client that REQUIRES it will break on a day nobody chose. Keep the reassembled `output` deltas as a fallback and record which of the two a transcript came from.\n\n**Two frames that are not endings and are mistaken for them.** `stream_timeout` means this connection reached its maximum duration and the session is unaffected \u2014 RECONNECT (it carries `seq: 0`, so resume from the last real event, never from it). `input_refused` means a turn you are waiting for will never arrive, while the session stays open \u2014 stop waiting.\n\nLines beginning `:` are keepalive comments; SSE parsers ignore them."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Stream session output (SSE)",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/evidence": {
      "post": {
        "operationId": "ingest_evidence_v1_sessions__session_id__evidence_post",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EvidenceIngestRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EvidenceIngestResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Ingest a retrieval for grounding \u2014 Cohort atomizes it and records it to the charter record",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/input": {
      "post": {
        "description": "Send a turn. 202 means ACCEPTED FOR DELIVERY. If the session's worker was reclaimed by the ~300s idle pause, this input RESPAWNS one with the conversation replayed and your message as its first turn \u2014 the reply arrives on the same event stream after a cold-start delay (tens of seconds) instead of the warm ~1s. An ENDED session never respawns: input to it is refused with 409 `session_ended`. A session still `queued` has not started: input to it is refused with 409 `session_queued`; send it once the session's `phase` is `starting` or `live`. On a chartered session the turn is also recorded as evidence BEFORE delivery, so anchored fields can only ever hold words you actually said.",
        "operationId": "submit_input_v1_sessions__session_id__input_post",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitInputRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InputAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Send a turn into a live session",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/patch": {
      "get": {
        "description": "#4426 what the agent changed in a workspace session: `git diff --binary` against the\nnormalized commit the session was given, the commands it ran, and whether the worker marked\nit final (sent at session end). Apply it to your own `source_commit` with `git apply`:\nthe normalized commit has the same tree, so the patch applies unchanged.\n\n404 for an unknown session, another workspace's, or one that has no workspace; 404 with\n`error: patch.none_yet` while the agent has not produced one.",
        "operationId": "get_session_patch_v1_sessions__session_id__patch_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionPatch"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "The agent's changes to its workspace, as a git patch",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/seal": {
      "post": {
        "description": "Close a chartered record on the PERSON's say-so, and hand back what was sealed.\n\nWHY THIS EXISTS. There are two seal paths: the doer's own (`as_doer`, #1419) and the\nsubstrate's. A charter that sets `doer_may_seal: false` says completion is a HUMAN act -\nand until now a tenant had no way to perform it, because the substrate path lives behind\nthe charter engine on the internal network. An application whose product is born by a person pressing a\nbutton could arm that button and never fire it.\n\nTHE TENANT NEVER HOLDS THE SUBSTRATE TOKEN. That token is the privileged-writer identity;\nhanding it to an application to press one button would grant it reserved writers and every\ndisposition across the whole surface. So the application asks with its ORDINARY tenant\ncredential, Cohort authorises the request the same way it authorises `/input`, and Cohort\nperforms the seal on its own authority. Privilege stays with whoever it belongs to.\n\n\ud83d\udd34 NO DISPOSITION PARAMETER, DELIBERATELY. A caller may say \"the person says this is\nfinished\" and nothing else. `partial` and `bypassed` are judgements ABOUT an unfinished\nrecord - the charter engine's own refusal puts it exactly right, that such a judgement \"belongs to\nwhoever owns the outcome\". Exposing them here would let a tenant grade its own work.\n\nA REFUSAL IS A NORMAL ANSWER, NOT AN OUTAGE. The charter engine gates a `complete` seal on the exit\nbeing satisfied and renders the unmet criteria when it is not; that verdict is forwarded\nVERBATIM. The caller is entitled to it - it is their own session - so a dead button can\nbecome \"here is what is still missing\" rather than silence.\n\nReturns the SEALED ARTIFACT rather than a bare receipt: the caller sealed it in order to\nuse it, and a second round trip to fetch the product invites reading a record whose seal\nit never confirmed.",
        "operationId": "session_seal_endpoint_v1_sessions__session_id__seal_post",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "The HUMAN's completion \u2014 seal a satisfied chartered session",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/submit": {
      "post": {
        "operationId": "submit_writes_v1_sessions__session_id__submit_post",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WritesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WritesResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Forward chartered-session setup writes to the charter record (shape/subject) \u2014 NOT the answer",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/turns": {
      "get": {
        "description": "THE DURABLE TRANSCRIPT (#4197). The same `messages` the `turn_content` stream frames\ncarried, read from the record store: one entry per stored turn, oldest first.\n\nUntil this route the conversation of an ended session survived only in the in-process\nevent ring (500 frames, gone on restart or eviction), because the stored turns existed for\nrespawn and were dropped at end. They now stay until `DELETE /sessions/{id}/content` or\nthe retention sweep removes them with the record, so a customer can read back what was\nsaid after the seal, not only what was recorded and graded.\n\n`complete` is false while the session is live and false when the stored indexes have a\ngap - a `turn_content` frame is best-effort and its storage rides the same path, so an\nabsent turn is reported, never papered over. An unknown or deleted session, or another\nworkspace's, is 404 here exactly as on every other read (no enumeration oracle).\n\nTO CHAIN SESSIONS, do not paste this into the next session's `messages` yourself: create\nthe next session with `inputs.sessions` naming this one (#4204). The platform reads these\nsame turns, hands them to the agent, and pins their digest on the new session so its seal\ncites exactly what was read.",
        "operationId": "get_session_turns_v1_sessions__session_id__turns_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionTurns"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "The stored conversation, read back after the fact (sealed sessions included)",
        "tags": [
          "sessions"
        ]
      }
    },
    "/v1/sessions/{session_id}/usage": {
      "get": {
        "description": "#1117: the LINE-ITEM bill for ONE session - per-turn token/compute rows, the grounding\naccumulator, the worker-lifetime row, and a rollup. The unit a customer reasons about\n(one patient conversation, itemized) and the number their optimization must match.\n\n\ud83d\udd11 THIS IS THE INSTRUMENT FOR METERING A SESSION IN FLIGHT, and the one to reach for when\nsettling what a piece of work cost. `rollup.cost_usd` is this session's running total and\n`rollup.price_estimated_rows` says whether that total has settled - bill or sweep only at 0.\n\n\u26a0\ufe0f IT IS CUMULATIVE. Settle by SETTING your recorded amount to `cost_usd`, never by adding a\ndelta: webhook deliveries retry, and a duplicate then writes the same number twice instead\nof charging somebody twice.\n\nNot to be confused with GET /v1/usage/windows, which answers how a KEY is spending across\neverything over canonical windows - the dashboard question, not the in-flight one. That\nsurface's all-time window is structurally estimated and can never fully settle, so a\nlifetime total is accumulated from these per-session rollups rather than read from there.",
        "operationId": "tenant_session_usage_v1_sessions__session_id__usage_get",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionUsage"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "What ONE session has cost so far \u2014 itemized rows plus a settled-or-not total"
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "tenant_usage_v1_usage_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageSummary"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Usage"
      }
    },
    "/v1/usage/by-key": {
      "get": {
        "description": "PER-KEY consumption over an explicit window - the read an INVOICE is cut from. (#1194)\n\nA platform customer issues one usage key per THEIR OWN customer and needs an invoice per key per\nperiod that reconciles against what we bill the account. So the response carries the arithmetic\nexplicitly - sum of the parts, the independently computed whole, and the difference - rather\nthan asserting they agree.\n\nAGGREGATED OVER THE WHOLE WINDOW, never a page. /v1/usage/recent is capped at 500 rows and\nis therefore not a billing read; this one cannot be silently truncated because it never\nreturns rows at all.\n\nKEYLESS SPEND IS AN EXPLICIT BUCKET. A human operating the dashboard holds no ck_ key, and\nthat money is real. Omitting it would make the parts fail to sum to the whole while every\nindividual line still looked correct.",
        "operationId": "tenant_usage_by_key_v1_usage_by_key_get",
        "parameters": [
          {
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "default": "",
              "title": "Since",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "until",
            "required": false,
            "schema": {
              "default": "",
              "title": "Until",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageByKey"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Usage By Key"
      }
    },
    "/v1/usage/economics": {
      "get": {
        "description": "#1117: per-session unit economics - avg/p50/p95 cost + tokens per session, turn-count\ndistribution, and the DRIVER split (input vs output share, holding dimensions) so a\ncustomer can see WHICH lever moves their bill before setting turn/token ceilings.",
        "operationId": "tenant_unit_economics_v1_usage_economics_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnitEconomics"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Unit Economics"
      }
    },
    "/v1/usage/recent": {
      "get": {
        "description": "Raw ledger rows. #1194 adds an optional key/window filter and truncation honesty.\n\nEvery new parameter is optional and defaults to the previous behaviour, so a caller that\npasses none gets exactly what it got before - the change is additive by construction, not\nby promise.",
        "operationId": "tenant_usage_recent_v1_usage_recent_get",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "key_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Key Id"
            }
          },
          {
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "default": "",
              "title": "Since",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "until",
            "required": false,
            "schema": {
              "default": "",
              "title": "Until",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageRecent"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Tenant Usage Recent"
      }
    },
    "/v1/usage/windows": {
      "get": {
        "description": "10m / 1h / 6h / 24h / all-time - the same billing block for each. (#1236)\n\nONE CALL BECAUSE THE ANSWERS HAVE TO AGREE. \"Which key is spending, on which model, and when\ndid it last do so\" is asked at several scales at once, and five round-trips against a live\nledger answer from five different ledgers: a turn landing between them lands in the\n10-minute window and not in the 24-hour window that contains it. The rows are snapshotted\nonce and sliced, so a narrow window is a SUBSET of a wide one by construction.\n\nThe window ids are the stable part of the contract; `label` is for humans. `until` is open\non every window on purpose - bounding it at now would drop the turn recorded in this very\nsecond, which is the one someone watching live spend is looking for.",
        "operationId": "tenant_usage_windows_v1_usage_windows_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageWindows"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Per-key and per-model spend over every canonical window, in one call"
      }
    },
    "/v1/workspace-bundles": {
      "post": {
        "description": "Create one with `git bundle create <file> <ref>` (or `--all`). Send its parts, complete it, then name it in POST /v1/sessions. Anything else the bundle carries (history, other branches, tags) never reaches the session.",
        "operationId": "create_bundle_v1_workspace_bundles_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceBundleCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBundle"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Declare a git bundle to upload in parts",
        "tags": [
          "workspace-bundles"
        ]
      }
    },
    "/v1/workspace-bundles/{upload_id}": {
      "get": {
        "operationId": "get_bundle_v1_workspace_bundles__upload_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBundle"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "A bundle upload's status",
        "tags": [
          "workspace-bundles"
        ]
      }
    },
    "/v1/workspace-bundles/{upload_id}/complete": {
      "post": {
        "description": "Checks every part is present, the byte total, and the sha256 when one was declared. The bundle is then `ready`. Whether it is a valid git bundle and holds the ref you name is checked when a session is created on it.",
        "operationId": "complete_v1_workspace_bundles__upload_id__complete_post",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBundle"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Finish a bundle upload",
        "tags": [
          "workspace-bundles"
        ]
      }
    },
    "/v1/workspace-bundles/{upload_id}/parts/{part_no}": {
      "put": {
        "description": "The raw bytes of part `part_no` (from 0). Sending the same part again with the same bytes is harmless; different bytes are refused (409 `upload.part_changed`).",
        "operationId": "put_part_v1_workspace_bundles__upload_id__parts__part_no__put",
        "parameters": [
          {
            "in": "path",
            "name": "upload_id",
            "required": true,
            "schema": {
              "title": "Upload Id",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "part_no",
            "required": true,
            "schema": {
              "title": "Part No",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceBundle"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Send one part of a bundle",
        "tags": [
          "workspace-bundles"
        ]
      }
    },
    "/v1/workspaces": {
      "get": {
        "description": "Every workspace YOUR account owns. Replaces the old claim-derived list.\n\nThis is what the SPA's switcher reads, so it is also what makes a newly created workspace\nappear without re-logging-in - the token no longer decides what exists.",
        "operationId": "list_workspaces_v1_workspaces_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceList"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List your account's workspaces"
      },
      "post": {
        "operationId": "create_workspace_v1_workspaces_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Create a workspace"
      }
    },
    "/v1/workspaces/{slug}": {
      "get": {
        "operationId": "get_workspace_v1_workspaces__slug__get",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Get one of your workspaces"
      },
      "patch": {
        "description": "The correction that did not exist. (#1777)\n\nBefore this route the only mutations on a workspace were `archive` and `reopen`, so a\ncustomer who mistyped a label could destroy the workspace but not fix it. Two seconds of\ntyping had a permanent consequence, because the slug is genuinely permanent and the label\nhad been given the same treatment by accident rather than by decision.\n\n\ud83d\udd34 THE SLUG STAYS PUT, and the refusal SAYS SO. Ignoring the field would be worse than\nrefusing it: the caller would get a 200 describing a rename that did not happen, and would\nfind out from a broken integration rather than from us. See WorkspaceUpdate.slug for the\nfour things bound to it.\n\nAUTHORIZED EXACTLY AS ARCHIVE IS - `require_account_or_management`, so a usage key cannot\nreach it and another account's slug is a 404 rather than a 403. Renaming is strictly less\ndangerous than archiving, and deliberately not given a weaker gate than the act it makes\nrecoverable: a credential that may not close a workspace has no business relabelling it\neither, and one gate is one thing to reason about.\n\nWORKS ON AN ARCHIVED WORKSPACE, on purpose. An archived row is still listed, still readable\nand still carries billing history a customer may need to identify years later - \"old prod,\nreplaced Sept 2026\" is exactly the label they want, and it is the one moment they cannot\nwrite it if this route refused archived rows.",
        "operationId": "update_workspace_v1_workspaces__slug__patch",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceEnvelope"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Rename a workspace (its label \u2014 the slug never moves)"
      }
    },
    "/v1/workspaces/{slug}/archive": {
      "post": {
        "description": "The closest thing to delete, and deliberately not DELETE.\n\n\ud83d\udd34 #1213 - ARCHIVE NOW STOPS SPEND, AND IT DID NOT. This route revoked keys and wrote\nstate='archived' and never reached the session manager, so live workers kept running on a\nvkey good for up to 24h, `oci_gc` refused to delete instances that were still ACTIVE, and\nusage kept accruing against the archived slug. Worse, nothing could then stop them: the\nusage key was revoked by this very call, and the owner's JWT could not resolve an archived\nslug - the stop button was behind the door the archive had just locked.\n\nTERMINATE, not drain (Lophie, 2026-08-19). A customer mid-turn loses that turn.\n\nTHE ORDER IS THE DESIGN, and every step refuses rather than continuing:\n  1. exists?                      -> 404\n  2. is it the account's DEFAULT workspace? -> 409 `default_workspace`, always (#2092)\n  3. is it the account's LAST active workspace? -> 409, before anything is touched\n  4. revoke keys                  -> 409 if any failed\n  5. END LIVE SESSIONS            -> 409 if any failed\n  6. only now write state=archived\n\nThe default workspace (slug `default-<account id>`, created with the account) can be\nrenamed and funded like any other but never archived, so an account always has somewhere\na session can live. Step 3 stays as the guard for accounts whose default predates this\nrule and is missing until their next sign-in seeds it.\n\nNothing is archived until the meter is provably stopped. A refusal at step 3 or 4 leaves the\nworkspace ACTIVE - which is the recoverable direction, because an active workspace still\nresolves for its owner, so the operator can retry and the stop button still works. The\nreverse (archived while a worker bills) is the state with no way out.",
        "operationId": "archive_workspace_v1_workspaces__slug__archive_post",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceArchived"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Archive a workspace and revoke its API keys"
      }
    },
    "/v1/workspaces/{slug}/credit": {
      "post": {
        "description": "MOVE money between the account and one workspace. (#1835)\n\n\ud83d\udd34 IT IS A TRANSFER, NOT A CAP, and that is the whole feature. An account holds a workspace\nper platform, and money appointed to one must be UNABLE to be spent by another - not\nmonitored, unable. A cap is a number somebody checks, and a check can be missed, raced or\nwired to nothing; money that has left the account balance is simply not there to spend by\nanyone else. Over-allocation is therefore refused by arithmetic rather than detected by a\njob: appointing $100 to two platforms out of $150 fails on the second, because after the\nfirst there is no longer $100 to move.\n\nPOSITIVE APPOINTS, NEGATIVE RETURNS. One operation, signed - how an owner divides their own\nmoney between their own platforms is their business to change as often as they like, and a\nseparate \"return\" endpoint would be a second set of edge cases for one idea.\n\n\ud83d\udd34 WHO MAY CALL THIS: A SIGNED-IN HUMAN, AND NOTHING ELSE. `require_tenant_account`\nrefuses EVERY api key, management ones included.\n\nThat is narrower than the neighbouring workspace routes on purpose. Whether a machine\ncredential may move money - a `ck_` funding a child key, or a `cm_` appointing to a\nworkspace - is an OPEN RULING, and there is no grant verb for moving money: all six\nexisting verbs are use or read. Mapping this route into the verb matrix would mean\ninventing that verb, which decides the ruling by implementation. A power nobody agreed to\nis much harder to take back than one that was never granted, so the console gets the owner\nand machine delegation waits for the decision.\n\n`idempotency_key` is REQUIRED and is the caller's, not ours. This is a money write, the one\nplace where \"applied twice\" is unrecoverable, and a key we generated would make every retry\na fresh transfer.",
        "operationId": "appoint_workspace_credit_v1_workspaces__slug__credit_post",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceCreditRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceCredit"
                }
              }
            },
            "description": "Successful Response"
          },
          "400": {
            "description": "The arithmetic or the destination refused. Over-allocation ('$X is more than workspace Y has available'), over-return ('more than this key holds'), a $0 movement, a workspace with no wallet yet (appoint to it first), an ARCHIVED workspace, or a REVOKED key. The message names both numbers, or which dead condition applied. \u26a0\ufe0f The archived/revoked refusals apply to POSITIVE amounts ONLY \u2014 a negative is always accepted, so credit already stranded in a dead place can still be taken back."
          },
          "403": {
            "description": "Not permitted to move this money. On the KEY route: the credential does not hold `credit.reserve` on the workspace IN THE PATH (a grant on a sibling workspace does not reach it), or it is an API key on the ACCOUNT-rung appointment, which is human-only \u2014 a workspace-scoped credential divides the money its workspace holds and can never increase it. No key kind carries `credit.reserve` by default; it is granted explicitly at mint, by an account holder."
          },
          "404": {
            "description": "No such workspace in this account, or no such key IN THIS WORKSPACE. Credit is always reserved to a key of the workspace whose money it is; naming a key of a sibling workspace reads as 'not here' and confirms nothing about whether that id exists."
          },
          "409": {
            "description": "The workspace resolves to no billing subject, so there is no account to move money from. The detail names the reason."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "503": {
            "description": "The account or quota store is unreachable. Nothing moved; retry is safe."
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Appoint credit from the account to this workspace, or return it"
      }
    },
    "/v1/workspaces/{slug}/keys": {
      "get": {
        "operationId": "list_keys_in_workspace_v1_workspaces__slug__keys_get",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "List a workspace's usage keys",
        "tags": [
          "keys"
        ]
      },
      "post": {
        "operationId": "mint_key_in_workspace_v1_workspaces__slug__keys_post",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MintKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MintKeyResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Mint a usage key in one of this account's workspaces",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/workspaces/{slug}/keys/{key_id}": {
      "delete": {
        "operationId": "revoke_key_in_workspace_v1_workspaces__slug__keys__key_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "key_id",
            "required": true,
            "schema": {
              "title": "Key Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyRevoked"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Revoke a usage key in one of this account's workspaces",
        "tags": [
          "keys"
        ]
      }
    },
    "/v1/workspaces/{slug}/keys/{key_id}/credit": {
      "post": {
        "description": "MOVE money from a workspace's wallet into one key's sub-wallet. (#1876)\n\nTHE SECOND LEG, and after #1876 the ONLY one that makes a session possible: a key's own\nwallet is the single pot the gate reads, so credit that has not been reserved to a key\ncannot be spent by anything. Owner ruling: \"to use credit must be with a key tied to a\nworkspace and that workspace must give the key money.\"\n\n\ud83d\udd34 THE SOURCE IS THIS WORKSPACE AND NEVER THE ACCOUNT. That is enforced in the store rather\nthan here, so the gRPC door and any future caller get the same rule - a workspace with no\nwallet refuses, naming the appointment that has to happen first. Reaching one rung up is\nthe seepage workspace wallets exist to prevent.\n\n\ud83d\udd34 WHO MAY CALL THIS: A SIGNED-IN HUMAN, OR A KEY HOLDING `credit.reserve` ON THIS\nWORKSPACE. The ruling that was open when this route shipped is now closed - owner,\n2026-09-10, verbatim: \"an api key with full permissions to the workspace can mint keys to\nthe workspace and move credit from the workspace wallet to those keys.\" So a customer's own\nconsole can fund the keys it mints, without a human in the loop for every hospital.\n\n\u26a0\ufe0f WHAT DID NOT CHANGE, AND IT IS THE HALF THAT MATTERS. `credit.reserve` reaches THIS rung\nonly. The appointment above it - account balance into a workspace wallet - is still\n`require_tenant_account`, which refuses every api key. A workspace-scoped credential can\ntherefore divide the money its workspace already holds, and can never increase it. That is\nwhat makes \"full permissions to the workspace\" a bounded sentence rather than an unbounded\none: the ceiling on everything this key can do is a number a human put there.\n\n\u26a0\ufe0f AND NO KEY KIND HOLDS THE VERB BY DEFAULT - it is absent from both USAGE_KEY_VERBS and\nMANAGEMENT_KEY_VERBS, so every key that exists today still gets a 403 here and nothing was\nretroactively widened. See the derivation note in grants.py: subtracting it there is what\nstops \"new verb\" from meaning \"new power for every key already issued\".\n\n\u26a0\ufe0f AND THE KEY MUST BELONG TO THIS WORKSPACE. Checked here, because the store deliberately\ndoes not read the api-key table - a cross-store read on a money path is what leaves\ntransfers half-checked. Without this check an owner could reserve their workspace's money to\na key in a DIFFERENT workspace of theirs, which is the same platform seepage in miniature.\n\n\ud83d\udd11 A KEY MAY FUND ITSELF, AND THAT IS DELIBERATE (#1934 GAP 5). Nothing here excludes\n`key_id` == the calling credential, so a console key holding `credit.reserve` can move its\nworkspace's wallet into its OWN sub-wallet and - if it also holds `sessions.drive` - spend\nit. Stated out loud because the natural reading of \"a funding power\" is that it is exercised\non OTHER keys, and a reader who assumes that would be wrong.\nWHY IT IS ALLOWED: the ceiling is unchanged either way. Everything such a key can reach was\nappointed to its workspace by a HUMAN, and forbidding self-funding would not lower that\nceiling by a cent - the key could simply mint a second key, fund THAT, and drive it. A rule\nthat is one API call from being irrelevant buys nothing and costs a legitimate use: a\nsingle-key integration that tops itself up.",
        "operationId": "reserve_key_credit_v1_workspaces__slug__keys__key_id__credit_post",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "key_id",
            "required": true,
            "schema": {
              "title": "Key Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeyCreditRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyCredit"
                }
              }
            },
            "description": "Successful Response"
          },
          "400": {
            "description": "The arithmetic or the destination refused. Over-allocation ('$X is more than workspace Y has available'), over-return ('more than this key holds'), a $0 movement, a workspace with no wallet yet (appoint to it first), an ARCHIVED workspace, or a REVOKED key. The message names both numbers, or which dead condition applied. \u26a0\ufe0f The archived/revoked refusals apply to POSITIVE amounts ONLY \u2014 a negative is always accepted, so credit already stranded in a dead place can still be taken back."
          },
          "403": {
            "description": "Not permitted to move this money. On the KEY route: the credential does not hold `credit.reserve` on the workspace IN THE PATH (a grant on a sibling workspace does not reach it), or it is an API key on the ACCOUNT-rung appointment, which is human-only \u2014 a workspace-scoped credential divides the money its workspace holds and can never increase it. No key kind carries `credit.reserve` by default; it is granted explicitly at mint, by an account holder."
          },
          "404": {
            "description": "No such workspace in this account, or no such key IN THIS WORKSPACE. Credit is always reserved to a key of the workspace whose money it is; naming a key of a sibling workspace reads as 'not here' and confirms nothing about whether that id exists."
          },
          "409": {
            "description": "The workspace resolves to no billing subject, so there is no account to move money from. The detail names the reason."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "503": {
            "description": "The account or quota store is unreachable. Nothing moved; retry is safe."
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Reserve credit from this workspace to one of its API keys (or take it back)"
      }
    },
    "/v1/workspaces/{slug}/reopen": {
      "post": {
        "description": "Archiving is reversible; the revoked keys are NOT reissued.\n\nSaid plainly in the response because \"reopen\" could reasonably be read as undoing\neverything, and a customer assuming their integration is live again would find out from a\n401 in production.",
        "operationId": "reopen_workspace_v1_workspaces__slug__reopen_post",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceReopened"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "security": [
          {
            "HTTPBearer": []
          }
        ],
        "summary": "Reactivate an archived workspace"
      }
    }
  },
  "servers": [
    {
      "description": "Cohort production",
      "url": "https://api.xcohort.xyz"
    }
  ]
}
