{"openapi":"3.1.0","info":{"title":"Pouchy World API","version":"1.34.0","description":"Build an interactive world — a short-drama story, a game’s NPCs, an interactive novel — without an official app. Two lanes: the OWNER lane (a signed-in project admin) authors and reads; the MACHINE lane (a project Secret Key plus a POUCHY-SOURCE-V1 provider signature over the exact raw body) drives sessions, turns and events from your backend. Secret keys and signing secrets never belong in a browser or a mobile app.\n\nIdempotency is explicit everywhere it matters: a turnId is the turn’s identity (re-send it to retry, mint a new one for a new beat), an eventId is the event’s, and a story or world publish is idempotent on its content bytes. Delivery is at-least-once with consumer-side de-duplication; GET /turns/{turnId} is the authoritative recovery path when a response is lost."},"servers":[{"url":"https://pouchy.ai/v1"}],"components":{"schemas":{"StoryPackageContent":{"type":"object","additionalProperties":true,"required":["name","summary","roles"],"description":"A Story Package v1 body. Array ORDER is narrative order and therefore content — reordering roles or scenes mints a different revision. Referential and uniqueness rules are enforced by the server and named in the field descriptions below.","properties":{"name":{"type":"string","maxLength":80,"description":"Required."},"summary":{"type":"string","maxLength":1000,"description":"Required."},"source":{"type":"object","additionalProperties":true,"required":["title","version"],"description":"A REFERENCE to the original screenplay — never its bytes. Both fields are required when `source` is present at all.","properties":{"title":{"type":"string","maxLength":200,"description":""},"version":{"type":"string","maxLength":60,"description":""}}},"canon":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":100,"description":"World-shared truth every role sees. Entries are trimmed; empty strings are refused."},"constraints":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":50,"description":"What the story must never do. Same entry rules as canon."},"roles":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["storyRoleId","worldRoleId"],"properties":{"storyRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"worldRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"The WORLD role this story role plays. Must exist in the world revision that pins this package — checked at PIN time, not here, because a package is project-scoped and may be pinned by many worlds. At most one story role per worldRoleId."},"description":{"type":"string","maxLength":500,"description":""},"secrets":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Role-PRIVATE canon. Never rendered to another role, in any projection."},"goals":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Drives coordinator role selection."},"effectsAllow":{"type":"array","maxItems":8,"items":{"type":"string","enum":["set_flag","increment_flag","reveal_fact","update_relationship","update_entity","advance_clock","request_scene_transition","complete_objective"]},"description":"What this role may PROPOSE. DEFAULT DENY: absent means the role proposes nothing and the effect tool is not offered to it at all. An unknown kind is an error, never a silent drop."}}},"maxItems":12,"description":"At least one role is required. storyRoleId is unique; worldRoleId is bound at most once."},"scenes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["sceneId","title"],"properties":{"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":"Required."},"description":{"type":"string","maxLength":500,"description":""},"location":{"type":"string","maxLength":120,"description":""}}},"maxItems":50,"description":"sceneId is unique within the package."},"nodes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["nodeId","sceneId","title","objective"],"properties":{"nodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must name a scene declared in `scenes`."},"title":{"type":"string","maxLength":120,"description":""},"objective":{"type":"string","maxLength":500,"description":""},"prerequisites":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"maxItems":10,"description":"Node ids that must be completed first. Each must be a declared node, and never this node itself."}}},"maxItems":100,"description":"A plot node is an objective inside a scene. nodeId is unique."},"establishedFacts":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["factId","text"],"properties":{"factId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"text":{"type":"string","maxLength":500,"description":""}}},"maxItems":200,"description":"factId is unique."},"branches":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["branchId","fromNodeId","condition","toNodeId"],"properties":{"branchId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"fromNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"toNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"condition":{"type":"string","maxLength":500,"description":"Descriptive text in v1 — the runtime does not evaluate it. It is what a turn offers as a next option."}}},"maxItems":100,"description":"branchId is unique."},"endings":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["endingId","title","condition"],"properties":{"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":""},"condition":{"type":"string","maxLength":500,"description":""}}},"maxItems":60,"description":"endingId is unique."},"stateSchema":{"type":"object","additionalProperties":true,"description":"The declared world-state flags. Absent means a package with no flags.","properties":{"flags":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["key","kind"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"kind":{"type":"string","enum":["boolean","number","string"]},"initial":{"description":"Optional starting value. Its JSON type must match `kind` exactly — a number initial on a string flag is refused."},"public":{"type":"boolean","enum":[true],"description":"Contract v4. `true` publishes this flag — its NAME and its VALUE — on the progress checkpoint’s `publicFlags` for any client holding the instance. Absent keeps it out of the checkpoint (a flag’s name can itself be a spoiler). `false` is refused: the only two states are published and not. Declaring any public flag is what makes a package contract v4; the server decides the version and refuses a client-supplied `contractVersion`."}}},"maxItems":50,"description":"key is unique."}}},"effectRules":{"type":"object","additionalProperties":true,"required":["conflict"],"description":"How the coordinator settles two roles proposing incompatible effects on the SAME target in one turn. Absent means `reject`. There is deliberately no \"the model decides\" option.","properties":{"conflict":{"type":"string","enum":["reject","first_wins"]}}},"initialRelations":{"type":"array","maxItems":50,"description":"Contract v2. The relations the story opens with, seeded into a NEW world instance only. A SET, not a list: [a,b] and [b,a] are one relation, a pair declared twice is REFUSED rather than merged (differing descriptors are a contradiction; identical ones are still two rows for one key), and the whole array is sorted before hashing — so authoring order cannot change the content hash. Every other array in this contract keeps declaration order. EVERY relation here is PUBLIC: relations live in the shared block of world state, so a relation that must stay hidden belongs in the authorised role’s `secrets` instead. Supplying this field is what makes a package contract v2; the server decides the version and refuses a client-supplied `contractVersion`.","items":{"type":"object","additionalProperties":true,"required":["between","descriptor"],"properties":{"between":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"description":"Two DISTINCT declared storyRoleIds. Normalized to ascending order."},"descriptor":{"type":"string","maxLength":120}}}},"series":{"type":"object","additionalProperties":true,"required":["seriesId","title","episodes"],"description":"Contract v3. Episodes, and the deterministic rules that decide when one is over. Supplying this field is what makes a package contract v3; the server decides the version and refuses a client-supplied `contractVersion`. Every id a rule names — node, fact, scene, flag, ending — must be DECLARED in the same package: a rule that can never hold would make the episode end only by fallback, silently. Ending rules are evaluated SERVER-SIDE over committed state after a beat commits; no model, client or prompt supplies an endingId.","properties":{"seriesId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"episodes":{"type":"array","maxItems":24,"description":"DECLARATION order is broadcast order. At least one; there is no empty-series form, because absent already means \"not a serial\".","items":{"type":"object","additionalProperties":true,"required":["episodeId","title","turnBudget","fallbackEndingId"],"properties":{"episodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"turnBudget":{"type":"integer","minimum":1,"maximum":200,"description":"Committed beats this episode may run before the fallback ending fires. Capped at the evidence window: an episode longer than one evidence read can never be turned into a script."},"fallbackEndingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"A DECLARED ending, used when no rule ever holds. Required — the alternative is an episode that cannot end."},"endingRules":{"type":"array","maxItems":20,"description":"Order-independent: priority decides (lower wins) and `ruleId` breaks a tie. Reordering this array changes nothing, unlike every other array in this contract.","items":{"type":"object","additionalProperties":true,"required":["ruleId","endingId","priority","all"],"properties":{"ruleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"priority":{"type":"integer"},"all":{"type":"array","minItems":1,"maxItems":10,"description":"A CONJUNCTION over a closed condition vocabulary — `node_completed`, `fact_revealed`, `flag_equals`, `scene_is`, `clock_at_least`. An OR is two rules. An empty list would be true of every state and is refused.","items":{"type":"object","additionalProperties":true}}}}}}}}}},"visibility":{"type":"object","additionalProperties":true,"required":["factDefault"],"description":"Role visibility over established facts. Canon is world-shared by definition and role `secrets` are private by definition; facts sit between. Absent means { factDefault: \"shared\" }.","properties":{"factDefault":{"type":"string","enum":["shared","role-scoped"]},"factAllow":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."}},"description":"worldRoleId → factId[]. Every key must be a declared worldRoleId and every value a declared factId; both are refused by name rather than ignored."}}}}}},"securitySchemes":{"OwnerToken":{"type":"http","scheme":"bearer","description":"A signed-in project admin’s Firebase ID token."},"SecretKey":{"type":"http","scheme":"bearer","description":"A project Secret Key (pchy_sk_…). Carries the test/live axis; must be paired with X-Pouchy-Source-Signature."},"AdminKey":{"type":"http","scheme":"bearer","description":"A project ADMIN key (pchy_admin_…), minted once from the dashboard. Long-lived and machine-held: this is the credential an unattended backend uses for the /admin mirror of the world reads, because an OwnerToken is a browser-minted Firebase ID token that expires within the hour. It proves the PROJECT — never the Provider — so it cannot drive a turn."},"SourceSignature":{"type":"apiKey","in":"header","name":"X-Pouchy-Source-Signature","description":"POUCHY-SOURCE-V1: t=<unix seconds>,kid=<keyId>,v1=<hex hmac>. The canonical string is five newline-joined lines — POUCHY-SOURCE-V1, t, the declared source, the ID SLOT, and the sha256 of the exact raw body (or \"-\" when empty). The id slot is per-door and there are three: turnId on /environments/{envId}/instances/{id}/turns, eventId on /events/ingest, and world.request_id (the value from the body you are signing) on /sessions — a session mint has no turn and no event, and signing an empty or invented id there is the first thing an integrator gets wrong. Sign at send time, every attempt."}}},"paths":{"/admin/environments/{envId}/overview":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."}],"get":{"summary":"Ready / not-ready and the next step for each blocker, on the admin key. The machine-lane twin of /projects/{projectId}/environments/{envId}/overview.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/cost":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"months","in":"query","required":false,"schema":{"type":"integer"},"description":"Window length; clamped server-side."}],"get":{"summary":"Per-experience cost on the admin key — the same numbers as the owner route. A backend that drives an experience is the party that wants its cost in its own dashboard, so this read belongs on a credential a server can hold.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/state":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"The world’s current state in the USER projection, mirroring the owner-plane route byte for byte — same projection, same shape. `flags` is EVERY flag, not only those an author marked `public: true` (that filter is the progress checkpoint’s); `completedNodes` is the plot progress the checkpoint reports only as a COUNT; `factsVisible` is the world-revealed set. Never the role-private layer.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"worldInstanceId":{"type":"string"},"environmentId":{"type":"string"},"environmentRevision":{"type":"integer"},"storyPackageRef":{"type":"string"},"state":{"type":"object","properties":{"stateRevision":{"type":"integer"},"clock":{"type":"integer"},"sceneId":{"type":"string"},"location":{"type":"string"},"flags":{"type":"object","additionalProperties":true},"completedNodes":{"type":"array","items":{"type":"string"}},"factsVisible":{"type":"array","items":{"type":"string"}},"relations":{"type":"object","additionalProperties":true},"entities":{"type":"object","additionalProperties":true},"lastEntries":{"type":"array","items":{"type":"object","properties":{"entryId":{"type":"string"},"kind":{"type":"string"},"at":{"type":"integer"}}}}}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/consistency":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"cursor","in":"query","required":false,"schema":{"type":"integer"},"description":"Resume an `incomplete` scan from its cursor."},{"name":"layers","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated. Omitted (the default) means deterministic only, with zero model calls and a body byte-identical to world API 1.19. `model_assisted` additionally requests the judgement layer, which also needs a server flag and a declaration on the published world revision — three gates, all required."}],"get":{"summary":"What this world’s own record proves about itself — the DETERMINISTIC layer. Every finding is a comparison between committed facts and the story package this instance pins; nothing is a judgement about writing, and nothing is repaired. Rate limited on two axes (project and IP, 30/min each) and never cached. Answers 200 with `status: \"unavailable\"` when the world cannot be read, and 429 with Retry-After when a ceiling is hit.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"status":{"type":"string","enum":["complete","incomplete"]},"contractVersion":{"type":"integer"},"findings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["revision_mismatch","ledger_drift","ledger_missing_entries","unrecorded_beat_claim","orphan_branch_reference","node_prereq_violation","effect_without_grant","flag_schema_violation","relation_change_without_cause","verbatim_secret_disclosure"],"description":"A CLOSED set. Members may be added in a later minor; a published member is never removed, renamed, or widened in meaning — a criterion found to be wrong is deprecated and replaced beside it. Treat an unknown code as informational rather than failing on it."},"layer":{"type":"string","enum":["deterministic"]},"status":{"type":"string","enum":["proven"]},"severity":{"type":"string","enum":["blocking","warning","info"]},"evidence":{"type":"array","description":"Never empty. Identifiers and already-committed facts; `quote` appears only on verbatim matches and carries the matched fragment, capped — never a whole line.","items":{"type":"object","properties":{"turnId":{"type":"string"},"seq":{"type":"integer"},"roleId":{"type":"string"},"sceneId":{"type":"string"},"nodeId":{"type":"string"},"branchId":{"type":"string"},"flagKey":{"type":"string"},"effectKind":{"type":"string"},"quote":{"type":"string"},"ledgerRejectionCode":{"type":"string"}}}}},"required":["code","layer","status","severity","evidence"]}},"coverage":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"skipped":{"type":"integer","description":"Entries this check could not examine."},"reason":{"type":"string","enum":["evidence_field_absent","scan_incomplete"]}},"required":["code","skipped","reason"]},"description":"What could NOT be examined, and why. A non-empty coverage list on an otherwise clean report means the report is clean about less than the whole world — absence of a finding is not proof of absence."},"entriesScanned":{"type":"integer"},"findingsTruncated":{"type":"boolean"},"cursor":{"type":"integer","description":"Present only when `status` is `incomplete`. Pass it back to continue."},"modelAssistedFindings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["suspected_canon_conflict","suspected_revival","suspected_persona_conflict","suspected_secret_leak","suspected_cross_ip_contamination"],"description":"A SEPARATE closed set from the deterministic one, which stays frozen. These are judgements, not proofs."},"layer":{"type":"string","enum":["model_assisted"]},"status":{"type":"string","enum":["potential","confirmed","dismissed"],"description":"`potential` until a person decides. Confirmation is an editorial judgement recorded against the finding — it changes nothing about the story, the canon, the ledger or the world state."},"severity":{"type":"string","enum":["warning","info"],"description":"Never `blocking`, at the type level as well as here. A suspicion must not be able to fail somebody’s build. `suspected_cross_ip_contamination` in particular is an editorial opinion even once confirmed, and is not evidence of isolation."},"findingId":{"type":"string","description":"Stable across scans for the same judgement: derived from the code and the server-normalised evidence refs, and from nothing else. A rephrased model note does not mint a new finding, which is what lets a dismissal stick."},"evidence":{"type":"array","items":{"type":"object","properties":{"turnId":{"type":"string"},"roleId":{"type":"string"}}},"description":"Assembled server-side from committed rows. The model selects which rows by index; it never writes evidence."},"suggestion":{"type":"object","properties":{"kind":{"type":"string","enum":["author_note"]},"text":{"type":"string"}}},"reviewedBy":{"type":"string"},"reviewedAt":{"type":"string"}},"required":["code","layer","status","severity","findingId","evidence"]},"description":"Present ONLY when the request asked for `model_assisted` in `layers`. A default request carries neither this key nor `modelAssistedNote`, byte-identically to world API 1.19. Suspicions never appear in `findings` — a consumer testing `severity === \"blocking\"` there must not begin failing on an opinion."},"modelAssistedNote":{"type":"string","enum":["disabled_server","not_declared","rate_limited","unavailable","incomplete"],"description":"Why the model layer produced less than a full pass, when it was asked for. `disabled_server` and `not_declared` mean a gate was shut; `incomplete` means the per-scan budget stopped it early and the suspicions listed are not the whole picture."}},"required":["status","contractVersion","findings","coverage","entriesScanned","findingsTruncated"]},{"type":"object","properties":{"status":{"type":"string","enum":["rate_limited"]},"retryAfterSec":{"type":"integer"}},"required":["status","retryAfterSec"]},{"type":"object","properties":{"status":{"type":"string","enum":["unavailable"]},"reason":{"type":"string","enum":["world_unavailable"]}},"required":["status","reason"]}]}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}/approved-export/{exportId}":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The evidence draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."},{"name":"exportId","in":"path","required":true,"schema":{"type":"string"},"description":"The approved export id — a content digest."}],"get":{"summary":"Read ONE approved export, SCRIPT INCLUDED. The collection GET beside this lists exports without their bodies; this is the only read that returns one. Mirrored on the admin key at the matching `/v1/admin/...` path, so an unattended backend can fetch an approved script without a browser anywhere in its deployment — every field it needs is in the `world.script_approved` webhook (`exportId`, and `environmentId` / `worldInstanceId` / `evidenceDraftId` / `editorialDraftId` inside `lineage`). It READS only: minting an export is a reviewer signing off on a specific text and stays on the owner plane. The body cannot change — `exportId` is its content digest. Narrow `content.contractVersion` before reading version-specific fields. Any miss — wrong project, environment, instance, draft, editorial or id — answers the SAME 404, so refusals cannot be used to map which objects exist. Served `Cache-Control: no-store`.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/progress":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"Where the story is, and whether it can go on — a DERIVED checkpoint over facts that already committed. Nothing is stored, no model is asked, and the story package is the one this instance pinned, at its exact revision and content hash. Answers 200 even with no checkpoint: \"this world cannot be resumed\" is an answer about the story, not an error about the request.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"status":{"type":"string","enum":["ready"]},"resumable":{"type":"boolean"},"checkpoint":{"type":"object","properties":{"contractVersion":{"type":"integer","description":"Checkpoint contract version. 1 today."},"environmentRevision":{"type":"integer","description":"The revision this INSTANCE is pinned to for life — never the environment head."},"storyPackageRef":{"type":"object","properties":{"packageId":{"type":"string"},"revision":{"type":"integer"},"contentHash":{"type":"string"}},"required":["packageId","revision","contentHash"]},"stateRevision":{"type":"integer","description":"The CAS counter the turns door expects."},"started":{"type":"boolean","description":"At least one beat has committed."},"currentScene":{"type":["object","null"],"properties":{"sceneId":{"type":"string"},"title":{"type":"string"}},"description":"Null when the world has not opened a scene. A sceneId the pinned story does not declare is NOT reported as null — the whole read answers `story_reference_invalid` instead."},"completedNodeCount":{"type":"integer"},"declaredNodeCount":{"type":"integer","description":"How many nodes the AUTHOR declared. Not a denominator: a branching story never visits all of them, so a ratio of these two numbers means nothing."},"publicRelations":{"type":"array","items":{"type":"object","properties":{"between":{"type":"array","items":{"type":"string"}},"descriptor":{"type":"string"}}},"description":"World-public relations, sorted. Role-private material never appears here."},"publicRelationsTruncated":{"type":"boolean"},"publicFlags":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"description":"boolean | number | string, or null when the flag was never set and declares no initial"}}},"description":"Contract v4. The flags the pinned story declared `public: true`, sorted by key, with the value the state THIS checkpoint reports (the declared initial when never set; null when there is neither). ALWAYS present — an empty array for a story that publishes nothing — so absent never reads as unknown. A flag the story did not mark public never appears here."},"publicFlagsTruncated":{"type":"boolean"},"recentProgressRecords":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"integer"},"kind":{"type":"string","enum":["turn","event","system"]},"at":{"type":"integer"}}},"description":"The newest committed beats, newest first — that one happened, at a revision, at a time. Not a story summary: no patches, no lines, no actors."},"recentProgressTruncated":{"type":"boolean"},"nextOptions":{"type":"array","items":{"type":"object","properties":{"branchId":{"type":"string"},"condition":{"type":"string"}}},"description":"Branches reachable from the completed nodes, derived from the pinned story. Never model-generated, and capped server-side."},"rebuildable":{"type":"boolean","description":"Always true — nothing here is stored; it is a function of committed facts."}},"required":["contractVersion","environmentRevision","storyPackageRef","stateRevision","started","completedNodeCount","declaredNodeCount","publicFlags","publicFlagsTruncated","nextOptions"]}},"required":["status","resumable","checkpoint"]},{"type":"object","properties":{"status":{"type":"string","enum":["unavailable"]},"resumable":{"type":"boolean","enum":[false]},"reason":{"type":"string","enum":["world_unavailable","story_reference_invalid"],"description":"`world_unavailable` covers every \"no answer\" cause at once — no such instance, no access, a revoked or deleted package, a contract this build cannot read. They are one word on purpose: naming them apart would let a caller map which internal objects exist by reading the refusals."}},"required":["status","resumable","reason"]}]}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/turns":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"How many beats to return. Capped server-side."},{"name":"sinceSeq","in":"query","required":false,"schema":{"type":"integer"},"description":"Walk FORWARD from a revision already processed: beats with seq > sinceSeq, OLDEST first, plus `nextSinceSeq` to continue. The resume cursor."}],"get":{"summary":"The committed timeline, on the admin key. READ only — driving a turn stays on the machine lane at /projects/{projectId}/…/turns, which requires a Secret Key AND a provider signature. An admin key proves the project, not the Provider.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/turns/{turnId}":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"turnId","in":"path","required":true,"schema":{"type":"string"},"description":"The turn id."}],"get":{"summary":"Read back ONE committed turn — the recovery path for a dropped response, now reachable by the process that dropped it.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/metrics":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"Delivery health and turn quality, kept apart, on the admin key.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/admin/environments/{envId}/instances/{worldInstanceId}/deliveries":{"parameters":[{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"status","in":"query","required":false,"schema":{"type":"string"},"description":"pending | delivering | delivered | retry_wait | dead"},{"name":"sessionId","in":"query","required":false,"schema":{"type":"string"},"description":"Narrow to one delivery stream."},{"name":"turnId","in":"query","required":false,"schema":{"type":"string"},"description":"Narrow to one beat’s lines."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Continue a page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"Rows per page. Capped server-side."}],"get":{"summary":"The delivery queue VIEW, on the admin key. Statuses, timings, attempts and error classes — never message bodies. The queue ACTIONS (requeue, rehydrate, resolve-gap, drain) are deliberately not mirrored: each decides what happens to a reader who is missing a beat.","security":[{"AdminKey":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/sessions":{"post":{"summary":"Mint a session. With a `world` block ({ environment, role, request_id, world_instance? }) this mints a WORLD session bound to one instance and one role — the token your own frontend then uses with the companion SDK. request_id makes the mint idempotent (same id + same body = same instance, no second metering).","security":[{"SecretKey":[],"SourceSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"external_user_id":{"type":"string"},"world":{"type":"object","properties":{"environment":{"type":"string"},"role":{"type":"string"},"request_id":{"type":"string"},"world_instance":{"type":"string"}},"required":["environment","role","request_id"]}},"required":["external_user_id"]}}}},"responses":{"200":{"description":"Session minted","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/events":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."}],"post":{"summary":"Deliver a trusted event. With a `world: { environment, instance }` block it routes into that world: on a coordinated world it becomes ONE coordinator turn (turnId derived from eventId, so a re-delivery re-runs nothing); on an actor world it wakes each subscribed role. Roles are resolved server-side from the pinned definition — a payload can never grant one.","security":[{"SecretKey":[],"SourceSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"eventId":{"type":"string"},"schemaVersion":{"type":"integer"},"data":{"type":"object","additionalProperties":true},"occurredAt":{"type":"integer"},"world":{"type":"object","properties":{"environment":{"type":"string"},"instance":{"type":"string"}},"required":["environment","instance"]}},"required":["name","eventId","data"]}}}},"responses":{"202":{"description":"Accepted (or a duplicate of an accepted event)","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/story-packages":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."}],"get":{"summary":"List story package heads — metadata only, never content bodies.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"packages":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Import a story package as revision 1: roles bound to world roles, scenes, plot nodes, established facts, constraints, branches, endings, a typed flag schema, per-role visibility and per-role effect grants. The original screenplay stays a REFERENCE ({title, version}), never stored bytes. Optionally IDEMPOTENT: supply `clientRequestId` and the package id is derived from it, so a creation whose response was lost can be retried with the same key AND the same body to get the ORIGINAL package back (`created: false`) instead of a second one. The same key with DIFFERENT content is refused 409 `idempotency_key_reused` — never reported as a success for content that was never written.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["name","summary","roles"],"description":"A Story Package v1 body. Array ORDER is narrative order and therefore content — reordering roles or scenes mints a different revision. Referential and uniqueness rules are enforced by the server and named in the field descriptions below.","properties":{"name":{"type":"string","maxLength":80,"description":"Required."},"summary":{"type":"string","maxLength":1000,"description":"Required."},"source":{"type":"object","additionalProperties":true,"required":["title","version"],"description":"A REFERENCE to the original screenplay — never its bytes. Both fields are required when `source` is present at all.","properties":{"title":{"type":"string","maxLength":200,"description":""},"version":{"type":"string","maxLength":60,"description":""}}},"canon":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":100,"description":"World-shared truth every role sees. Entries are trimmed; empty strings are refused."},"constraints":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":50,"description":"What the story must never do. Same entry rules as canon."},"roles":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["storyRoleId","worldRoleId"],"properties":{"storyRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"worldRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"The WORLD role this story role plays. Must exist in the world revision that pins this package — checked at PIN time, not here, because a package is project-scoped and may be pinned by many worlds. At most one story role per worldRoleId."},"description":{"type":"string","maxLength":500,"description":""},"secrets":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Role-PRIVATE canon. Never rendered to another role, in any projection."},"goals":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Drives coordinator role selection."},"effectsAllow":{"type":"array","maxItems":8,"items":{"type":"string","enum":["set_flag","increment_flag","reveal_fact","update_relationship","update_entity","advance_clock","request_scene_transition","complete_objective"]},"description":"What this role may PROPOSE. DEFAULT DENY: absent means the role proposes nothing and the effect tool is not offered to it at all. An unknown kind is an error, never a silent drop."}}},"maxItems":12,"description":"At least one role is required. storyRoleId is unique; worldRoleId is bound at most once."},"scenes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["sceneId","title"],"properties":{"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":"Required."},"description":{"type":"string","maxLength":500,"description":""},"location":{"type":"string","maxLength":120,"description":""}}},"maxItems":50,"description":"sceneId is unique within the package."},"nodes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["nodeId","sceneId","title","objective"],"properties":{"nodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must name a scene declared in `scenes`."},"title":{"type":"string","maxLength":120,"description":""},"objective":{"type":"string","maxLength":500,"description":""},"prerequisites":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"maxItems":10,"description":"Node ids that must be completed first. Each must be a declared node, and never this node itself."}}},"maxItems":100,"description":"A plot node is an objective inside a scene. nodeId is unique."},"establishedFacts":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["factId","text"],"properties":{"factId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"text":{"type":"string","maxLength":500,"description":""}}},"maxItems":200,"description":"factId is unique."},"branches":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["branchId","fromNodeId","condition","toNodeId"],"properties":{"branchId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"fromNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"toNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"condition":{"type":"string","maxLength":500,"description":"Descriptive text in v1 — the runtime does not evaluate it. It is what a turn offers as a next option."}}},"maxItems":100,"description":"branchId is unique."},"endings":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["endingId","title","condition"],"properties":{"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":""},"condition":{"type":"string","maxLength":500,"description":""}}},"maxItems":60,"description":"endingId is unique."},"stateSchema":{"type":"object","additionalProperties":true,"description":"The declared world-state flags. Absent means a package with no flags.","properties":{"flags":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["key","kind"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"kind":{"type":"string","enum":["boolean","number","string"]},"initial":{"description":"Optional starting value. Its JSON type must match `kind` exactly — a number initial on a string flag is refused."},"public":{"type":"boolean","enum":[true],"description":"Contract v4. `true` publishes this flag — its NAME and its VALUE — on the progress checkpoint’s `publicFlags` for any client holding the instance. Absent keeps it out of the checkpoint (a flag’s name can itself be a spoiler). `false` is refused: the only two states are published and not. Declaring any public flag is what makes a package contract v4; the server decides the version and refuses a client-supplied `contractVersion`."}}},"maxItems":50,"description":"key is unique."}}},"effectRules":{"type":"object","additionalProperties":true,"required":["conflict"],"description":"How the coordinator settles two roles proposing incompatible effects on the SAME target in one turn. Absent means `reject`. There is deliberately no \"the model decides\" option.","properties":{"conflict":{"type":"string","enum":["reject","first_wins"]}}},"initialRelations":{"type":"array","maxItems":50,"description":"Contract v2. The relations the story opens with, seeded into a NEW world instance only. A SET, not a list: [a,b] and [b,a] are one relation, a pair declared twice is REFUSED rather than merged (differing descriptors are a contradiction; identical ones are still two rows for one key), and the whole array is sorted before hashing — so authoring order cannot change the content hash. Every other array in this contract keeps declaration order. EVERY relation here is PUBLIC: relations live in the shared block of world state, so a relation that must stay hidden belongs in the authorised role’s `secrets` instead. Supplying this field is what makes a package contract v2; the server decides the version and refuses a client-supplied `contractVersion`.","items":{"type":"object","additionalProperties":true,"required":["between","descriptor"],"properties":{"between":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"description":"Two DISTINCT declared storyRoleIds. Normalized to ascending order."},"descriptor":{"type":"string","maxLength":120}}}},"series":{"type":"object","additionalProperties":true,"required":["seriesId","title","episodes"],"description":"Contract v3. Episodes, and the deterministic rules that decide when one is over. Supplying this field is what makes a package contract v3; the server decides the version and refuses a client-supplied `contractVersion`. Every id a rule names — node, fact, scene, flag, ending — must be DECLARED in the same package: a rule that can never hold would make the episode end only by fallback, silently. Ending rules are evaluated SERVER-SIDE over committed state after a beat commits; no model, client or prompt supplies an endingId.","properties":{"seriesId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"episodes":{"type":"array","maxItems":24,"description":"DECLARATION order is broadcast order. At least one; there is no empty-series form, because absent already means \"not a serial\".","items":{"type":"object","additionalProperties":true,"required":["episodeId","title","turnBudget","fallbackEndingId"],"properties":{"episodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"turnBudget":{"type":"integer","minimum":1,"maximum":200,"description":"Committed beats this episode may run before the fallback ending fires. Capped at the evidence window: an episode longer than one evidence read can never be turned into a script."},"fallbackEndingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"A DECLARED ending, used when no rule ever holds. Required — the alternative is an episode that cannot end."},"endingRules":{"type":"array","maxItems":20,"description":"Order-independent: priority decides (lower wins) and `ruleId` breaks a tie. Reordering this array changes nothing, unlike every other array in this contract.","items":{"type":"object","additionalProperties":true,"required":["ruleId","endingId","priority","all"],"properties":{"ruleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"priority":{"type":"integer"},"all":{"type":"array","minItems":1,"maxItems":10,"description":"A CONJUNCTION over a closed condition vocabulary — `node_completed`, `fact_revealed`, `flag_equals`, `scene_is`, `clock_at_least`. An OR is two rules. An empty list would be true of every state and is refused.","items":{"type":"object","additionalProperties":true}}}}}}}}}},"visibility":{"type":"object","additionalProperties":true,"required":["factDefault"],"description":"Role visibility over established facts. Canon is world-shared by definition and role `secrets` are private by definition; facts sit between. Absent means { factDefault: \"shared\" }.","properties":{"factDefault":{"type":"string","enum":["shared","role-scoped"]},"factAllow":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."}},"description":"worldRoleId → factId[]. Every key must be a declared worldRoleId and every value a declared factId; both are refused by name rather than ignored."}}},"clientRequestId":{"type":"string","pattern":"^[A-Za-z0-9_-]{16,128}$","description":"Optional idempotency token for CREATION. Transport, not story content: it is lifted out of the body before validation and is never stored, logged or echoed. Omit it and creation behaves exactly as it always has (a random package id)."}}}}}},"responses":{"201":{"description":"Created as revision 1","content":{"application/json":{"schema":{"type":"object","properties":{"packageId":{"type":"string"},"revision":{"type":"integer"},"contentHash":{"type":"string"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/story-packages/{packageId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"},"description":"The story package id — sp_…."}],"get":{"summary":"Head plus the current story content.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"head":{"type":"object","additionalProperties":true},"content":{"type":"object","additionalProperties":true,"required":["name","summary","roles"],"description":"A Story Package v1 body. Array ORDER is narrative order and therefore content — reordering roles or scenes mints a different revision. Referential and uniqueness rules are enforced by the server and named in the field descriptions below.","properties":{"name":{"type":"string","maxLength":80,"description":"Required."},"summary":{"type":"string","maxLength":1000,"description":"Required."},"source":{"type":"object","additionalProperties":true,"required":["title","version"],"description":"A REFERENCE to the original screenplay — never its bytes. Both fields are required when `source` is present at all.","properties":{"title":{"type":"string","maxLength":200,"description":""},"version":{"type":"string","maxLength":60,"description":""}}},"canon":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":100,"description":"World-shared truth every role sees. Entries are trimmed; empty strings are refused."},"constraints":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":50,"description":"What the story must never do. Same entry rules as canon."},"roles":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["storyRoleId","worldRoleId"],"properties":{"storyRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"worldRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"The WORLD role this story role plays. Must exist in the world revision that pins this package — checked at PIN time, not here, because a package is project-scoped and may be pinned by many worlds. At most one story role per worldRoleId."},"description":{"type":"string","maxLength":500,"description":""},"secrets":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Role-PRIVATE canon. Never rendered to another role, in any projection."},"goals":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Drives coordinator role selection."},"effectsAllow":{"type":"array","maxItems":8,"items":{"type":"string","enum":["set_flag","increment_flag","reveal_fact","update_relationship","update_entity","advance_clock","request_scene_transition","complete_objective"]},"description":"What this role may PROPOSE. DEFAULT DENY: absent means the role proposes nothing and the effect tool is not offered to it at all. An unknown kind is an error, never a silent drop."}}},"maxItems":12,"description":"At least one role is required. storyRoleId is unique; worldRoleId is bound at most once."},"scenes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["sceneId","title"],"properties":{"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":"Required."},"description":{"type":"string","maxLength":500,"description":""},"location":{"type":"string","maxLength":120,"description":""}}},"maxItems":50,"description":"sceneId is unique within the package."},"nodes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["nodeId","sceneId","title","objective"],"properties":{"nodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must name a scene declared in `scenes`."},"title":{"type":"string","maxLength":120,"description":""},"objective":{"type":"string","maxLength":500,"description":""},"prerequisites":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"maxItems":10,"description":"Node ids that must be completed first. Each must be a declared node, and never this node itself."}}},"maxItems":100,"description":"A plot node is an objective inside a scene. nodeId is unique."},"establishedFacts":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["factId","text"],"properties":{"factId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"text":{"type":"string","maxLength":500,"description":""}}},"maxItems":200,"description":"factId is unique."},"branches":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["branchId","fromNodeId","condition","toNodeId"],"properties":{"branchId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"fromNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"toNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"condition":{"type":"string","maxLength":500,"description":"Descriptive text in v1 — the runtime does not evaluate it. It is what a turn offers as a next option."}}},"maxItems":100,"description":"branchId is unique."},"endings":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["endingId","title","condition"],"properties":{"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":""},"condition":{"type":"string","maxLength":500,"description":""}}},"maxItems":60,"description":"endingId is unique."},"stateSchema":{"type":"object","additionalProperties":true,"description":"The declared world-state flags. Absent means a package with no flags.","properties":{"flags":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["key","kind"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"kind":{"type":"string","enum":["boolean","number","string"]},"initial":{"description":"Optional starting value. Its JSON type must match `kind` exactly — a number initial on a string flag is refused."},"public":{"type":"boolean","enum":[true],"description":"Contract v4. `true` publishes this flag — its NAME and its VALUE — on the progress checkpoint’s `publicFlags` for any client holding the instance. Absent keeps it out of the checkpoint (a flag’s name can itself be a spoiler). `false` is refused: the only two states are published and not. Declaring any public flag is what makes a package contract v4; the server decides the version and refuses a client-supplied `contractVersion`."}}},"maxItems":50,"description":"key is unique."}}},"effectRules":{"type":"object","additionalProperties":true,"required":["conflict"],"description":"How the coordinator settles two roles proposing incompatible effects on the SAME target in one turn. Absent means `reject`. There is deliberately no \"the model decides\" option.","properties":{"conflict":{"type":"string","enum":["reject","first_wins"]}}},"initialRelations":{"type":"array","maxItems":50,"description":"Contract v2. The relations the story opens with, seeded into a NEW world instance only. A SET, not a list: [a,b] and [b,a] are one relation, a pair declared twice is REFUSED rather than merged (differing descriptors are a contradiction; identical ones are still two rows for one key), and the whole array is sorted before hashing — so authoring order cannot change the content hash. Every other array in this contract keeps declaration order. EVERY relation here is PUBLIC: relations live in the shared block of world state, so a relation that must stay hidden belongs in the authorised role’s `secrets` instead. Supplying this field is what makes a package contract v2; the server decides the version and refuses a client-supplied `contractVersion`.","items":{"type":"object","additionalProperties":true,"required":["between","descriptor"],"properties":{"between":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"description":"Two DISTINCT declared storyRoleIds. Normalized to ascending order."},"descriptor":{"type":"string","maxLength":120}}}},"series":{"type":"object","additionalProperties":true,"required":["seriesId","title","episodes"],"description":"Contract v3. Episodes, and the deterministic rules that decide when one is over. Supplying this field is what makes a package contract v3; the server decides the version and refuses a client-supplied `contractVersion`. Every id a rule names — node, fact, scene, flag, ending — must be DECLARED in the same package: a rule that can never hold would make the episode end only by fallback, silently. Ending rules are evaluated SERVER-SIDE over committed state after a beat commits; no model, client or prompt supplies an endingId.","properties":{"seriesId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"episodes":{"type":"array","maxItems":24,"description":"DECLARATION order is broadcast order. At least one; there is no empty-series form, because absent already means \"not a serial\".","items":{"type":"object","additionalProperties":true,"required":["episodeId","title","turnBudget","fallbackEndingId"],"properties":{"episodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"turnBudget":{"type":"integer","minimum":1,"maximum":200,"description":"Committed beats this episode may run before the fallback ending fires. Capped at the evidence window: an episode longer than one evidence read can never be turned into a script."},"fallbackEndingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"A DECLARED ending, used when no rule ever holds. Required — the alternative is an episode that cannot end."},"endingRules":{"type":"array","maxItems":20,"description":"Order-independent: priority decides (lower wins) and `ruleId` breaks a tie. Reordering this array changes nothing, unlike every other array in this contract.","items":{"type":"object","additionalProperties":true,"required":["ruleId","endingId","priority","all"],"properties":{"ruleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"priority":{"type":"integer"},"all":{"type":"array","minItems":1,"maxItems":10,"description":"A CONJUNCTION over a closed condition vocabulary — `node_completed`, `fact_revealed`, `flag_equals`, `scene_is`, `clock_at_least`. An OR is two rules. An empty list would be true of every state and is refused.","items":{"type":"object","additionalProperties":true}}}}}}}}}},"visibility":{"type":"object","additionalProperties":true,"required":["factDefault"],"description":"Role visibility over established facts. Canon is world-shared by definition and role `secrets` are private by definition; facts sit between. Absent means { factDefault: \"shared\" }.","properties":{"factDefault":{"type":"string","enum":["shared","role-scoped"]},"factAllow":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."}},"description":"worldRoleId → factId[]. Every key must be a declared worldRoleId and every value a declared factId; both are refused by name rather than ignored."}}}}},"contentHash":{"type":"string"}},"required":["head","content","contentHash"]}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"summary":"Publish the next IMMUTABLE story revision. Idempotent on content: unchanged bytes return the existing revision (created: false); different bytes mint the next one and never rewrite an old one. Re-publishing an old revision’s bytes IS the rollback.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"required":["name","summary","roles"],"description":"A Story Package v1 body. Array ORDER is narrative order and therefore content — reordering roles or scenes mints a different revision. Referential and uniqueness rules are enforced by the server and named in the field descriptions below.","properties":{"name":{"type":"string","maxLength":80,"description":"Required."},"summary":{"type":"string","maxLength":1000,"description":"Required."},"source":{"type":"object","additionalProperties":true,"required":["title","version"],"description":"A REFERENCE to the original screenplay — never its bytes. Both fields are required when `source` is present at all.","properties":{"title":{"type":"string","maxLength":200,"description":""},"version":{"type":"string","maxLength":60,"description":""}}},"canon":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":100,"description":"World-shared truth every role sees. Entries are trimmed; empty strings are refused."},"constraints":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":50,"description":"What the story must never do. Same entry rules as canon."},"roles":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["storyRoleId","worldRoleId"],"properties":{"storyRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"worldRoleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"The WORLD role this story role plays. Must exist in the world revision that pins this package — checked at PIN time, not here, because a package is project-scoped and may be pinned by many worlds. At most one story role per worldRoleId."},"description":{"type":"string","maxLength":500,"description":""},"secrets":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Role-PRIVATE canon. Never rendered to another role, in any projection."},"goals":{"type":"array","items":{"type":"string","maxLength":500,"description":""},"maxItems":20,"description":"Drives coordinator role selection."},"effectsAllow":{"type":"array","maxItems":8,"items":{"type":"string","enum":["set_flag","increment_flag","reveal_fact","update_relationship","update_entity","advance_clock","request_scene_transition","complete_objective"]},"description":"What this role may PROPOSE. DEFAULT DENY: absent means the role proposes nothing and the effect tool is not offered to it at all. An unknown kind is an error, never a silent drop."}}},"maxItems":12,"description":"At least one role is required. storyRoleId is unique; worldRoleId is bound at most once."},"scenes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["sceneId","title"],"properties":{"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":"Required."},"description":{"type":"string","maxLength":500,"description":""},"location":{"type":"string","maxLength":120,"description":""}}},"maxItems":50,"description":"sceneId is unique within the package."},"nodes":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["nodeId","sceneId","title","objective"],"properties":{"nodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"sceneId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must name a scene declared in `scenes`."},"title":{"type":"string","maxLength":120,"description":""},"objective":{"type":"string","maxLength":500,"description":""},"prerequisites":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"maxItems":10,"description":"Node ids that must be completed first. Each must be a declared node, and never this node itself."}}},"maxItems":100,"description":"A plot node is an objective inside a scene. nodeId is unique."},"establishedFacts":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["factId","text"],"properties":{"factId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"text":{"type":"string","maxLength":500,"description":""}}},"maxItems":200,"description":"factId is unique."},"branches":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["branchId","fromNodeId","condition","toNodeId"],"properties":{"branchId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"fromNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"toNodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Must be a declared node."},"condition":{"type":"string","maxLength":500,"description":"Descriptive text in v1 — the runtime does not evaluate it. It is what a turn offers as a next option."}}},"maxItems":100,"description":"branchId is unique."},"endings":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["endingId","title","condition"],"properties":{"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120,"description":""},"condition":{"type":"string","maxLength":500,"description":""}}},"maxItems":60,"description":"endingId is unique."},"stateSchema":{"type":"object","additionalProperties":true,"description":"The declared world-state flags. Absent means a package with no flags.","properties":{"flags":{"type":"array","items":{"type":"object","additionalProperties":true,"required":["key","kind"],"properties":{"key":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"kind":{"type":"string","enum":["boolean","number","string"]},"initial":{"description":"Optional starting value. Its JSON type must match `kind` exactly — a number initial on a string flag is refused."},"public":{"type":"boolean","enum":[true],"description":"Contract v4. `true` publishes this flag — its NAME and its VALUE — on the progress checkpoint’s `publicFlags` for any client holding the instance. Absent keeps it out of the checkpoint (a flag’s name can itself be a spoiler). `false` is refused: the only two states are published and not. Declaring any public flag is what makes a package contract v4; the server decides the version and refuses a client-supplied `contractVersion`."}}},"maxItems":50,"description":"key is unique."}}},"effectRules":{"type":"object","additionalProperties":true,"required":["conflict"],"description":"How the coordinator settles two roles proposing incompatible effects on the SAME target in one turn. Absent means `reject`. There is deliberately no \"the model decides\" option.","properties":{"conflict":{"type":"string","enum":["reject","first_wins"]}}},"initialRelations":{"type":"array","maxItems":50,"description":"Contract v2. The relations the story opens with, seeded into a NEW world instance only. A SET, not a list: [a,b] and [b,a] are one relation, a pair declared twice is REFUSED rather than merged (differing descriptors are a contradiction; identical ones are still two rows for one key), and the whole array is sorted before hashing — so authoring order cannot change the content hash. Every other array in this contract keeps declaration order. EVERY relation here is PUBLIC: relations live in the shared block of world state, so a relation that must stay hidden belongs in the authorised role’s `secrets` instead. Supplying this field is what makes a package contract v2; the server decides the version and refuses a client-supplied `contractVersion`.","items":{"type":"object","additionalProperties":true,"required":["between","descriptor"],"properties":{"between":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{1,63}$","description":"Lowercase machine id, 2–64 chars."},"description":"Two DISTINCT declared storyRoleIds. Normalized to ascending order."},"descriptor":{"type":"string","maxLength":120}}}},"series":{"type":"object","additionalProperties":true,"required":["seriesId","title","episodes"],"description":"Contract v3. Episodes, and the deterministic rules that decide when one is over. Supplying this field is what makes a package contract v3; the server decides the version and refuses a client-supplied `contractVersion`. Every id a rule names — node, fact, scene, flag, ending — must be DECLARED in the same package: a rule that can never hold would make the episode end only by fallback, silently. Ending rules are evaluated SERVER-SIDE over committed state after a beat commits; no model, client or prompt supplies an endingId.","properties":{"seriesId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"episodes":{"type":"array","maxItems":24,"description":"DECLARATION order is broadcast order. At least one; there is no empty-series form, because absent already means \"not a serial\".","items":{"type":"object","additionalProperties":true,"required":["episodeId","title","turnBudget","fallbackEndingId"],"properties":{"episodeId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"title":{"type":"string","maxLength":120},"turnBudget":{"type":"integer","minimum":1,"maximum":200,"description":"Committed beats this episode may run before the fallback ending fires. Capped at the evidence window: an episode longer than one evidence read can never be turned into a script."},"fallbackEndingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"A DECLARED ending, used when no rule ever holds. Required — the alternative is an episode that cannot end."},"endingRules":{"type":"array","maxItems":20,"description":"Order-independent: priority decides (lower wins) and `ruleId` breaks a tie. Reordering this array changes nothing, unlike every other array in this contract.","items":{"type":"object","additionalProperties":true,"required":["ruleId","endingId","priority","all"],"properties":{"ruleId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"endingId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."},"priority":{"type":"integer"},"all":{"type":"array","minItems":1,"maxItems":10,"description":"A CONJUNCTION over a closed condition vocabulary — `node_completed`, `fact_revealed`, `flag_equals`, `scene_is`, `clock_at_least`. An OR is two rules. An empty list would be true of every state and is refused.","items":{"type":"object","additionalProperties":true}}}}}}}}}},"visibility":{"type":"object","additionalProperties":true,"required":["factDefault"],"description":"Role visibility over established facts. Canon is world-shared by definition and role `secrets` are private by definition; facts sit between. Absent means { factDefault: \"shared\" }.","properties":{"factDefault":{"type":"string","enum":["shared","role-scoped"]},"factAllow":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_.-]{1,63}$","description":"Lowercase machine id; dots allowed for namespacing (fact.ferry)."}},"description":"worldRoleId → factId[]. Every key must be a declared worldRoleId and every value a declared factId; both are refused by name rather than ignored."}}}}}}}},"responses":{"200":{"description":"Content unchanged","content":{"application/json":{"schema":{"type":"object","properties":{"packageId":{"type":"string"},"revision":{"type":"integer"},"contentHash":{"type":"string"},"created":{"type":"boolean"}}}}}},"201":{"description":"New revision","content":{"application/json":{"schema":{"type":"object","properties":{"packageId":{"type":"string"},"revision":{"type":"integer"},"contentHash":{"type":"string"},"created":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/story-packages/import-candidate":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."}],"post":{"summary":"Read a dramatic script and PROPOSE a Story Package candidate. Writes nothing: no package, no world, no agent, and the script itself is never stored. One bounded model call whose output is validated by the same total validator a hand-written package faces — a candidate that does not validate is returned WITH its errors rather than repaired. Confirming a candidate is a separate human act through the normal publish route. `relationsWritable` reports whether THIS environment may publish contract v2 (relations); relations are always PUBLIC shared state.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"script":{"type":"string","maxLength":16000},"model":{"type":"string"}},"required":["script"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"candidate":{"$ref":"#/components/schemas/StoryPackageContent"},"nodesFound":{"type":"integer"},"relationsWritable":{"type":"boolean"},"relationsArePublic":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/story-packages/import-candidate/validate":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."}],"post":{"summary":"Re-run the FULL Story Package validator over an edited candidate. No model call, no storage, no writes. Answers 200 in BOTH directions — `valid: true` with the NORMALIZED candidate (relation pairs sorted, an empty relation list dropped) so a wizard shows what will actually be published, or `valid: false` with `errors[]`. An invalid candidate is the answer to the question, not a failed request.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"candidate":{"$ref":"#/components/schemas/StoryPackageContent"}},"required":["candidate"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean"},"errors":{"type":"array","items":{"type":"string"}},"candidate":{"$ref":"#/components/schemas/StoryPackageContent"},"usesRelations":{"type":"boolean"},"relationsWritable":{"type":"boolean"},"relationsArePublic":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/story-packages/{packageId}/versions":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"},"description":"The story package id."}],"get":{"summary":"The immutable revision list for one package. `versions` holds only the revisions THIS deployment can read: a revision stamped with a story contract this build does not support is withheld and counted in `unsupportedCount` instead. Read `latestRevision` — the head’s own — rather than treating `versions[0]` as the newest revision.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"versions":{"type":"array","items":{"type":"object","additionalProperties":true}},"unsupported":{"type":"array","items":{"type":"object","properties":{"revision":{"type":"integer"},"contractVersion":{"type":["integer","null"]}}}},"unsupportedCount":{"type":"integer"},"hasUnsupported":{"type":"boolean"},"latestRevision":{"type":["integer","null"]}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/story-packages/{packageId}/disable":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"packageId","in":"path","required":true,"schema":{"type":"string"},"description":"The story package id."}],"post":{"summary":"Disable or re-enable a package. A disabled package stops resolving for every world that pins it — the pinned turn refuses rather than running a story nobody stands behind.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"disabled":{"type":"boolean"}},"required":["disabled"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"packageId":{"type":"string"},"disabled":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."}],"get":{"summary":"List worlds (environments) in this project.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"environments":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Create a world as revision 1. Roles bind agents (one agent per role); capabilityAllow is default-deny and roles may only NARROW it; runtimeMode picks the runtime EXPLICITLY (omitted or \"actor\" = the per-role event wake; \"coordinated\" = the multi-role turn coordinator, which also needs a storyPackageRef pin).","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"rulesText":{"type":"string"},"providerRef":{"type":"string"},"runtimeMode":{"type":"string","enum":["actor","coordinated"]},"storyPackageRef":{"type":"object","properties":{"packageId":{"type":"string"},"revision":{"type":"integer"},"contentHash":{"type":"string"}}},"capabilityAllow":{"type":"object","properties":{"views":{"type":"array","items":{"type":"string"}},"actions":{"type":"array","items":{"type":"string"}},"events":{"type":"array","items":{"type":"string"}}}},"roles":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["name","roles"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"environmentId":{"type":"string"},"environmentRevision":{"type":"integer"},"contentHash":{"type":"string"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."}],"get":{"summary":"Head plus the current world definition.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"summary":"Publish the next world revision. The body is a COMPLETE world definition — this is a whole replacement, never a partial update, so a body without `name` and `roles` is refused as a malformed definition rather than applied as a patch (enable/disable lives on POST …/{envId}/disable). Existing world INSTANCES keep the revision they were created on — a published change reaches new instances only, which is what stops a running story from changing its rules or its runtime underneath its players.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Content unchanged","content":{"application/json":{"schema":{"type":"object","properties":{"environmentId":{"type":"string"},"environmentRevision":{"type":"integer"},"contentHash":{"type":"string"},"created":{"type":"boolean"}}}}}},"201":{"description":"New revision","content":{"application/json":{"schema":{"type":"object","properties":{"environmentId":{"type":"string"},"environmentRevision":{"type":"integer"},"contentHash":{"type":"string"},"created":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"summary":"Delete an experience that has NEVER BEEN RUN. Refused with 409 `has_instances` — and the instance count — the moment any instance exists: a membership parents the story ledger, and the rows under it are the record that a beat happened, with evidence drafts, editorial drafts and approved exports among their descendants. Deleting an experience that HAS run would cascade through reviewed, signed-off content, which is a decision this endpoint does not make for you. Disable it instead: a disabled experience no longer counts against the per-project ceiling, so the slot comes back either way. Owner plane only, and deliberately not on the /admin mirror — the mirror carries reads, not the most destructive write on this plane.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/cost":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"months","in":"query","required":false,"schema":{"type":"integer"},"description":"Window length; clamped server-side."}],"get":{"summary":"What this experience costs: tokens and model hops per month. Read from a per-experience view written in the SAME merge as the project totals — one write, two views, never a second charge. Keyed by ENVIRONMENT, not by instance: a month document has a hard size ceiling and a project has one instance per player, so per-player cost is a separate question this does not answer. Months that could not be read are reported rather than counted as zero spend.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/preflight":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."}],"get":{"summary":"WHY the signed doors are refusing you. Both doors answer one uniform 403 on a bad signature so the wire cannot tell an attacker which credential to keep guessing at; this returns the reason out of the project’s own audit trail — which door, which code, when. Reasons are a closed vocabulary (missing | malformed | unknown_key | stale | bad_signature | no_keys) and the rows carry no key material. OWNER plane only, and deliberately not on the /admin mirror: a machine credential reading a stream of \"which credential is wrong\" is a different risk from one reading world state.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/versions":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."}],"get":{"summary":"The immutable revision list for one world.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"versions":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/disable":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."}],"post":{"summary":"Disable or re-enable a world. A disabled world stops every session, turn and event at the next hop — the live gate, not a cached flag.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"disabled":{"type":"boolean"}},"required":["disabled"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"environmentId":{"type":"string"},"disabled":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/episodes/next":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance id."}],"post":{"summary":"Start the NEXT episode of a worldline that just finished one. The worldline does not restart: its instance id, membership, state and ledger all continue, and this moves the `episode` field on — nothing is copied and nothing is deleted, so there is no migration to get wrong. The response carries the CARRYOVER: a projection of what the next episode gets to stand on (canon the last episode established, where the characters stand, the threads it left hanging, the setups it never paid off). Role-private notes are never in it, and no memory is carried because the world runtime writes none. Idempotent — the run id is derived from the worldline, the episode and the revision it starts at, so a retry lands on the same run and answers `created: false`. TWO lanes since 1.30.0: a signed-in project admin (owner lane, empty body), or the same two proofs as the turn door — a Secret Key AND a provider signature over the exact raw body, id slot `epn:{requestId}` with `requestId` in the body — so an unattended serial backend can advance its own player. Still no /admin mirror: an admin key proves the project, never the Provider. Refuses with `episode_not_ended` (the current episode is still running), `series_complete` (there is no episode after this one) or `not_a_serial`.","security":[{"OwnerToken":[]},{"SecretKey":[],"SourceSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string","description":"Machine lane only: the signature id slot is `epn:{requestId}`. The owner lane sends an empty body. Not an idempotency key — the run id is derived server-side either way."}}}}}},"responses":{"200":{"description":"Already started — same run","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"boolean"},"episode":{"type":"object","additionalProperties":true},"carryover":{"type":"object","additionalProperties":true}}}}}},"201":{"description":"Next episode started","content":{"application/json":{"schema":{"type":"object","properties":{"created":{"type":"boolean"},"episode":{"type":"object","additionalProperties":true},"carryover":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliberations":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world id."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance id."}],"post":{"summary":"Preview where a beat could go WITHOUT taking it. Returns short, PUBLIC directions plus a signed envelope; selecting one commits it through the ordinary turn door. Writes nothing — no world state, no ledger, no session, no outbox — and the candidates are not stored anywhere: they live in the envelope the caller holds. OFF unless the pinned world revision declares `deliberation`, and coordinated-mode only. Machine lane, with its own signature id namespace (`dlb:{requestId}`) so a deliberation signature can never be replayed as a turn or an event.","security":[{"SecretKey":[],"SourceSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"requestId":{"type":"string","maxLength":128,"description":"Signed in the `dlb:` id slot; also fixes the turn id a later select commits under."},"text":{"type":"string","maxLength":4000},"model":{"type":"string"}},"required":["requestId","text"]}}}},"responses":{"201":{"description":"Candidates and a signed envelope","content":{"application/json":{"schema":{"type":"object","properties":{"candidates":{"type":"array","description":"The WHOLE of what a user may see. No reasoning, no per-role output, no simulated effects, no scores, no raw model response.","items":{"type":"object","properties":{"candidateId":{"type":"string"},"title":{"type":"string"},"direction":{"type":"string"},"shortTeaser":{"type":"string"},"participatingRoles":{"type":"array","items":{"type":"string"}}}}},"envelope":{"type":"string"},"expiresAt":{"type":"integer"},"budgetSpent":{"type":"object","properties":{"modelCalls":{"type":"integer"},"tokens":{"type":"integer"},"ms":{"type":"integer"}}},"stop":{"type":"string","enum":["complete","candidate_cap","model_call_cap","token_cap","time_cap","no_roles"],"description":"Why it stopped. A run that hit a ceiling says so rather than presenting a short list as a complete one."}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliberations/select":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world id."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance id."}],"post":{"summary":"Commit the direction a player chose. This IS an ordinary beat — same coordinator, same ledger, same WorldTurnResult. A deliberation has no privileges: its simulated effects are never carried here, roles re-propose their own, and the only thing that crosses is `direction`, a narrative steer. `direction` is caller-supplied — the field on this request, at most 400 characters — and is NOT bound to the chosen candidate by the server: the envelope carries a `candidateDigest` over the candidates, but this operation does not re-prove it, so hold the direction to one of the deliberation candidates on your own backend before calling. Every claim in the envelope is re-proven against live reads BEFORE any model call — membership, revision, story pin, stateRevision, provider and actor binding, and the live gate — so a world that moved answers 409 `stale_deliberation` having spent and written nothing. The turn id is fixed inside the envelope, so re-sending the same envelope is a duplicate rather than a second beat.","security":[{"SecretKey":[],"SourceSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"envelope":{"type":"string","description":"Opaque. Hand back exactly what the deliberation returned."},"candidateId":{"type":"string"},"direction":{"type":"string","maxLength":400},"text":{"type":"string","maxLength":4000,"description":"The same input the deliberation was about — compared against the envelope’s digest."}},"required":["envelope","candidateId","direction","text"]}}}},"responses":{"200":{"description":"The committed beat","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/turns":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"},"description":"How many beats to return. Capped server-side."},{"name":"sinceSeq","in":"query","required":false,"schema":{"type":"integer"},"description":"Walk FORWARD from a revision already processed: returns beats with seq > sinceSeq, OLDEST first, plus `nextSinceSeq` to continue with. This is the resume cursor — without it the response is the newest-first window, which cannot be paged past its cap. The response names its own `order` either way."}],"get":{"summary":"The committed TIMELINE — read-only, owner-token. Projects the ledger into a beat-per-turn shape: who acted, which roles spoke and what they said, what the world changed, which effects were accepted or refused (with the taxonomy code), and the revision each beat committed at. Pass `sinceSeq` to resume forward from a known revision. Delivered narrative only; the ledger never held a prompt, a tool trace, or the private role layer.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Drive ONE coordinated beat: server-side role selection, per-role perspective, typed effect proposals settled by the coordinator, one commit, then delivery. turnId is the identity — re-send it to retry (it re-runs no model and commits nothing twice), mint a new one for a new beat. The \"evt:\" prefix is reserved for trusted events. Requires runtimeMode \"coordinated\".","security":[{"SecretKey":[],"SourceSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"turnId":{"type":"string"},"trigger":{"type":"object","properties":{"kind":{"type":"string","enum":["user"]},"text":{"type":"string"},"sourceRoleId":{"type":"string"},"focusRoles":{"type":"array","items":{"type":"string"},"maxItems":16,"description":"Narrow this beat to the named roles — shrink-only, mirroring the event lane’s subscription routing. Intersected with the bound story cast: an id outside the cast selects nothing, and a focus that selects nobody is the ordinary \"no selectable role\" refusal, which spends no model call. Roles outside the focus appear in skippedRoles as \"not focused\". Absent or empty means every bound role, capped as before."}},"required":["kind","text"]},"traceId":{"type":"string"}},"required":["turnId","trigger"]}}}},"responses":{"200":{"description":"The turn ran (see completionStatus / executionStatus / deliveryStatus)","content":{"application/json":{"schema":{"type":"object","properties":{"turnId":{"type":"string"},"worldInstanceId":{"type":"string"},"beforeStateRevision":{"type":"integer"},"afterStateRevision":{"type":"integer"},"selectedRoles":{"type":"array","items":{"type":"string"}},"roleMessages":{"type":"array","items":{"type":"object","properties":{"roleId":{"type":"string"},"message":{"type":"string"},"fallback":{"type":"boolean"}}}},"committedStateDiff":{"type":"array","items":{"type":"object","additionalProperties":true}},"rejectedEffects":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"reason":{"type":"string"},"roleId":{"type":"string"},"kind":{"type":"string"}}}},"skippedRoles":{"type":"array","description":"Roles that were in the cast but did not speak. `reason` is the HUMAN line and carries the sub-cause; it is free to be reworded, so never key off it. `code` is the machine half — a closed enum (see below) added 2026-09-09 after an integrator, given the prefixes in a letter because this document carried none, built a cost model on `reason.startsWith(...)`. Subtract to read a beat: in `selectedRoles` and absent here means the role chose silence; listed here means they never ran.","items":{"type":"object","properties":{"roleId":{"type":"string"},"reason":{"type":"string"},"code":{"type":"string","enum":["not_focused","role_cap_reached","no_actor_bound","deadline_exhausted","not_admitted","actor_unavailable","session_busy","content_policy","turn_error","no_message","effect_refused","conflict_undelivered"],"x-pouchy-cost-model":{"version":"1.0.0","noModelCall":["not_focused","role_cap_reached","no_actor_bound","deadline_exhausted","not_admitted","actor_unavailable","session_busy","content_policy"],"modelMayHaveRun":["turn_error","no_message","effect_refused","conflict_undelivered"],"digest":"4a8369322817e3fa"},"description":"WHY, machine-readable. No model call was made for `not_focused` (the caller narrowed the cast), `role_cap_reached` (the three-seat staging limit, decided in declaration order — the same role is skipped every beat until the cast changes), `no_actor_bound` (setup incomplete), `deadline_exhausted` (the beat ran out of budget first), `not_admitted` (metering / suspension / a gate said no — a decision), `actor_unavailable` (the actor registry row is missing or belongs to the other env — a defect), `session_busy` (a beat for this role is already in flight) or `content_policy` (the moderation gate silenced them BEFORE any model ran). A model call was made, or may have been, for `turn_error` (the runtime failed — deliberately does NOT promise a call happened: assembly failing and a provider dying mid-stream both land here) and `no_message` (the turn completed with no line — a non-message terminal, or a deliberately silent beat). The role DID speak and the line was withheld for `effect_refused` (their effect was refused and the one correction did not save it) and `conflict_undelivered` (the world moved under the turn; nothing was published to anyone). Absent only from a non-production caller; every path in the platform sets it."}}}},"nextOptions":{"type":"array","items":{"type":"object","properties":{"branchId":{"type":"string"},"condition":{"type":"string"}}}},"repairs":{"type":"array","items":{"type":"object","properties":{"roleId":{"type":"string"},"outcome":{"type":"string","enum":["repaired","fallback"]},"reason":{"type":"string"}}}},"traceId":{"type":"string"},"executionStatus":{"type":"string","enum":["committed","not_committed"]},"deliveryStatus":{"type":"string","enum":["delivered","pending","none"]},"completionStatus":{"type":"string","enum":["complete","partial","duplicate","conflict","rejected","refused"]}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/turns/{turnId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"turnId","in":"path","required":true,"schema":{"type":"string"},"description":"The turn id."}],"get":{"summary":"Read back a COMMITTED turn in the same shape the door returned — the recovery path when a response was lost. 404 means the turn never committed: there is no \"maybe\" state to report.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/overview":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."}],"get":{"summary":"Read-only operational overview for one world: readiness (are all roles bound, is the provider connected, do all capabilities resolve) with a machine next-step per blocker, plus a bounded rollup of active instances, delivery health, pending script drafts, and whether quality has been evaluated. Composes existing reads into one call for the dashboard; writes nothing.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/state":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"The world’s current state in the USER projection — the shape is described below rather than left to a live call. `flags` is EVERY flag, not only those an author marked `public: true`: that filter belongs to the progress checkpoint, whose audience is a player. `completedNodes` is the plot progress the checkpoint reports only as a COUNT. `factsVisible` is the world-revealed set. The role-private layer is never here. `stateRevision` is the CAS counter the turns door expects.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"worldInstanceId":{"type":"string"},"environmentId":{"type":"string"},"environmentRevision":{"type":"integer"},"storyPackageRef":{"type":"string"},"state":{"type":"object","properties":{"stateRevision":{"type":"integer"},"clock":{"type":"integer"},"sceneId":{"type":"string"},"location":{"type":"string"},"flags":{"type":"object","additionalProperties":true},"completedNodes":{"type":"array","items":{"type":"string"}},"factsVisible":{"type":"array","items":{"type":"string"}},"relations":{"type":"object","additionalProperties":true},"entities":{"type":"object","additionalProperties":true},"lastEntries":{"type":"array","items":{"type":"object","properties":{"entryId":{"type":"string"},"kind":{"type":"string"},"at":{"type":"integer"}}}}}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/consistency":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"cursor","in":"query","required":false,"schema":{"type":"integer"},"description":"Resume an `incomplete` scan from its cursor."},{"name":"layers","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated. Omitted (the default) means deterministic only, with zero model calls and a body byte-identical to world API 1.19. `model_assisted` additionally requests the judgement layer, which also needs a server flag and a declaration on the published world revision — three gates, all required."}],"get":{"summary":"What this world’s own record proves about itself — the DETERMINISTIC layer. Every finding is a comparison between committed facts and the story package this instance pins; nothing is a judgement about writing, and nothing is repaired. Rate limited on two axes (project and IP, 30/min each) and never cached. Answers 200 with `status: \"unavailable\"` when the world cannot be read, and 429 with Retry-After when a ceiling is hit.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"status":{"type":"string","enum":["complete","incomplete"]},"contractVersion":{"type":"integer"},"findings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["revision_mismatch","ledger_drift","ledger_missing_entries","unrecorded_beat_claim","orphan_branch_reference","node_prereq_violation","effect_without_grant","flag_schema_violation","relation_change_without_cause","verbatim_secret_disclosure"],"description":"A CLOSED set. Members may be added in a later minor; a published member is never removed, renamed, or widened in meaning — a criterion found to be wrong is deprecated and replaced beside it. Treat an unknown code as informational rather than failing on it."},"layer":{"type":"string","enum":["deterministic"]},"status":{"type":"string","enum":["proven"]},"severity":{"type":"string","enum":["blocking","warning","info"]},"evidence":{"type":"array","description":"Never empty. Identifiers and already-committed facts; `quote` appears only on verbatim matches and carries the matched fragment, capped — never a whole line.","items":{"type":"object","properties":{"turnId":{"type":"string"},"seq":{"type":"integer"},"roleId":{"type":"string"},"sceneId":{"type":"string"},"nodeId":{"type":"string"},"branchId":{"type":"string"},"flagKey":{"type":"string"},"effectKind":{"type":"string"},"quote":{"type":"string"},"ledgerRejectionCode":{"type":"string"}}}}},"required":["code","layer","status","severity","evidence"]}},"coverage":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"skipped":{"type":"integer","description":"Entries this check could not examine."},"reason":{"type":"string","enum":["evidence_field_absent","scan_incomplete"]}},"required":["code","skipped","reason"]},"description":"What could NOT be examined, and why. A non-empty coverage list on an otherwise clean report means the report is clean about less than the whole world — absence of a finding is not proof of absence."},"entriesScanned":{"type":"integer"},"findingsTruncated":{"type":"boolean"},"cursor":{"type":"integer","description":"Present only when `status` is `incomplete`. Pass it back to continue."},"modelAssistedFindings":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","enum":["suspected_canon_conflict","suspected_revival","suspected_persona_conflict","suspected_secret_leak","suspected_cross_ip_contamination"],"description":"A SEPARATE closed set from the deterministic one, which stays frozen. These are judgements, not proofs."},"layer":{"type":"string","enum":["model_assisted"]},"status":{"type":"string","enum":["potential","confirmed","dismissed"],"description":"`potential` until a person decides. Confirmation is an editorial judgement recorded against the finding — it changes nothing about the story, the canon, the ledger or the world state."},"severity":{"type":"string","enum":["warning","info"],"description":"Never `blocking`, at the type level as well as here. A suspicion must not be able to fail somebody’s build. `suspected_cross_ip_contamination` in particular is an editorial opinion even once confirmed, and is not evidence of isolation."},"findingId":{"type":"string","description":"Stable across scans for the same judgement: derived from the code and the server-normalised evidence refs, and from nothing else. A rephrased model note does not mint a new finding, which is what lets a dismissal stick."},"evidence":{"type":"array","items":{"type":"object","properties":{"turnId":{"type":"string"},"roleId":{"type":"string"}}},"description":"Assembled server-side from committed rows. The model selects which rows by index; it never writes evidence."},"suggestion":{"type":"object","properties":{"kind":{"type":"string","enum":["author_note"]},"text":{"type":"string"}}},"reviewedBy":{"type":"string"},"reviewedAt":{"type":"string"}},"required":["code","layer","status","severity","findingId","evidence"]},"description":"Present ONLY when the request asked for `model_assisted` in `layers`. A default request carries neither this key nor `modelAssistedNote`, byte-identically to world API 1.19. Suspicions never appear in `findings` — a consumer testing `severity === \"blocking\"` there must not begin failing on an opinion."},"modelAssistedNote":{"type":"string","enum":["disabled_server","not_declared","rate_limited","unavailable","incomplete"],"description":"Why the model layer produced less than a full pass, when it was asked for. `disabled_server` and `not_declared` mean a gate was shut; `incomplete` means the per-scan budget stopped it early and the suspicions listed are not the whole picture."}},"required":["status","contractVersion","findings","coverage","entriesScanned","findingsTruncated"]},{"type":"object","properties":{"status":{"type":"string","enum":["rate_limited"]},"retryAfterSec":{"type":"integer"}},"required":["status","retryAfterSec"]},{"type":"object","properties":{"status":{"type":"string","enum":["unavailable"]},"reason":{"type":"string","enum":["world_unavailable"]}},"required":["status","reason"]}]}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/progress":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"Where the story is, and whether it can go on — a DERIVED checkpoint over facts that already committed. Nothing is stored, no model is asked, and the story package is the one this instance pinned, at its exact revision and content hash. Answers 200 even with no checkpoint: \"this world cannot be resumed\" is an answer about the story, not an error about the request.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"status":{"type":"string","enum":["ready"]},"resumable":{"type":"boolean"},"checkpoint":{"type":"object","properties":{"contractVersion":{"type":"integer","description":"Checkpoint contract version. 1 today."},"environmentRevision":{"type":"integer","description":"The revision this INSTANCE is pinned to for life — never the environment head."},"storyPackageRef":{"type":"object","properties":{"packageId":{"type":"string"},"revision":{"type":"integer"},"contentHash":{"type":"string"}},"required":["packageId","revision","contentHash"]},"stateRevision":{"type":"integer","description":"The CAS counter the turns door expects."},"started":{"type":"boolean","description":"At least one beat has committed."},"currentScene":{"type":["object","null"],"properties":{"sceneId":{"type":"string"},"title":{"type":"string"}},"description":"Null when the world has not opened a scene. A sceneId the pinned story does not declare is NOT reported as null — the whole read answers `story_reference_invalid` instead."},"completedNodeCount":{"type":"integer"},"declaredNodeCount":{"type":"integer","description":"How many nodes the AUTHOR declared. Not a denominator: a branching story never visits all of them, so a ratio of these two numbers means nothing."},"publicRelations":{"type":"array","items":{"type":"object","properties":{"between":{"type":"array","items":{"type":"string"}},"descriptor":{"type":"string"}}},"description":"World-public relations, sorted. Role-private material never appears here."},"publicRelationsTruncated":{"type":"boolean"},"publicFlags":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"description":"boolean | number | string, or null when the flag was never set and declares no initial"}}},"description":"Contract v4. The flags the pinned story declared `public: true`, sorted by key, with the value the state THIS checkpoint reports (the declared initial when never set; null when there is neither). ALWAYS present — an empty array for a story that publishes nothing — so absent never reads as unknown. A flag the story did not mark public never appears here."},"publicFlagsTruncated":{"type":"boolean"},"recentProgressRecords":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"integer"},"kind":{"type":"string","enum":["turn","event","system"]},"at":{"type":"integer"}}},"description":"The newest committed beats, newest first — that one happened, at a revision, at a time. Not a story summary: no patches, no lines, no actors."},"recentProgressTruncated":{"type":"boolean"},"nextOptions":{"type":"array","items":{"type":"object","properties":{"branchId":{"type":"string"},"condition":{"type":"string"}}},"description":"Branches reachable from the completed nodes, derived from the pinned story. Never model-generated, and capped server-side."},"rebuildable":{"type":"boolean","description":"Always true — nothing here is stored; it is a function of committed facts."}},"required":["contractVersion","environmentRevision","storyPackageRef","stateRevision","started","completedNodeCount","declaredNodeCount","publicFlags","publicFlagsTruncated","nextOptions"]}},"required":["status","resumable","checkpoint"]},{"type":"object","properties":{"status":{"type":"string","enum":["unavailable"]},"resumable":{"type":"boolean","enum":[false]},"reason":{"type":"string","enum":["world_unavailable","story_reference_invalid"],"description":"`world_unavailable` covers every \"no answer\" cause at once — no such instance, no access, a revoked or deleted package, a contract this build cannot read. They are one word on purpose: naming them apart would let a caller map which internal objects exist by reading the refusals."}},"required":["status","resumable","reason"]}]}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/consistency/reviews/{findingId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"findingId","in":"path","required":true,"schema":{"type":"string"},"description":"The finding this verdict is about."}],"post":{"summary":"Record a person’s verdict on one model-assisted finding. Owner plane only and NOT mirrored on the admin key: reading a scan is something a backend can do unattended, deciding that a suspicion about someone’s writing is real is not. The body is a CLAIM — the server re-normalises the evidence refs, checks each against this instance’s committed ledger, and recomputes the findingId before writing. The effect is bounded to the verdict: no Story Package, no canon, no ledger, no world state. Idempotent on the same decision; pass `expectedRevision` for CAS.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string","enum":["confirmed","dismissed","potential"]},"code":{"type":"string"},"evidenceRefs":{"type":"array","items":{"type":"object","properties":{"turnId":{"type":"string"},"roleId":{"type":"string"}}}},"expectedRevision":{"type":"integer"}},"required":["decision","code","evidenceRefs"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/ledger/replay":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"post":{"summary":"Verify that the materialized state is what the committed ledger says. ALWAYS a dry run: it reports consistent / drift / missing_entries / illegal_patch / incomplete / unavailable, and never repairs. Bounded and resumable — pass a previous report’s cursor to continue.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"cursor":{"type":"string"},"maxEntries":{"type":"integer"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"worldInstanceId":{"type":"string"},"verdict":{"type":"string"},"detail":{"type":"string"},"fromRevision":{"type":"integer"},"rebuiltRevision":{"type":"integer"},"rebuiltHash":{"type":"string"},"currentRevision":{"type":"integer"},"currentHash":{"type":"string"},"entriesScanned":{"type":"integer"},"cursor":{"type":"string"},"saturated":{"type":"boolean"},"elapsedMs":{"type":"integer"},"dryRun":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliveries":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"The delivery queue for this instance: statuses, timings, attempt counts, error CLASSES, and which row is blocking its session. Never message bodies — one delivery’s text comes from the single-row route, explicitly, and that read is audited. Optional filters: status, sessionId, turnId, cursor, limit. Filters narrow what you see and never change the blocking calculation, which runs over the whole instance.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","additionalProperties":true}},"truncated":{"type":"boolean"}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliveries/drain":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"post":{"summary":"Run ONE bounded drain now, scoped to this instance — for the moment after a requeue, when the queue should move without waiting for the sweeper’s next tick. The report includes `blocked`: sessions still owing their oldest line, which is what says whether the requeue helped.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliveries/{deliveryId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"},"description":"The delivery id."}],"get":{"summary":"One delivery. `?includePayload=true` returns the line itself and writes an audit row naming the reader — the audit is what separates debugging a stuck queue from reading someone’s conversation. Signing material and the payload hash never travel.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliveries/{deliveryId}/requeue":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"},"description":"The delivery id."}],"post":{"summary":"Put a dead letter back in the queue. Takes NO body by design: the beat was committed by the coordinator, and an operator who could supply a replacement line would be authoring world history through the delivery plane. Idempotent by state — an already-queued row is a no-op. Refused for a delivered row, and for one whose payload has aged out (30 days), because sending \"something\" is worse than sending nothing. There is no discard verb.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}/approved-export":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The evidence draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."}],"get":{"summary":"List the approved exports produced from this editorial draft, without their bodies.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"exports":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Turn an APPROVED editorial draft into a versioned approved script export — the production hand-off. Carries lineage from the original story package through the world instance, ledger range, evidence draft and editorial draft to the reviewer who approved it; every line keeps the origin the server verified, and human edits keep the editor’s text. Idempotent on content: the same approved review exports to the same id forever. `version` selects the contract and DEFAULTS TO 1 (`ApprovedScriptExportV1`, frozen); `version: 2` returns `ApprovedScriptExportV2`, which adds a synopsis assembled from scene headings, character notes, a mechanical episode split, the beats that completed a story node, committed relation changes and where the run diverged from its story — all derived from committed material, with no model call. The two contracts hash under separate domains, so one approval exported both ways yields two different ids; that is correct, and each is stable. Any other `version` value is a 400. `notify: true` also hands it to the project’s webhooks as `world.script_approved`, carrying identifiers, lineage and `contractVersion` but never the script itself. Publishes nothing.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"notify":{"type":"boolean"},"version":{"type":"integer","enum":[1,2],"description":"Export contract. Defaults to 1."}}}}}},"responses":{"200":{"description":"Existing export","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"201":{"description":"Export created","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}/approved-export/{exportId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The evidence draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."},{"name":"exportId","in":"path","required":true,"schema":{"type":"string"},"description":"The approved export id — a content digest."}],"get":{"summary":"Read ONE approved export, SCRIPT INCLUDED. The collection GET beside this lists exports without their bodies; this is the only read that returns one. Mirrored on the admin key at the matching `/v1/admin/...` path, so an unattended backend can fetch an approved script without a browser anywhere in its deployment — every field it needs is in the `world.script_approved` webhook (`exportId`, and `environmentId` / `worldInstanceId` / `evidenceDraftId` / `editorialDraftId` inside `lineage`). It READS only: minting an export is a reviewer signing off on a specific text and stays on the owner plane. The body cannot change — `exportId` is its content digest. Narrow `content.contractVersion` before reading version-specific fields. Any miss — wrong project, environment, instance, draft, editorial or id — answers the SAME 404, so refusals cannot be used to map which objects exist. Served `Cache-Control: no-store`.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}/approved-export/candidate":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The evidence draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."}],"post":{"summary":"Derive the next Story Package as a CANDIDATE from a VERSION 1 approved export (`?exportId=`). A version 2 export is refused with 422: the candidate is the one path from an export into published canon, and which V2 fields may become canon has not been decided — so export the approval as version 1 and derive from that. Validated against the same validator the publish route runs, then returned — this writes no package, and the story-package plane holds published revisions only, so a candidate becomes a package exactly when a person submits it. Only evidence-origin material becomes canon: a model’s connective line does not arrive as an established fact, and a voice that only ever spoke in suggested lines does not become a role. `droppedSuggestions` reports what was left behind. The source package is carried as provenance and never overwritten.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"summary":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliveries/{deliveryId}/rehydrate":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"},"description":"The delivery id."}],"post":{"summary":"Rebuild a dead letter whose text was redacted, FROM THE RECORD — the committed ledger entry, or the verified archive segment holding it once the live rows are pruned. Takes no body: nobody types a replacement line and no model regenerates one. Accepted only if the rebuild re-derives the same delivery id AND the same content digest captured before the text was destroyed. A refusal carries a code (source_missing, archive_unavailable, hash_mismatch, ledger_drift, identity_mismatch) and leaves the row `unrecoverable`, which still blocks the session. Note: the turn log’s other half (the prompt envelope the role answered) is not in the ledger and is not reconstructed.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/deliveries/{deliveryId}/resolve-gap":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"},"description":"The delivery id."}],"post":{"summary":"Account for a beat that cannot be recovered. The only exit from `unrecoverable`, and it takes NO text — the notice’s wording comes from a fixed server-side catalogue and is rendered in the reader’s language by their client. It creates a notice delivery that rides the same at-least-once queue, positioned where the missing beat was; the original becomes `resolved_gap` when that notice LANDS, not when you call this. Not a discard: it makes “content was lost here” a user-visible, audited fact. Writes no world state, touches no canon, deletes no dead-letter audit.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/metrics":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"Operator metrics for one instance, in two families kept deliberately apart: DELIVERY (outbox depth by status, retry pressure, dead letters, commit→delivered latency) answers “did the lines arrive”, and TURN (proposals refused, repair-eligible turns, fallback beats, load across the cast) answers “did the world play well”. Every number is derived from records that already exist, and every scan reports whether it was cut short.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/eval":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"post":{"summary":"Score this instance’s committed history against a quality suite — `drama` or `npc`. Deterministic: no model judges the output. A metric with nothing to measure reports not-applicable rather than scoring zero. Read-only; it replays nothing and advances nothing.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"suite":{"type":"string"},"replayVerdict":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/ledger/archive":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"post":{"summary":"Ledger archival. `action` is one of plan (the default — what archiving WOULD copy; writes nothing), execute (copy the replay-verified range into immutable hash-chained segments; creates only, deletes nothing), verify (recompute the archive chain end to end), prune (delete the LIVE rows a verified, retained archive already holds — the only operation in the world plane that destroys a row). execute and prune both require confirm:true, and prune additionally requires the archive to verify and to have outlived its retention. Nothing calls prune on a schedule. A drift is reported, never repaired.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string"},"confirm":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."}],"get":{"summary":"List this instance’s script drafts — metadata only, never the draft bodies.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"drafts":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Generate (or return) the draft for a COMMITTED range: beats, delivered lines, state changes, refused attempts as conflicts, cast, branches reached, unresolved objectives — every line traceable to a turnId. Deterministic and idempotent on the ledger range: the same range is the same draft. WHOLE or not at all — a draft’s ledgerRange is a claim everything downstream inherits, so a history too long to read at once is refused rather than drafted from its tail. Default scope is the whole INSTANCE; pass `episodeRunId` to scope it to ONE ended episode, whose range comes from that episode’s own terminal record rather than from the caller. That is what keeps a serial worldline draftable after it outgrows the instance-wide cap.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"episodeRunId":{"type":"string","description":"Optional. Scope the draft to one ENDED episode. A running episode has no range to draft yet and answers 404 `episode_run_not_found`."}}}}}},"responses":{"200":{"description":"Existing draft","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"201":{"description":"Draft created","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."}],"get":{"summary":"One draft, body and all. Reading is not exporting.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/review":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."}],"post":{"summary":"Mark a draft reviewed. The reviewer is the signed-in human whose token this call carries — never a field in the body. Reviewing twice keeps the FIRST reviewer.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/export":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."}],"get":{"summary":"The reviewed draft as standard JSON. Refuses with 409 until a human has reviewed it — humanReviewRequired is part of the content, and this is where it has teeth.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."}],"get":{"summary":"List the editorial drafts derived from this evidence draft — metadata, scene counts and suggestion ratios, never the bodies.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"editorials":{"type":"array","items":{"type":"object","additionalProperties":true}}}}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Generate (or return) a model-organized EDITORIAL reading of the evidence draft: scenes, per-scene source turnIds, role and world-state changes. Every line the model claims came from the story is re-checked against the committed record server-side; anything that does not match is recorded as a suggestion. Suggestions are never written back to world canon. Idempotent per model + prompt version + input digest. The evidence draft is never modified.","security":[{"OwnerToken":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"model":{"type":"string"}}}}}},"responses":{"200":{"description":"Existing editorial","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"201":{"description":"Editorial created","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."}],"get":{"summary":"One editorial draft, body and all — scenes, provenance and review state.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"summary":"Move an editorial draft along its human review lifecycle: draft → under_review → changes_requested | approved | rejected. The reviewer is the signed-in human whose token this call carries. `exported` is not settable here — the export route writes it.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}/scenes":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."}],"post":{"summary":"Record one human verdict on one scene: accepted, edited (with the editor’s own text) or rejected. Viewing a scene’s sources and its role / world-state changes is a read of the draft itself — the scene carries both.","security":[{"OwnerToken":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sceneId":{"type":"string"},"decision":{"type":"string"},"editedText":{"type":"string"},"note":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/projects/{projectId}/environments/{envId}/instances/{worldInstanceId}/script-drafts/{draftId}/editorial/{editorialId}/export":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string"},"description":"The project id."},{"name":"envId","in":"path","required":true,"schema":{"type":"string"},"description":"The world (environment) id — env_…."},{"name":"worldInstanceId","in":"path","required":true,"schema":{"type":"string"},"description":"The world instance — one user’s story."},{"name":"draftId","in":"path","required":true,"schema":{"type":"string"},"description":"The draft id."},{"name":"editorialId","in":"path","required":true,"schema":{"type":"string"},"description":"The editorial draft id."}],"get":{"summary":"Preview the export payload without exporting. Refuses with 409 unless the draft is approved. Pure: it changes no state.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"summary":"Export an APPROVED editorial draft and stamp it exported. Rejected scenes do not travel; edited scenes travel as the human’s text, marked humanEdited. Nothing here writes to the story package.","security":[{"OwnerToken":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"Malformed request — the body did not meet the contract.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"401":{"description":"Missing or invalid credential for this lane.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Refused: wrong lane, missing or invalid source signature, or a world/provider that is no longer live. One generic message by design — the specific reason goes to the audit trail.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such project, world, instance, turn or draft. An episode-scoped draft answers 404 with `code: \"episode_run_not_found\"` when no ENDED episode carries that run id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The world disagreed: a dead pin, an actor-runtime world, no selectable role, or a draft that has not been reviewed. Retrying the same request will disagree again. A Story Package publish may also answer 409 with body `code: \"story_contract_v2_write_disabled\"` — the package carries `initialRelations` but this environment is not yet permitted to write contract v2; the field is never dropped to let the publish succeed as v1. A publish carrying `series` may likewise answer `code: \"story_contract_v3_write_disabled\"` — contract v3, same rule, same refusal to drop the field. A publish declaring a `public` flag may answer `code: \"story_contract_v4_write_disabled\"` — contract v4, same rule. An episode-scoped script draft may answer 409 with `code: \"episode_history_incomplete\"` — the beats that episode declares could not all be read, and a draft would claim a range it does not contain. A turn or a deliberation on a finished episode answers 409 `code: \"episode_ended\"`, and starting the next episode answers 409 with one of `episode_not_ended`, `series_complete` or `not_a_serial` — each names a different thing to go and do. Story Package CREATION may answer 409 with `code: \"idempotency_key_reused\"` — the supplied `clientRequestId` already created a package with DIFFERENT content; retrying an interrupted creation must re-send the same key AND the same body. Selecting a deliberation candidate may answer 409 with `code: \"stale_deliberation\"` — the world moved since the candidates were produced, and nothing was spent or written; deliberate again. A deliberation may also answer 409 with one of `deliberation_disabled` (not switched on in this environment), `deliberation_uncalibrated` (the token/latency budget has not been measured for this deployment), `deliberation_not_enabled` (this world’s published revision does not declare it) or `deliberation_candidates_exceeded` — each names a different thing to go and do, and all four are refused before any model call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Body exceeds the endpoint cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The request was understood and its effects were refused.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited — the response carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"503":{"description":"Storage or a dependency was unavailable. Retry the same request.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}}}}