{"openapi":"3.1.0","info":{"title":"Sleeper Hit Studio Story API","summary":"Programmatic story generation, source-grounded planning, and durable artifact delivery for AI agents and enterprise integrations.","description":"Authenticated B2B API for programmatic story generation. Customer API keys, scopes, idempotent POSTs, keyed rate limiting, and Studio Credit accounting are stable today. Source ingestion, StoryPlan jobs, artifacts, and webhooks are planned follow-on phases — discover current availability through GET /api/v1/capabilities before integrating a flow.  Authentication: pass an API key as `Authorization: Bearer sh_<prefix>_<secret>`. Errors use a stable envelope `{ error: { code, message, requestId } }` with the codes listed under components.schemas.ErrorCode.","version":"1.0.0-foundation","contact":{"name":"Sleeper Hit Studio","url":"https://sleeperhit.studio"}},"servers":[{"url":"https://docs.sleeperhit.studio/api/v1","description":"Production"}],"security":[{"BearerApiKey":[]}],"tags":[{"name":"Discovery","description":"Machine-readable capability and credit metadata used to plan integration flows."},{"name":"Credits","description":"Studio Credit balance and quota for the authenticated account."},{"name":"Projects","description":"Durable workspaces that own ingested sources and generated artifacts."},{"name":"Sources","description":"Source packs attached to a project: inline text/markdown, URL fetch, PDF extraction."},{"name":"Plans","description":"Canonical source-grounded StoryPlan: thesis, journey beats, visual + audio system, per-artifact render intents, deterministic quote."},{"name":"Jobs","description":"Queued artifact-generation jobs against an APPROVED StoryPlan: credit reserve/settle, status polling, cancellation."},{"name":"Artifacts","description":"Durable artifact outputs. The table read returns a share token + theater URL + on-demand live-audio URL. Post-creation: refine in-place (POST /refine, scope artifact:publish, 8 credits) or finalize to a durable MP3 (POST /finalize mode=audio, 6 credits) or MP4 (mode=video, 12 credits). The artifact id and share URLs are stable across refines."}],"paths":{"/capabilities":{"get":{"operationId":"getCapabilities","tags":["Discovery"],"summary":"Discover supported sources, artifacts, scopes, and documentation surfaces.","description":"Returns a machine-readable manifest so agent clients can adapt to current feature availability without hardcoded assumptions. Authenticated to keep the API surface uniformly keyed and to attribute usage for capacity planning.","security":[{"BearerApiKey":["story:read"]}],"responses":{"200":{"description":"Capability manifest.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Capabilities"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/agent-guidance":{"get":{"operationId":"getAgentGuidance","tags":["Discovery"],"summary":"Read backend-managed agent workflow guidance.","description":"Returns the current admin-editable agent guidance sidecar for Story API, MCP, and CLI clients, including MCP server instructions plus tool/resource titles and descriptions. Agents should read this before planning, refining, mutating audio, or finalizing table-read artifacts.","security":[{"BearerApiKey":["story:read"]}],"responses":{"200":{"description":"Backend-managed agent guidance.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentGuidance"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/credits":{"get":{"operationId":"getCredits","tags":["Credits"],"summary":"Get Studio Credit balance for the authenticated account.","description":"Read-only summary of the current Studio Credit balance, monthly bucket allocation/use, and perpetual credit balance. Use this before submitting expensive generation jobs so agents can short-circuit with `insufficient_credits` guidance instead of triggering reservations.","security":[{"BearerApiKey":["credits:read"]}],"responses":{"200":{"description":"Credit summary.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditsSummary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-projects":{"get":{"operationId":"listStoryProjects","tags":["Projects"],"summary":"List story projects owned by the authenticated account.","description":"Returns a cursor-paginated list of non-deleted story projects, newest first. Use `cursor` from the previous response to page.","security":[{"BearerApiKey":["story:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Max items to return per page (default 25, max 100)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque cursor from a previous response."}],"responses":{"200":{"description":"Paginated project list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryProjectList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createStoryProject","tags":["Projects"],"summary":"Create a new story project workspace.","description":"Creates an empty project. Requires `Idempotency-Key` for safe retries.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryProjectCreate"}}}},"responses":{"200":{"description":"Created project envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryProjectEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-projects/{projectId}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"getStoryProject","tags":["Projects"],"summary":"Read a story project by id.","security":[{"BearerApiKey":["story:read"]}],"responses":{"200":{"description":"Project envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryProjectEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"patch":{"operationId":"updateStoryProject","tags":["Projects"],"summary":"Update a story project.","description":"Partial update. Only the supplied fields are modified. Pass `archived: true` to archive (or `false` to unarchive).","security":[{"BearerApiKey":["story:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryProjectPatch"}}}},"responses":{"200":{"description":"Updated project envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryProjectEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"deleteStoryProject","tags":["Projects"],"summary":"Soft-delete a story project.","description":"The project is marked deleted and excluded from list responses. Subsequent reads return `project_not_found`.","security":[{"BearerApiKey":["story:write"]}],"responses":{"200":{"description":"Deletion ack.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionAck"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-projects/{projectId}/series-bible":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"getProjectSeriesBible","tags":["Projects"],"summary":"Read the project Series Bible.","description":"Returns the durable project-level canon and continuity document shared by the web UI, Story API, CLI, and MCP.","security":[{"BearerApiKey":["story:read"]}],"responses":{"200":{"description":"Series Bible document envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesBibleEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"patch":{"operationId":"saveProjectSeriesBible","tags":["Projects"],"summary":"Save the project Series Bible.","description":"Stores caller-authored project canon, episode map, character roster, style, and audio direction. This is the same project document the web UI edits and the MCP/CLI tools read.","security":[{"BearerApiKey":["story:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesBiblePatch"}}}},"responses":{"200":{"description":"Saved Series Bible document envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesBibleEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"generateProjectSeriesBible","tags":["Projects"],"summary":"Generate or refresh the project Series Bible.","description":"Synthesizes the Series Bible from the project, sources, plans, scripts, existing document, and optional instructions using the DB sidecar `story_api_series_bible`. Requires `Idempotency-Key`.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesBibleGenerateRequest"}}}},"responses":{"200":{"description":"Generated Series Bible document envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesBibleEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-projects/{projectId}/sources":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"listStorySources","tags":["Sources"],"summary":"List sources attached to a project.","security":[{"BearerApiKey":["source:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Max items per page (default 50, max 200)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque cursor from a previous response."}],"responses":{"200":{"description":"Paginated source list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorySourceList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createStorySource","tags":["Sources"],"summary":"Attach a source to a project.","description":"Accepts inline `text`, `markdown`, URL, and PDF sources. Requires `Idempotency-Key` for safe retries. Check `/api/v1/capabilities.sources.types` before sending a type.","security":[{"BearerApiKey":["source:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorySourceCreate"}}}},"responses":{"200":{"description":"Created source envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorySourceEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/SourceTooLarge"},"422":{"$ref":"#/components/responses/SourceTypeUnsupported"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-projects/{projectId}/story-plans":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"listStoryPlans","tags":["Plans"],"summary":"List StoryPlans for a project.","security":[{"BearerApiKey":["story:read"]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated plan list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryPlanList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createStoryPlan","tags":["Plans"],"summary":"Create a StoryPlan and enqueue its async generation.","description":"Validates target + artifactRequests + sourceIds synchronously, including source ownership and status READY, persists the plan as `PENDING`, and enqueues the planner backed by DB sidecar `story_api_plan`. Requires `Idempotency-Key` for safe retries. The response includes a preliminary quote computed from the artifact requests; the worker overwrites it with a plan-aware quote once the StoryPlan envelope is generated. Table-read plans accept `creativeBrief`, return a generated `scriptBlueprint`, cannot use `autoApprove`, and are limited to one table_read script/installment per plan. Table-read artifact requests may set `narrationPolicy` (`auto | include | suppress`) to make narrator/no-narrator a user-level creation choice instead of implying it from mode.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryPlanCreate"}}}},"responses":{"200":{"description":"Created plan envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryPlanEnvelopeResponse"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-plans/{planId}":{"parameters":[{"$ref":"#/components/parameters/StoryPlanId"}],"get":{"operationId":"getStoryPlan","tags":["Plans"],"summary":"Read a StoryPlan by id.","security":[{"BearerApiKey":["story:read"]}],"responses":{"200":{"description":"Plan envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryPlanEnvelopeResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-plans/{planId}/approve":{"parameters":[{"$ref":"#/components/parameters/StoryPlanId"}],"post":{"operationId":"approveStoryPlan","tags":["Plans"],"summary":"Approve a generated StoryPlan.","description":"Transitions the plan to `APPROVED`. Valid only from `REQUIRES_APPROVAL` or `READY`. Requires `Idempotency-Key`; re-approving an already-approved plan is a no-op.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Approved plan.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryPlanEnvelopeResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StoryPlanStateInvalid"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-plans/{planId}/reject":{"parameters":[{"$ref":"#/components/parameters/StoryPlanId"}],"post":{"operationId":"rejectStoryPlan","tags":["Plans"],"summary":"Reject a generated StoryPlan.","description":"Transitions the plan to `REJECTED`. Valid only from `REQUIRES_APPROVAL` or `READY`. Pass an optional `reason` in the body. Requires `Idempotency-Key`.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"reason":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Rejected plan.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryPlanEnvelopeResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StoryPlanStateInvalid"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-jobs":{"get":{"operationId":"listStoryJobs","tags":["Jobs"],"summary":"List story jobs owned by the authenticated account.","description":"Cursor-paginated list of non-deleted story jobs, newest first. Filter with optional `projectId` and `storyPlanId` query params.","security":[{"BearerApiKey":["story:read"]}],"parameters":[{"name":"projectId","in":"query","required":false,"schema":{"type":"string"},"description":"Restrict to jobs in this project."},{"name":"storyPlanId","in":"query","required":false,"schema":{"type":"string"},"description":"Restrict to jobs for this plan."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"description":"Max items per page (default 25, max 100)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque cursor from a previous response."}],"responses":{"200":{"description":"Paginated job list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryJobList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createStoryJob","tags":["Jobs"],"summary":"Create an artifact-generation job against an APPROVED StoryPlan.","description":"Requires the referenced plan to be `APPROVED`. For table_read, the plan must include a reviewed `scriptBlueprint` and the job may contain only one table_read artifact request. Reserves Studio Credits for the artifact line items, persists the job, and enqueues the async runner. Requires `Idempotency-Key`. Returns `insufficient_credits` if the reservation cannot be covered. `artifactRequests` defaults to the plan's own requests when omitted; table-read overrides may include `narrationPolicy` (`auto | include | suppress`).","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryJobCreate"}}}},"responses":{"200":{"description":"Created job envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryJobEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StoryPlanStateInvalid"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-jobs/{jobId}":{"parameters":[{"$ref":"#/components/parameters/StoryJobId"}],"get":{"operationId":"getStoryJob","tags":["Jobs"],"summary":"Read a story job by id, including its artifacts.","description":"Poll this to follow status + progress. Terminal states: `READY`, `PARTIAL`, `FAILED`, `CANCELED`. Each artifact in the response carries its manifest URLs once `status: READY`.","security":[{"BearerApiKey":["story:read"]}],"responses":{"200":{"description":"Job envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryJobEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/StoryJobNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-jobs/{jobId}/cancel":{"parameters":[{"$ref":"#/components/parameters/StoryJobId"}],"post":{"operationId":"cancelStoryJob","tags":["Jobs"],"summary":"Cancel a story job and release its reserved credits.","description":"Valid only while the job is `PENDING` or `RESERVED`. Releases any reserved (not-yet-settled) credit line items. Requires `Idempotency-Key`; canceling an already-canceled job is a no-op.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Canceled job envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryJobEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/StoryJobNotFound"},"409":{"$ref":"#/components/responses/StoryJobStateInvalid"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-jobs/{jobId}/artifacts":{"parameters":[{"$ref":"#/components/parameters/StoryJobId"}],"get":{"operationId":"listStoryJobArtifacts","tags":["Artifacts"],"summary":"List the artifacts produced by a story job.","security":[{"BearerApiKey":["artifact:read"]}],"responses":{"200":{"description":"Artifact list.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryArtifactListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/StoryJobNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"get":{"operationId":"getStoryArtifact","tags":["Artifacts"],"summary":"Read a single artifact and its manifest.","description":"For a `table_read`, the manifest carries the share token, theater URL, on-demand live-audio start URL, refine versioning, and finalize state. Pass `?revision=N` to fetch a prior revision (v1 = initial generation; each refine increments). `currentVersion` and `refineCount` are always the current values regardless of the revision queried. Poll this endpoint for `manifest.audio.finalize.status` (audio finalize) and `manifest.video.status` (video finalize/render).","security":[{"BearerApiKey":["artifact:read"]}],"parameters":[{"name":"revision","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Revision number to fetch. Omit for the current (latest) revision. v1 is the initial generation; each POST /artifacts/{id}/refine increments the version."}],"responses":{"200":{"description":"Artifact envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryArtifactEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/script":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"get":{"operationId":"getArtifactScript","tags":["Artifacts"],"summary":"Read actual script content for a table read artifact.","description":"Returns the parsed backing entries used by theater playback, music, SFX, and coverage. Use `scope=page`, `scope=scene`, `scope=character`, or `scope=range` for focused reads. Terminal agents should call this before answering questions about what is on a page, which scene is strongest, or what a character says. Scope: `artifact:read`.","security":[{"BearerApiKey":["artifact:read"]}],"parameters":[{"name":"scope","in":"query","required":false,"schema":{"type":"string","enum":["all","page","scene","character","range"]},"description":"Selection scope. Defaults to `all`."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Required when `scope=page`."},{"name":"sceneIndex","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Required when `scope=scene`. Scene indexes are zero-based and listed in the response `scenes` array."},{"name":"character","in":"query","required":false,"schema":{"type":"string"},"description":"Required when `scope=character`. Use names from the response `characters` array."},{"name":"startEntry","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Required when `scope=range`."},{"name":"endEntry","in":"query","required":false,"schema":{"type":"integer","minimum":0},"description":"Required when `scope=range`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500},"description":"Maximum selected entries returned. Default 250, max 500."}],"responses":{"200":{"description":"Script content envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtifactScriptContentEnvelope"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/render-video":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"renderArtifactVideo","tags":["Artifacts"],"summary":"Trigger the opt-in MP4 render for a table read artifact.","description":"Queues the Remotion MP4 export for a `table_read` artifact. This output is never produced by default; calling it incurs a separate Studio Credit charge. Returns `artifact_not_ready` if the underlying share link does not exist yet. Requires `Idempotency-Key`. The same render is also reachable via POST /artifacts/{id}/finalize with `{ mode: \"video\" }`. Scope: `artifact:publish` (billable + mutating).","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Render acknowledgement.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtifactRenderAck"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"422":{"$ref":"#/components/responses/ArtifactGenerationFailed"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/refine":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"refineArtifact","tags":["Artifacts"],"summary":"Conversationally revise a table read artifact in place.","description":"Submits a free-form instruction that rewrites the screenplay and/or re-grounds the StoryPlan, producing a NEW REVISION of the SAME artifact. The artifact id, share token, and all three player URLs (theaterUrl / theaterFullscreenUrl / audio.liveUrl) remain STABLE — no re-embed, no new links.  `scope` controls the rewrite strategy: - `\"auto\"` (default) — the model self-routes; most instructions hit `screenplay`. - `\"screenplay\"` — rewrites the screenplay and recasts voices in place; faster. - `\"plan\"` — re-grounds the StoryPlan against sources then re-adapts; use for major structural changes.  Returns `{ storyJob }` immediately. Poll GET /story-jobs/{jobId} until terminal (READY / FAILED), exactly like a standard artifact generation job. Once complete, GET /artifacts/{id} returns the updated `currentVersion` and `refineCount`. Fetch a prior revision with GET /artifacts/{id}?revision=N (v1 = initial generation).  Reserves TABLE_READ_REFINE (8 Studio Credits) at job create time. Requires `Idempotency-Key`.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["instruction"],"additionalProperties":false,"properties":{"instruction":{"type":"string","minLength":1,"description":"Free-form revision instruction (e.g. \"rename the host to Dana and drop the narrator; make the skeptic warmer\")."},"scope":{"type":"string","enum":["auto","screenplay","plan"],"description":"`auto` (default) lets the model self-route. `screenplay` rewrites cast + screenplay only. `plan` re-grounds against sources first."}}}}}},"responses":{"200":{"description":"Refine job created. Poll the returned storyJob id until terminal.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["storyJob"],"additionalProperties":false,"properties":{"storyJob":{"$ref":"#/components/schemas/StoryJob"}}}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/voice":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"recastArtifactVoice","tags":["Artifacts"],"summary":"Recast a single character's voice on a table read artifact.","description":"Reassigns one character's voice on the table read backing this artifact, in place. The artifact id, share token, and all player URLs remain STABLE — no new revision, no charge.  Body: `{ character, voiceId, voiceName, gender?, provider? }`. `provider` is the TTS provider for the voice (e.g. `cartesia`, `openai`, `hume`); omit to keep the existing provider. Returns the updated `voiceMap` (character → voice). The current cast is also readable on `manifest.audio.voiceMap` via GET /artifacts/{id}.  Requires `Idempotency-Key`. Scope: `artifact:publish`.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["character","voiceId","voiceName"],"additionalProperties":false,"properties":{"character":{"type":"string","minLength":1,"description":"Character name as it appears in the cast (e.g. \"NARRATOR\")."},"voiceId":{"type":"string","minLength":1,"description":"Provider voice id to assign."},"voiceName":{"type":"string","minLength":1,"description":"Human-readable voice name (shown in the cast)."},"gender":{"type":"string","description":"Optional voice gender hint. Preserves the existing value when omitted."},"provider":{"type":"string","description":"Optional TTS provider for this voice (e.g. cartesia, openai, hume). Preserves the existing value when omitted."}}}}}},"responses":{"200":{"description":"Voice recast. Returns the updated cast as `voiceMap`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["artifactId","character","voiceMap"],"additionalProperties":false,"properties":{"artifactId":{"type":"string"},"character":{"type":"string"},"voiceMap":{"type":"object","additionalProperties":true,"description":"Full updated cast: character → { voiceId, voiceName, gender, provider }."}}}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/avatar":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"recastArtifactAvatar","tags":["Artifacts"],"summary":"Regenerate a single character's avatar portrait on a table read artifact.","description":"Re-renders ONE character's avatar portrait on the table read backing this `table_read` artifact, in place. The artifact id, share token, and all player URLs remain STABLE — no new revision, no charge, like a voice recast.  Body: `{ character, refinement?, style?, referenceImageUrl? }`. `refinement` is a freeform art-direction nudge; `style` overrides the resolved avatar style for this one render; `referenceImageUrl` seeds the image model from an already-uploaded image. Async + queue-backed: enqueues a `regenerate_avatar` job and returns `{ character, status, chatToolJobId }`. Poll GET /artifacts/{artifactId}/cast and watch that character's `avatarStatus` (`queued` → `rendering` → `ready`).  Requires `Idempotency-Key`. Scope: `artifact:publish`.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvatarRecastRequest"}}}},"responses":{"200":{"description":"Avatar regeneration dispatched. Poll GET /artifacts/{artifactId}/cast until the character's avatarStatus is ready.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvatarRecastResponse"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/cast":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"updateArtifactCast","tags":["Artifacts"],"summary":"Batch-update the cast (voices + avatars) on a table read artifact.","description":"Batch-updates the cast on the table read backing this `table_read` artifact in one call, in place. Each `entries` item targets one character and may reassign its voice (`voiceId` / `voiceName` / `gender` / `provider`), request an avatar re-render (`regenerateAvatar: true`, with an optional `avatarRefinement` art-direction nudge), or both. A top-level `avatarStyle` restyles ALL portraits (re-renders every character against the new art-direction). Provide at least one `entries` item OR a top-level `avatarStyle` — a body with neither is rejected as `validation_failed`. Voice reassignments apply immediately. Avatar work is async + queue-backed: the response returns the updated `voiceMap` plus one avatar op `{ character, status, chatToolJobId }` per queued render — poll GET /artifacts/{artifactId}/cast until each character's `avatarStatus` reaches `ready`. No new revision — the artifact id and share URLs stay stable, like a voice recast (no charge).  Requires `Idempotency-Key`. Scope: `artifact:publish`.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CastBatchRequest"}}}},"responses":{"200":{"description":"Cast updated. Returns the merged voiceMap plus the in-flight avatar ops to poll.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CastBatchResponse"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"operationId":"getArtifactCast","tags":["Artifacts"],"summary":"Read the enriched cast for a table read artifact.","description":"Returns the enriched cast for the read backing this artifact: each character's assigned voice (`voiceId` / `voiceName` / `gender` / `provider`) merged with its avatar state (`avatarUrl` / `avatarStyle` / `avatarStatus`), plus the artifact's resolved top-level `avatarStyle`. `avatarStatus` is `none` (no avatar), `queued` / `rendering` (a render is in flight), `ready`, or `failed` — poll this endpoint after recast_avatar / update_cast until in-flight renders reach `ready`. The cast is assigned automatically when the table read becomes performable; an empty `cast` means it is not performable yet (poll GET /artifacts/{artifactId}).  Scope: `artifact:read`.","security":[{"BearerApiKey":["artifact:read"]}],"responses":{"200":{"description":"Enriched cast envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CastEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/coverage":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"generateArtifactCoverage","tags":["Artifacts"],"summary":"Generate professional script coverage for a table read artifact.","description":"Runs a queued, async coverage analysis of the screenplay backing this `table_read` artifact: logline, premise, structure, characters, dialogue, pacing, market fit, an overall score, top fixes, and comparables.  Returns `{ reportId, status }` immediately (`status: \"generating\"`). Poll GET /artifacts/{artifactId}/coverage (optionally `?reportId=<id>`) until `status` is `complete` or `failed`.  Coverage produces a separate report — it does NOT change the artifact, its revision, or its share URLs. Free-plan accounts may generate one coverage report; additional reports require an upgrade (returned as `validation_failed`).  Requires `Idempotency-Key`. Scope: `story:write`.","security":[{"BearerApiKey":["story:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"focusPrompt":{"type":"string","maxLength":2000,"description":"Optional free-form lens the analysis should weave throughout (e.g. \"focus on whether the second act earns its midpoint\")."}}}}}},"responses":{"200":{"description":"Coverage generation queued. Poll GET /artifacts/{artifactId}/coverage until terminal.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageDispatchAck"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"operationId":"getArtifactCoverage","tags":["Artifacts"],"summary":"Read the coverage report for a table read artifact.","description":"Returns the latest coverage report for the screenplay backing this artifact, or a specific report via `?reportId=<id>`. The report carries a `status` field (`generating` → `complete` / `failed`) so you can poll it to terminal. When `status: complete`, `report` carries the structured coverage payload; it is `null` while generating or on failure. Returns `{ coverage: null }` when no coverage has been requested yet.  Scope: `artifact:read`.","security":[{"BearerApiKey":["artifact:read"]}],"parameters":[{"name":"reportId","in":"query","required":false,"schema":{"type":"string"},"description":"Read a specific coverage report by id. Omit for the latest."}],"responses":{"200":{"description":"Coverage report envelope (`coverage` may be null).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageReportEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/voice-modification":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"modifyArtifactVoice","tags":["Artifacts"],"summary":"Apply a voice effect to a range of lines on a table read artifact.","description":"Applies a voice effect (currently only `autotune`) to a contiguous range of dialogue entries `[startEntryIndex..endEntryIndex]` on the live read backing a `table_read` artifact. No new revision — the artifact id and share URLs stay stable, like a voice recast. The autotune recipe (`params`) is optional and partial: any omitted field falls back to the proven defaults (key `D`, scale `minpent`, strength `1.0`, smooth `1`, reverb `chapel`). Async + queued: returns `{ artifactId, modificationId, status }`; the rendered audio is projected onto the read once the modification reaches `status: \"ready\"`.  Requires `Idempotency-Key`. Scope: `artifact:publish`.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceModificationRequest"}}}},"responses":{"200":{"description":"Voice modification dispatched.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceModificationResponse"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/music":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"generateArtifactMusic","tags":["Artifacts"],"summary":"Generate adaptive music for a table read artifact.","description":"(Re)generates per-scene adaptive music for the read backing this `table_read` artifact, or updates one scene music direction when `sceneIndex` is provided. No new revision — the artifact id and share URLs stay stable, like a voice recast. Optionally tune how much of the read carries music via `musicCoveragePercent` (a fraction 0..1 or a whole percent 0..100; clamped into [0,1]). For scene-level edits, send `{ sceneIndex, prompt?, summary?, enabled?, weight? }`, e.g. \"make scene 2 heavier bass\"; the edit is applied immediately and any finalized MP3/video becomes stale until explicit finalize. Full regeneration is async + queued: returns `{ status: \"generating\" }`. Scene edits return `{ status: \"ready\" }`. Poll GET /artifacts/{artifactId}/music until `music.status` is `ready`.  Requires `Idempotency-Key`. Scope: `artifact:publish`.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MusicGenerateRequest"}}}},"responses":{"200":{"description":"Adaptive music generation dispatched or scene music direction updated.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MusicGenerateResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"operationId":"getArtifactMusic","tags":["Artifacts"],"summary":"Read adaptive music status for a table read artifact.","description":"Returns adaptive music readiness for the read backing this artifact. `music.status` is `none` (no active music, including intentional 0% coverage), `generating` (scoring is still in progress), or `ready` (at least one scene carries music).  Scope: `artifact:read`.","security":[{"BearerApiKey":["artifact:read"]}],"responses":{"200":{"description":"Adaptive music status envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MusicStatusEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/sfx":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"manageArtifactSfx","tags":["Artifacts"],"summary":"Add or remove sound-effect cues for a table read artifact.","description":"Mutates timed sound-effect cues on the live read backing a `table_read` artifact. Use `{ op: \"add\", entryIndex, label, prompt, volume?, triggerOffset? }` to add a cue. Use `{ op: \"remove\", id }` to remove a cue. This does not create a new artifact revision and does not change share URLs.  Scope: `artifact:publish`. Idempotency-Key is required.","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"op":{"type":"string","enum":["add","update","remove"],"default":"add"},"entryIndex":{"type":"integer","minimum":0,"description":"Dialogue entry index for add or update operations."},"label":{"type":"string","minLength":1},"prompt":{"type":"string","minLength":1},"volume":{"type":"number","minimum":0,"maximum":1},"triggerOffset":{"type":"string","enum":["before","with","after"]},"regenerate":{"type":"boolean","description":"For update operations, regenerate cue audio from the revised prompt."},"id":{"type":"string","description":"Cue id for update or remove operations."}}}}}},"responses":{"200":{"description":"SFX mutation result.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SfxMutationResponse"}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"operationId":"listArtifactSfx","tags":["Artifacts"],"summary":"List sound-effect cues for a table read artifact.","description":"Returns timed sound-effect cues on the read backing this artifact. Scope: `artifact:read`.","security":[{"BearerApiKey":["artifact:read"]}],"responses":{"200":{"description":"SFX cue list envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SfxListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/artifacts/{artifactId}/finalize":{"parameters":[{"$ref":"#/components/parameters/StoryArtifactId"}],"post":{"operationId":"finalizeArtifact","tags":["Artifacts"],"summary":"Durably render a table read artifact as an MP3 or MP4.","description":"Initiates a durable render of a `table_read` artifact alongside the live share.  `mode: \"audio\"` (default, 6 credits via TABLE_READ_FINALIZE_AUDIO) reserves credits and queues a durable full-mix MP3 render (voices, adaptive music, and SFX) regardless of whether a live Pipecat/Daily recording exists. When a completed recording is present, the worker freezes that take as the voice layer; otherwise it synthesizes the voice track headlessly from TTS using the same producer the MP4 export uses. Poll GET /artifacts/{id}: `manifest.audio.finalize.status` progresses through `queued → rendering → uploading → complete` (or `failed` with `manifest.audio.finalize.error`). On `complete`, `manifest.audio.recordingUrl` (full-mix MP3 download URL) and `manifest.audio.recordingDurationMs` are populated.  `mode: \"video\"` (12 credits via TABLE_READ_FINALIZE_VIDEO) queues the Remotion MP4 export (same render as POST /artifacts/{id}/render-video). Poll GET /artifacts/{id}: `manifest.video.status` and `manifest.video.url` on completion.  Replay-safe: re-calling finalize when already complete or in-flight returns the current status without re-charging credits.  Requires `Idempotency-Key`. Scope: `artifact:publish` (billable + mutating).","security":[{"BearerApiKey":["artifact:publish"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"mode":{"type":"string","enum":["audio","video"],"description":"`audio` (default) reserves credits and queues a durable full-mix MP3 (voices + adaptive music + SFX); the worker uses a completed Pipecat/Daily recording when present, otherwise synthesizes the voice track from TTS. `video` queues the Remotion MP4 export."}}}}}},"responses":{"200":{"description":"Finalize accepted. For audio, poll GET /artifacts/{id} for `manifest.audio.finalize.status`. For video, poll `manifest.video.status`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["artifactId","mode","finalize"],"additionalProperties":false,"properties":{"artifactId":{"type":"string"},"mode":{"type":"string","enum":["audio","video"]},"finalize":{"type":"object","additionalProperties":true,"description":"Current finalize state at the time of the call. For audio: `{ status }`. For video: render acknowledgement object."}}}}}},"400":{"$ref":"#/components/responses/ValidationFailed"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/ArtifactNotFound"},"409":{"$ref":"#/components/responses/ArtifactNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/story-projects/{projectId}/sources/{sourceId}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/SourceId"}],"get":{"operationId":"getStorySource","tags":["Sources"],"summary":"Read a source by id.","security":[{"BearerApiKey":["source:read"]}],"responses":{"200":{"description":"Source envelope.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorySourceEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"deleteStorySource","tags":["Sources"],"summary":"Soft-delete a source.","description":"The source is marked deleted, its extracted text preview is cleared, and it stops appearing in list responses.","security":[{"BearerApiKey":["source:write"]}],"responses":{"200":{"description":"Deletion ack.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletionAck"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"components":{"securitySchemes":{"BearerApiKey":{"type":"http","scheme":"bearer","bearerFormat":"Sleeper Hit API key (sh_<prefix>_<secret>)","description":"Production-only customer API key. Created and revoked at https://sleeperhit.studio/dashboard/api. Tokens are shown once; store them in your secret manager."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Customer-generated stable identifier for safe retries of idempotent POST requests."},"ProjectId":{"name":"projectId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"description":"Story project id (cuid)."},"SourceId":{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"description":"Story source id (cuid)."},"StoryPlanId":{"name":"planId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"description":"Story plan id (cuid)."},"StoryJobId":{"name":"jobId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"description":"Story job id (cuid)."},"StoryArtifactId":{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"description":"Story artifact id (cuid)."}},"headers":{"XRequestId":{"description":"Per-request identifier. Echo this back when reporting integration issues.","schema":{"type":"string","format":"uuid"}},"RateLimitLimit":{"description":"Maximum requests permitted in the current window for this API key + IP + route family.","schema":{"type":"integer","minimum":1}},"RateLimitRemaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer","minimum":0}},"RateLimitReset":{"description":"Seconds until the current rate-limit window resets.","schema":{"type":"integer","minimum":1}}},"responses":{"Unauthorized":{"description":"Missing, malformed, revoked, or expired API key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Forbidden":{"description":"API key is valid but lacks the scope, is blocked by IP allowlist, or the account is disabled.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"Rate limit for this API key + IP + route family was exceeded.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"description":"Seconds the client should wait before retrying.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InternalError":{"description":"The API failed unexpectedly. Safe to retry with the same Idempotency-Key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"ValidationFailed":{"description":"Request payload failed validation. The error message names the offending field.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"NotFound":{"description":"The named project or source does not exist or is owned by another account.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"SourceTooLarge":{"description":"Source content is larger than the documented inline limit.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"SourceTypeUnsupported":{"description":"Requested source type is not yet supported. See `/api/v1/capabilities.sources.types`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"StoryPlanStateInvalid":{"description":"The plan is in a state that does not allow the requested operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"StoryJobNotFound":{"description":"The story job does not exist, was deleted, or is owned by another account.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"StoryJobStateInvalid":{"description":"The job is in a state that does not allow the requested operation (e.g. canceling a terminal job).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"ArtifactNotFound":{"description":"The story artifact does not exist or is owned by another account.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"ArtifactNotReady":{"description":"The artifact is not in a state where this operation is valid yet (e.g. requesting an MP4 render before the share link exists).","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"ArtifactGenerationFailed":{"description":"The artifact adapter failed to produce a shareable output.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"InsufficientCredits":{"description":"Not enough Studio Credits to reserve this job. See `details.required` and `details.available`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/XRequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"schemas":{"ErrorCode":{"type":"string","enum":["authentication_required","invalid_api_key","api_key_revoked","api_key_expired","account_disabled","ip_not_allowed","insufficient_scope","rate_limited","idempotency_key_required","idempotency_conflict","validation_failed","project_not_found","source_not_found","source_type_unsupported","source_too_large","source_fetch_failed","story_plan_not_found","story_plan_state_invalid","story_plan_failed","story_job_not_found","story_job_state_invalid","artifact_not_found","artifact_not_ready","artifact_generation_failed","insufficient_credits","internal_error"],"description":"Stable machine-readable error code. Match on this rather than the human message."},"ErrorEnvelope":{"type":"object","required":["error"],"additionalProperties":false,"properties":{"error":{"type":"object","required":["code","message","requestId"],"additionalProperties":false,"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable error description. Subject to change; do not pattern-match."},"requestId":{"type":"string","format":"uuid","description":"Per-request identifier mirrored in the X-Request-Id response header."}}}}},"Scope":{"type":"string","enum":["story:read","story:write","source:read","source:write","artifact:read","artifact:publish","credits:read","webhook:write"],"description":"OAuth-style scope encoded on a customer API key."},"Capabilities":{"type":"object","required":["version","status","auth","projects","sources","artifacts","jobs","docs"],"additionalProperties":false,"properties":{"version":{"type":"string","const":"v1"},"status":{"type":"string","const":"foundation","description":"Lifecycle marker for the API surface. `foundation` means production-stable identity, auth, credits, and discovery — generation features are rolling out incrementally."},"auth":{"type":"object","required":["schemes","scopes","supportsIdempotency"],"properties":{"schemes":{"type":"array","items":{"type":"string","enum":["bearer_api_key"]}},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"supportsIdempotency":{"type":"boolean","const":true}}},"projects":{"type":"object","required":["available","crudScopes","documents"],"properties":{"available":{"type":"boolean"},"crudScopes":{"type":"object","required":["read","write"],"properties":{"read":{"$ref":"#/components/schemas/Scope"},"write":{"$ref":"#/components/schemas/Scope"}}},"documents":{"type":"object","required":["seriesBible"],"additionalProperties":false,"properties":{"seriesBible":{"type":"object","required":["available","scopes","promptSystem","sidecar","note"],"additionalProperties":false,"properties":{"available":{"type":"boolean"},"scopes":{"type":"object","required":["read","write","generate"],"additionalProperties":false,"properties":{"read":{"$ref":"#/components/schemas/Scope"},"write":{"$ref":"#/components/schemas/Scope"},"generate":{"$ref":"#/components/schemas/Scope"}}},"promptSystem":{"type":"string","const":"SidecarPrompt"},"sidecar":{"type":"string","const":"story_api_series_bible"},"note":{"type":"string"}}}}}}},"sources":{"type":"object","required":["types","retentionPolicies","note"],"properties":{"types":{"type":"array","items":{"type":"object","required":["type","availability"],"properties":{"type":{"type":"string","enum":["text","markdown","url","pdf"]},"availability":{"type":"string","enum":["available","planned"]},"maxInlineBytes":{"type":"integer","minimum":1},"note":{"type":"string"}}}},"retentionPolicies":{"type":"array","items":{"$ref":"#/components/schemas/RetentionPolicy"}},"note":{"type":"string"}}},"artifacts":{"type":"object","additionalProperties":{"type":"object","required":["outputs","availability"],"properties":{"outputs":{"type":"array","items":{"type":"string"}},"availability":{"type":"string","enum":["available","planned"]},"durationSeconds":{"type":"object","required":["min","max"],"properties":{"min":{"type":"integer","minimum":1},"max":{"type":"integer","minimum":1}}},"modes":{"type":"array","items":{"type":"string"},"description":"Supported generation modes for this artifact (e.g. table read `documentary | podcast | drama`)."},"narrationPolicies":{"type":"array","items":{"type":"string","enum":["auto","include","suppress"]},"description":"Supported table-read narrator policies. `auto` lets the plan infer; `include` requests a narrator; `suppress` makes the read speaker-only."},"optInOutputs":{"type":"array","items":{"type":"string"},"description":"Outputs that are never produced by default and must be requested explicitly (e.g. the table read `video` MP4 render)."},"note":{"type":"string"}}}},"jobs":{"type":"object","required":["available","statuses","artifactStatuses","scopes","note"],"properties":{"available":{"type":"boolean"},"statuses":{"type":"array","items":{"$ref":"#/components/schemas/StoryJobStatus"}},"artifactStatuses":{"type":"array","items":{"$ref":"#/components/schemas/StoryArtifactStatus"}},"scopes":{"type":"object","required":["create","read","cancel","artifactRead","renderVideo","refine","recastVoice","modifyVoice","finalize","scriptRead","coverageGenerate","coverageRead","musicGenerate","musicRead","sfxManage","sfxRead"],"properties":{"create":{"$ref":"#/components/schemas/Scope"},"read":{"$ref":"#/components/schemas/Scope"},"cancel":{"$ref":"#/components/schemas/Scope"},"artifactRead":{"$ref":"#/components/schemas/Scope"},"renderVideo":{"$ref":"#/components/schemas/Scope"},"refine":{"$ref":"#/components/schemas/Scope"},"recastVoice":{"$ref":"#/components/schemas/Scope"},"modifyVoice":{"$ref":"#/components/schemas/Scope"},"finalize":{"$ref":"#/components/schemas/Scope"},"scriptRead":{"$ref":"#/components/schemas/Scope"},"coverageGenerate":{"$ref":"#/components/schemas/Scope"},"coverageRead":{"$ref":"#/components/schemas/Scope"},"musicGenerate":{"$ref":"#/components/schemas/Scope"},"musicRead":{"$ref":"#/components/schemas/Scope"},"sfxManage":{"$ref":"#/components/schemas/Scope"},"sfxRead":{"$ref":"#/components/schemas/Scope"}}},"note":{"type":"string"}}},"docs":{"type":"object","required":["agentGuidance","openapi","examples","llms","llmsFull","docsSite","agentIndex","searchIndex","cliCommands","mcpTools","sitemap","robots"],"properties":{"agentGuidance":{"$ref":"#/components/schemas/DocLink"},"openapi":{"$ref":"#/components/schemas/DocLink"},"examples":{"$ref":"#/components/schemas/DocLink"},"llms":{"$ref":"#/components/schemas/DocLink"},"llmsFull":{"$ref":"#/components/schemas/DocLink"},"docsSite":{"$ref":"#/components/schemas/DocLink"},"agentIndex":{"$ref":"#/components/schemas/DocLink"},"searchIndex":{"$ref":"#/components/schemas/DocLink"},"cliCommands":{"$ref":"#/components/schemas/DocLink"},"mcpTools":{"$ref":"#/components/schemas/DocLink"},"sitemap":{"$ref":"#/components/schemas/DocLink"},"robots":{"$ref":"#/components/schemas/DocLink"}}}}},"DocLink":{"type":"object","required":["url","availability"],"additionalProperties":false,"properties":{"url":{"type":"string"},"availability":{"type":"string","enum":["available","planned"]}}},"AgentGuidance":{"type":"object","required":["version","available","promptSystem","source","guidance","mcp"],"additionalProperties":false,"properties":{"version":{"type":"integer","const":1},"available":{"type":"boolean"},"promptSystem":{"type":"string","const":"SidecarPrompt"},"source":{"type":"object","required":["feature","name","sidecarId","currentVersion","updatedAt","interpolatesSnippets"],"additionalProperties":false,"properties":{"feature":{"type":"string","const":"B2B_STORY_API"},"name":{"type":"string","const":"story_api_agent_guidance"},"sidecarId":{"type":["string","null"]},"currentVersion":{"type":["integer","null"]},"updatedAt":{"type":["string","null"],"format":"date-time"},"interpolatesSnippets":{"type":"boolean"}}},"guidance":{"anyOf":[{"type":"object","required":["format","content"],"additionalProperties":false,"properties":{"format":{"type":"string","const":"markdown"},"content":{"type":"string"}}},{"type":"null"}]},"mcp":{"type":"object","required":["available","promptSystem","source","metadata"],"additionalProperties":false,"properties":{"available":{"type":"boolean"},"promptSystem":{"type":"string","const":"SidecarPrompt"},"source":{"type":"object","required":["feature","name","sidecarId","currentVersion","updatedAt","interpolatesSnippets"],"additionalProperties":false,"properties":{"feature":{"type":"string","const":"B2B_STORY_API"},"name":{"type":"string","const":"story_api_mcp_metadata"},"sidecarId":{"type":["string","null"]},"currentVersion":{"type":["integer","null"]},"updatedAt":{"type":["string","null"],"format":"date-time"},"interpolatesSnippets":{"type":"boolean"}}},"metadata":{"anyOf":[{"type":"object","required":["version","serverInstructions","resources","tools"],"additionalProperties":false,"properties":{"version":{"type":"integer","const":1},"serverInstructions":{"type":"string"},"resources":{"type":"object","additionalProperties":{"type":"object","additionalProperties":false,"properties":{"title":{"type":"string"},"description":{"type":"string"}}}},"tools":{"type":"object","additionalProperties":{"type":"object","additionalProperties":false,"properties":{"title":{"type":"string"},"description":{"type":"string"}}}}}},{"type":"null"}]},"message":{"type":"string"}}},"message":{"type":"string"}}},"CreditsSummary":{"type":"object","required":["credits"],"additionalProperties":false,"properties":{"credits":{"type":"object","required":["balance","monthlyBucketAllocated","monthlyBucketUsed","monthlyBucketRemaining","perpetualBalance","cycleStartedAt","cycleExpiresAt"],"additionalProperties":false,"properties":{"balance":{"type":"integer","description":"Total Studio Credits available across monthly and perpetual buckets."},"monthlyBucketAllocated":{"type":"integer","minimum":0},"monthlyBucketUsed":{"type":"integer","minimum":0},"monthlyBucketRemaining":{"type":"integer","minimum":0},"perpetualBalance":{"type":"integer","minimum":0},"cycleStartedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"cycleExpiresAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]}}}}},"RetentionPolicy":{"type":"string","enum":["EXTRACTED_ONLY","STORE_ORIGINAL","METADATA_ONLY"],"description":"How long the API persists source content. `EXTRACTED_ONLY` (default) stores the extracted text and metadata. `STORE_ORIGINAL` keeps the raw bytes too. `METADATA_ONLY` drops extracted text after the digest pass."},"SourceType":{"type":"string","enum":["text","markdown","url","pdf"],"description":"Source payload type. Inline `text` and `markdown` are accepted today; `url` and `pdf` are planned."},"SourceStatus":{"type":"string","enum":["PENDING","EXTRACTING","READY","FAILED"]},"SourceDigestStatus":{"type":"string","enum":["NOT_STARTED","PENDING","RUNNING","READY","FAILED","SKIPPED"],"description":"Lifecycle of the post-extraction LLM digest. `SKIPPED` means the digest could not run (sidecar/provider missing or no extracted text) but the underlying source remains usable."},"StorySourceDigest":{"type":"object","required":["summary","thesis","claims","evidence","gaps","rightsNotes"],"additionalProperties":false,"description":"Structured digest of a single source. Always source-grounded; the model is instructed not to invent claims that the source does not make.","properties":{"summary":{"type":"string","description":"3-6 sentence plain-prose summary."},"thesis":{"type":"string","description":"Single sentence capturing the source's dominant point."},"claims":{"type":"array","maxItems":12,"items":{"type":"object","required":["statement","kind","attribution"],"properties":{"statement":{"type":"string"},"kind":{"type":"string","enum":["fact","estimate","opinion","forecast","quotation","definition"]},"attribution":{"oneOf":[{"type":"string"},{"type":"null"}]}}}},"evidence":{"type":"array","maxItems":20,"items":{"type":"object","required":["claimIndex","support"],"properties":{"claimIndex":{"type":"integer","minimum":0},"support":{"type":"string"}}}},"gaps":{"type":"array","maxItems":10,"items":{"type":"string"}},"rightsNotes":{"type":"array","maxItems":8,"items":{"type":"string"}}}},"StoryProject":{"type":"object","required":["id","name","description","metadata","archivedAt","createdAt","updatedAt"],"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"oneOf":[{"type":"string"},{"type":"null"}]},"metadata":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"archivedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"sourceCount":{"type":"integer","minimum":0}}},"StoryProjectEnvelope":{"type":"object","required":["project"],"additionalProperties":false,"properties":{"project":{"$ref":"#/components/schemas/StoryProject"}}},"StoryProjectList":{"type":"object","required":["projects","nextCursor"],"additionalProperties":false,"properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/StoryProject"}},"nextCursor":{"oneOf":[{"type":"string"},{"type":"null"}]}}},"StoryProjectCreate":{"type":"object","required":["name"],"additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":4000},"metadata":{"type":"object","additionalProperties":true}}},"StoryProjectPatch":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"description":{"oneOf":[{"type":"string","maxLength":4000},{"type":"null"}]},"metadata":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"archived":{"type":"boolean"}}},"SeriesBibleEpisode":{"type":"object","required":["id","label","title","summary","status"],"additionalProperties":false,"properties":{"id":{"type":"string"},"label":{"type":"string","description":"Episode/installment label such as \"Episode 1\" or \"Pilot\"."},"title":{"type":"string"},"summary":{"type":"string"},"status":{"type":"string","description":"Planning state, e.g. planned, drafted, table read ready, complete."}}},"SeriesBibleCharacter":{"type":"object","required":["id","name","role","want","wound","arc","voice","relationships"],"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"want":{"type":"string"},"wound":{"type":"string"},"arc":{"type":"string"},"voice":{"type":"string"},"relationships":{"type":"string"}}},"SeriesBibleEpisodePatch":{"type":"object","additionalProperties":false,"description":"Partial Series Bible episode item accepted by PATCH; the server fills missing fields with normalized defaults.","properties":{"id":{"type":"string"},"label":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"status":{"type":"string"}}},"SeriesBibleCharacterPatch":{"type":"object","additionalProperties":false,"description":"Partial Series Bible character item accepted by PATCH; the server fills missing fields with normalized defaults.","properties":{"id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"want":{"type":"string"},"wound":{"type":"string"},"arc":{"type":"string"},"voice":{"type":"string"},"relationships":{"type":"string"}}},"SeriesBibleContent":{"type":"object","required":["logline","format","genre","audience","tone","premise","seasonArc","themes","visualStyle","audioStyle","writingStyle","worldRules","canonTimeline","openQuestions","notes","episodes","characters"],"additionalProperties":false,"description":"Project-level canon and continuity shared by UI, API, CLI, and MCP.","properties":{"logline":{"type":"string"},"format":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"},"tone":{"type":"string"},"premise":{"type":"string"},"seasonArc":{"type":"string"},"themes":{"type":"string"},"visualStyle":{"type":"string"},"audioStyle":{"type":"string"},"writingStyle":{"type":"string"},"worldRules":{"type":"string"},"canonTimeline":{"type":"string"},"openQuestions":{"type":"string"},"notes":{"type":"string"},"episodes":{"type":"array","items":{"$ref":"#/components/schemas/SeriesBibleEpisode"}},"characters":{"type":"array","items":{"$ref":"#/components/schemas/SeriesBibleCharacter"}}}},"SeriesBibleDocument":{"type":"object","required":["id","projectId","kind","title","content","generatedFrom","currentVersion","createdAt","updatedAt"],"additionalProperties":false,"properties":{"id":{"type":"string"},"projectId":{"type":"string"},"kind":{"type":"string","const":"series_bible"},"title":{"oneOf":[{"type":"string"},{"type":"null"}]},"content":{"$ref":"#/components/schemas/SeriesBibleContent"},"generatedFrom":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"currentVersion":{"type":"integer","minimum":0},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"SeriesBibleEnvelope":{"type":"object","required":["document"],"additionalProperties":false,"properties":{"document":{"$ref":"#/components/schemas/SeriesBibleDocument"}}},"SeriesBibleContentPatch":{"type":"object","additionalProperties":false,"description":"Partial Series Bible content payload accepted by PATCH; the server fills missing fields with their normalized defaults.","properties":{"logline":{"type":"string"},"format":{"type":"string"},"genre":{"type":"string"},"audience":{"type":"string"},"tone":{"type":"string"},"premise":{"type":"string"},"seasonArc":{"type":"string"},"themes":{"type":"string"},"visualStyle":{"type":"string"},"audioStyle":{"type":"string"},"writingStyle":{"type":"string"},"worldRules":{"type":"string"},"canonTimeline":{"type":"string"},"openQuestions":{"type":"string"},"notes":{"type":"string"},"episodes":{"type":"array","items":{"$ref":"#/components/schemas/SeriesBibleEpisodePatch"}},"characters":{"type":"array","items":{"$ref":"#/components/schemas/SeriesBibleCharacterPatch"}}}},"SeriesBiblePatch":{"type":"object","required":["content"],"additionalProperties":false,"properties":{"title":{"type":"string","maxLength":200},"content":{"$ref":"#/components/schemas/SeriesBibleContentPatch"}}},"SeriesBibleGenerateRequest":{"type":"object","additionalProperties":false,"properties":{"instructions":{"type":"string","maxLength":8000}}},"StorySource":{"type":"object","required":["id","projectId","type","label","uri","status","retentionPolicy","byteSize","tokenEstimate","contentHash","extractedTextPreview","extractedObjectKey","rawObjectKey","failureCode","failureMessage","metadata","digest","digestStatus","digestFailureCode","digestFailureMessage","digestedAt","createdAt","updatedAt","extractedAt"],"additionalProperties":false,"properties":{"id":{"type":"string"},"projectId":{"type":"string"},"type":{"type":"string","enum":["TEXT","MARKDOWN","URL","PDF"]},"label":{"oneOf":[{"type":"string"},{"type":"null"}]},"uri":{"oneOf":[{"type":"string"},{"type":"null"}]},"status":{"$ref":"#/components/schemas/SourceStatus"},"retentionPolicy":{"$ref":"#/components/schemas/RetentionPolicy"},"byteSize":{"oneOf":[{"type":"integer","minimum":0},{"type":"null"}]},"tokenEstimate":{"oneOf":[{"type":"integer","minimum":0},{"type":"null"}]},"contentHash":{"oneOf":[{"type":"string"},{"type":"null"}]},"extractedTextPreview":{"oneOf":[{"type":"string"},{"type":"null"}]},"extractedObjectKey":{"oneOf":[{"type":"string"},{"type":"null"}]},"rawObjectKey":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureMessage":{"oneOf":[{"type":"string"},{"type":"null"}]},"metadata":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"digest":{"oneOf":[{"$ref":"#/components/schemas/StorySourceDigest"},{"type":"null"}]},"digestStatus":{"$ref":"#/components/schemas/SourceDigestStatus"},"digestFailureCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"digestFailureMessage":{"oneOf":[{"type":"string"},{"type":"null"}]},"digestedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"extractedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]}}},"StorySourceEnvelope":{"type":"object","required":["source"],"additionalProperties":false,"properties":{"source":{"$ref":"#/components/schemas/StorySource"}}},"StorySourceList":{"type":"object","required":["sources","nextCursor"],"additionalProperties":false,"properties":{"sources":{"type":"array","items":{"$ref":"#/components/schemas/StorySource"}},"nextCursor":{"oneOf":[{"type":"string"},{"type":"null"}]}}},"StorySourceCreate":{"type":"object","required":["type"],"additionalProperties":false,"description":"Inline `text` and `markdown` require `content`. `url` and `pdf` require `uri` and extract asynchronously — poll the source until `status: READY` or `FAILED`. PDF sources require the upstream Content-Type to be `application/pdf`; `url` sources require a text-y Content-Type.","properties":{"type":{"$ref":"#/components/schemas/SourceType"},"content":{"type":"string","description":"Inline payload for `text` and `markdown` types. Max 1,000,000 bytes (UTF-8)."},"uri":{"type":"string","format":"uri","description":"URL for `url` and `pdf` sources. Must be http or https; SSRF-blocked addresses (private, link-local, cloud metadata, multicast) are rejected."},"label":{"type":"string","maxLength":200},"retentionPolicy":{"$ref":"#/components/schemas/RetentionPolicy"},"metadata":{"type":"object","additionalProperties":true}}},"DeletionAck":{"type":"object","required":["id","deletedAt"],"additionalProperties":false,"properties":{"id":{"type":"string"},"deletedAt":{"type":"string","format":"date-time"}}},"StoryPlanStatus":{"type":"string","enum":["PENDING","RUNNING","REQUIRES_APPROVAL","READY","APPROVED","REJECTED","FAILED","SUPERSEDED"]},"StoryArtifactType":{"type":"string","enum":["pitch_deck","table_read","storyboard","trailer","production_video"]},"StoryArtifactRequest":{"type":"object","required":["type"],"additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/StoryArtifactType"},"mode":{"type":"string","maxLength":80},"durationSeconds":{"type":"integer","minimum":5},"aspectRatio":{"type":"string","pattern":"^\\d+:\\d+$"},"includePdf":{"type":"boolean"},"narrationPolicy":{"type":"string","enum":["auto","include","suppress"],"description":"For table_read: auto lets the planner decide; include requests a narrator; suppress makes the read speaker-only. This is a user/plan choice, not tied to mode."},"notes":{"type":"string","maxLength":2000}}},"StoryPlanTarget":{"type":"object","required":["audience","objective","outcome"],"additionalProperties":false,"properties":{"audience":{"type":"string","maxLength":200},"objective":{"type":"string","maxLength":400},"outcome":{"type":"string","maxLength":400},"tone":{"type":"string","maxLength":120},"industry":{"type":"string","maxLength":120},"distributionContext":{"type":"string","maxLength":300}}},"StoryPlanStyleConstraints":{"type":"object","additionalProperties":false,"properties":{"preferredVisualStyle":{"type":"string","maxLength":400},"forbiddenVisuals":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":160}},"voicePreference":{"type":"string","maxLength":160},"musicPolicy":{"type":"string","maxLength":300},"brandSafety":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":160}}}},"StoryPlanCreativeBrief":{"type":"object","additionalProperties":false,"description":"Optional Storyteller/script brief used before table-read planning. For table_read plans, capture the audience, genre, page target, cast, music, and SFX basics here so the generated scriptBlueprint can be reviewed before generation.","properties":{"projectFormat":{"type":"string","enum":["standalone","feature","tv_series","limited_series","short_form_series","audio_series","other"]},"installmentLabel":{"type":"string","maxLength":160},"installmentNumber":{"type":"integer","minimum":1,"maximum":999},"seriesContext":{"type":"string","maxLength":1200},"genre":{"type":"string","maxLength":160},"audience":{"type":"string","maxLength":240},"writingStyle":{"type":"string","maxLength":600},"comps":{"type":"array","maxItems":8,"items":{"type":"string","maxLength":160}},"pageTarget":{"type":"integer","minimum":1},"castNotes":{"type":"string","maxLength":1000},"musicStyle":{"type":"string","maxLength":500},"sfxPolicy":{"type":"string","maxLength":500},"mustKnowBeforeWriting":{"type":"array","maxItems":12,"items":{"type":"string","maxLength":220}}}},"StoryPlanCreate":{"type":"object","required":["target","artifactRequests"],"additionalProperties":false,"description":"Inputs for a new StoryPlan. `sourceIds` are validated as belonging to the project and having status READY. `autoApprove: true` is only allowed for non-table-read plans; table_read plans require review/approval of the generated scriptBlueprint.","properties":{"title":{"type":"string","maxLength":200},"target":{"$ref":"#/components/schemas/StoryPlanTarget"},"artifactRequests":{"type":"array","minItems":1,"maxItems":6,"items":{"$ref":"#/components/schemas/StoryArtifactRequest"}},"styleConstraints":{"$ref":"#/components/schemas/StoryPlanStyleConstraints"},"creativeBrief":{"$ref":"#/components/schemas/StoryPlanCreativeBrief"},"sourceIds":{"type":"array","maxItems":20,"items":{"type":"string"}},"autoApprove":{"type":"boolean"}}},"StoryPlanQuoteLineItem":{"type":"object","required":["kind","label","credits"],"additionalProperties":false,"properties":{"kind":{"type":"string","enum":["planning","source_digest","artifact","addon"]},"label":{"type":"string"},"artifactType":{"$ref":"#/components/schemas/StoryArtifactType"},"opKey":{"oneOf":[{"type":"string"},{"type":"null"}]},"credits":{"type":"integer","minimum":0},"note":{"type":"string"}}},"StoryPlanQuote":{"type":"object","required":["currency","total","lineItems","notes","computedAt"],"additionalProperties":false,"properties":{"currency":{"type":"string","const":"studio_credits"},"total":{"type":"integer","minimum":0},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/StoryPlanQuoteLineItem"}},"notes":{"type":"array","items":{"type":"string"}},"computedAt":{"type":"string","format":"date-time"}}},"StoryPlanBeatRole":{"type":"string","enum":["hook","context","conflict","proof","turn","payoff","cta"]},"StoryPlanBeat":{"type":"object","required":["id","role","title"],"additionalProperties":false,"properties":{"id":{"type":"string","maxLength":80},"role":{"$ref":"#/components/schemas/StoryPlanBeatRole"},"title":{"type":"string","maxLength":200},"narration":{"type":"string","maxLength":1200},"visualIntent":{"type":"string","maxLength":800},"evidenceIds":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":120}},"estimatedSeconds":{"type":"integer","minimum":2,"maximum":120}}},"StoryPlanScriptBlueprint":{"type":"object","required":["pageTarget","genre","audience","writingStyle","outline","cast","approvalSummary"],"additionalProperties":false,"description":"Generated for table_read plans and intended for review before approving the plan. The job runner requires this field for table_read generation.","properties":{"projectFormat":{"type":"string","enum":["standalone","feature","tv_series","limited_series","short_form_series","audio_series","other"]},"installmentLabel":{"type":"string"},"pageTarget":{"type":"integer","minimum":1},"genre":{"type":"string"},"audience":{"type":"string"},"writingStyle":{"type":"string"},"narrationPolicy":{"type":"string","enum":["auto","include","suppress"],"description":"Narration posture for this read. `suppress` means no NARRATOR voice/cue; `include` means a narrator is expected; `auto` lets the plan infer from the format and cast."},"outline":{"type":"array","minItems":1,"maxItems":12,"items":{"type":"object","required":["sceneId","heading","dramaticPurpose","storyAction"],"properties":{"sceneId":{"type":"string"},"heading":{"type":"string"},"pageBudget":{"type":"integer","minimum":1,"maximum":30},"dramaticPurpose":{"type":"string"},"storyAction":{"type":"string"},"sourceEvidenceIds":{"type":"array","maxItems":10,"items":{"type":"string"}}}}},"cast":{"type":"array","minItems":1,"maxItems":16,"items":{"type":"object","required":["name","role","function"],"properties":{"name":{"type":"string"},"role":{"type":"string"},"function":{"type":"string"},"voiceDirection":{"type":"string"}}}},"audio":{"type":"object","properties":{"musicStyle":{"type":"string"},"sfxMoments":{"type":"array","maxItems":20,"items":{"type":"object","required":["sceneId","label","description"],"properties":{"sceneId":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}}}}}},"plannedNextInstallments":{"type":"array","maxItems":10,"items":{"type":"object","required":["label","premise"],"properties":{"label":{"type":"string"},"premise":{"type":"string"}}}},"approvalSummary":{"type":"string"}}},"StoryPlanEnvelope":{"type":"object","required":["version","projectId","title","target","sourceDigest","thesis","journey","visualSystem","artifacts"],"additionalProperties":false,"description":"Canonical source-grounded plan. Returned inside `plan.plan` on a StoryPlanEnvelopeResponse.","properties":{"version":{"type":"integer","const":1},"projectId":{"type":"string"},"title":{"type":"string","maxLength":200},"target":{"$ref":"#/components/schemas/StoryPlanTarget"},"sourceDigest":{"type":"object","required":["summary","sourceIds","evidence","gaps","rightsNotes"],"properties":{"summary":{"type":"string"},"sourceIds":{"type":"array","items":{"type":"string"},"maxItems":20},"evidence":{"type":"array","maxItems":40,"items":{"type":"object","required":["sourceId","claim","support","confidence"],"properties":{"sourceId":{"type":"string"},"claim":{"type":"string"},"support":{"type":"string"},"confidence":{"type":"string","enum":["low","medium","high"]}}}},"gaps":{"type":"array","maxItems":15,"items":{"type":"string"}},"rightsNotes":{"type":"array","maxItems":10,"items":{"type":"string"}}}},"thesis":{"type":"object","required":["oneLine","supportingClaims","avoidClaims"],"properties":{"oneLine":{"type":"string","maxLength":300},"supportingClaims":{"type":"array","maxItems":10,"items":{"type":"string"}},"avoidClaims":{"type":"array","maxItems":10,"items":{"type":"string"}}}},"journey":{"type":"object","required":["beats"],"properties":{"beats":{"type":"array","minItems":3,"maxItems":14,"items":{"$ref":"#/components/schemas/StoryPlanBeat"}}}},"visualSystem":{"type":"object","required":["style"],"properties":{"style":{"type":"string"},"palette":{"type":"array","maxItems":8,"items":{"type":"string"}},"typography":{"type":"string"},"aspectRatios":{"type":"array","maxItems":6,"items":{"type":"string","pattern":"^\\d+:\\d+$"}},"continuityNotes":{"type":"string"},"forbiddenVisuals":{"type":"array","maxItems":10,"items":{"type":"string"}}}},"audioSystem":{"type":"object","additionalProperties":false,"properties":{"voice":{"type":"string"},"music":{"type":"string"},"sfxPolicy":{"type":"string"}}},"scriptBlueprint":{"$ref":"#/components/schemas/StoryPlanScriptBlueprint"},"artifacts":{"type":"array","minItems":1,"maxItems":6,"items":{"type":"object","required":["type","renderPlan"],"properties":{"type":{"$ref":"#/components/schemas/StoryArtifactType"},"mode":{"type":"string","maxLength":80},"durationSeconds":{"type":"integer","minimum":5},"aspectRatio":{"type":"string","pattern":"^\\d+:\\d+$"},"renderPlan":{"type":"object","additionalProperties":true}}}}}},"StoryPlan":{"type":"object","required":["id","projectId","status","title","target","artifactRequests","styleConstraints","creativeBrief","sourceIds","autoApprove","plan","quote","modelUsed","providerUsed","failureCode","failureMessage","approvedAt","rejectedAt","rejectionReason","generatedAt","currentVersion","createdAt","updatedAt"],"additionalProperties":false,"properties":{"id":{"type":"string"},"projectId":{"type":"string"},"status":{"$ref":"#/components/schemas/StoryPlanStatus"},"title":{"oneOf":[{"type":"string"},{"type":"null"}]},"target":{"$ref":"#/components/schemas/StoryPlanTarget"},"artifactRequests":{"type":"array","items":{"$ref":"#/components/schemas/StoryArtifactRequest"}},"styleConstraints":{"oneOf":[{"$ref":"#/components/schemas/StoryPlanStyleConstraints"},{"type":"null"}]},"creativeBrief":{"oneOf":[{"$ref":"#/components/schemas/StoryPlanCreativeBrief"},{"type":"null"}]},"sourceIds":{"type":"array","items":{"type":"string"}},"autoApprove":{"type":"boolean"},"plan":{"oneOf":[{"$ref":"#/components/schemas/StoryPlanEnvelope"},{"type":"null"}]},"quote":{"oneOf":[{"$ref":"#/components/schemas/StoryPlanQuote"},{"type":"null"}]},"modelUsed":{"oneOf":[{"type":"string"},{"type":"null"}]},"providerUsed":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureMessage":{"oneOf":[{"type":"string"},{"type":"null"}]},"approvedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"rejectedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"rejectionReason":{"oneOf":[{"type":"string"},{"type":"null"}]},"generatedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"currentVersion":{"type":"integer","minimum":1},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"StoryPlanEnvelopeResponse":{"type":"object","required":["plan"],"additionalProperties":false,"properties":{"plan":{"$ref":"#/components/schemas/StoryPlan"}}},"StoryPlanList":{"type":"object","required":["plans","nextCursor"],"additionalProperties":false,"properties":{"plans":{"type":"array","items":{"$ref":"#/components/schemas/StoryPlan"}},"nextCursor":{"oneOf":[{"type":"string"},{"type":"null"}]}}},"StoryJobStatus":{"type":"string","enum":["PENDING","RESERVED","RUNNING","PARTIAL","READY","FAILED","CANCELED"],"description":"Lifecycle of a queued artifact-generation job. `RESERVED` means credits are held; `PARTIAL` means at least one artifact is READY while others are still running; `READY` is fully complete. Cancelable while `PENDING | RESERVED | RUNNING`."},"StoryArtifactStatus":{"type":"string","enum":["PENDING","RUNNING","READY","FAILED"],"description":"Lifecycle of an individual artifact within a job. `READY` means the manifest URLs are populated and the artifact is consumable."},"StoryArtifact":{"type":"object","required":["id","storyJobId","type","status","manifest","tableReadShareLinkId","creditLineItemIds","sourcePlanRevision","currentVersion","refineCount","failureCode","failureMessage","createdAt","updatedAt"],"additionalProperties":false,"description":"A single generated output of a story job. For `table_read`, the `manifest` carries the public share token, theater URL, on-demand live-audio start URL, refine state, and finalize state. The artifact id and share URLs are stable across refines. Fetch a prior revision with GET /artifacts/{id}?revision=N.","properties":{"id":{"type":"string"},"storyJobId":{"type":"string"},"type":{"$ref":"#/components/schemas/StoryArtifactType"},"status":{"$ref":"#/components/schemas/StoryArtifactStatus"},"manifest":{"oneOf":[{"$ref":"#/components/schemas/TableReadArtifactManifest"},{"type":"object","additionalProperties":true},{"type":"null"}]},"tableReadShareLinkId":{"oneOf":[{"type":"string"},{"type":"null"}]},"creditLineItemIds":{"type":"array","items":{"type":"string"}},"sourcePlanRevision":{"oneOf":[{"type":"integer","minimum":1},{"type":"null"}]},"currentVersion":{"type":"integer","minimum":1,"description":"Current manifest revision. v1 is the initial generation; each refine increments. Use ?revision=N on GET /artifacts/{id} to fetch a prior version."},"refineCount":{"type":"integer","minimum":0,"description":"Number of times this artifact has been refined. 0 for the initial generation."},"failureCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureMessage":{"oneOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"TableReadArtifactManifest":{"type":"object","description":"Manifest for a `table_read` artifact. The share token authorizes the returned URLs (not the customer API key), so the consumer can embed or hand them to end-users. The artifact id, share token, and all player URLs remain STABLE across refine operations. The MP4 `video` output and the durable MP3 `audio.finalize` are opt-in.","properties":{"type":{"type":"string","const":"table_read"},"status":{"type":"string","enum":["ready"]},"shareToken":{"type":"string","description":"Expiring JWT share token. Public but unguessable (\"unlisted\"). Stable across refines."},"theaterUrl":{"type":"string","format":"uri","description":"Framed visual theater-mode page (`/share/table-read/{token}`) with attribution chrome. Stable across refines."},"theaterFullscreenUrl":{"type":"string","format":"uri","description":"Immersive, chrome-free fullscreen theater (same in-app theater stage). Ideal for embeds/kiosk. Stable across refines."},"audio":{"type":"object","description":"Audio output section. The live on-demand path is always available. The durable MP3 finalize path is opt-in via POST /artifacts/{id}/finalize.","properties":{"mode":{"type":"string","enum":["live_on_demand"]},"liveUrl":{"type":"string","format":"uri","description":"Minimal-chrome, audio-only live player page (`/share/table-read-audio/{token}`). End-user clicks play; audio streams from the live Pipecat/Daily read. No stage, no durable file."},"startLiveUrl":{"type":"string","format":"uri","description":"POST `{ token }` here to spin up an on-demand Pipecat/Daily live read; returns `{ dailyRoomUrl }`. Used by both theater and audio-only players."},"recordingUrl":{"oneOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Populated after a successful audio finalize. The finalized MP3 is based on a completed Pipecat/Daily table-read recording. `null` until finalize is complete."},"recordingDurationMs":{"oneOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Duration of the finalized MP3 recording in milliseconds. Populated alongside `recordingUrl` on finalize completion."},"voiceMap":{"type":"object","additionalProperties":true,"description":"Current cast: character name → { voiceId, voiceName, gender, provider }. Recast a single character in place with POST /artifacts/{id}/voice (scope `artifact:publish`)."},"finalize":{"type":"object","description":"State of the opt-in durable MP3 finalize. Audio finalize requires a completed Pipecat/Daily table-read recording; until then the read stays live and editable.","properties":{"status":{"type":"string","enum":["not_finalized","needs_recording","queued","synthesizing","stitching","rendering","uploading","complete","failed"],"description":"`needs_recording` means no completed Pipecat/Daily recording is ready to freeze. Otherwise poll GET /artifacts/{id} until `complete` or `failed`."},"finalizeUrl":{"oneOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Internal staging URL used during the finalize pipeline. Not for end-user consumption; use `recordingUrl` on completion."},"error":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Human-readable description of the finalize failure when `status` is `failed`."}}}}},"video":{"type":"object","description":"Video output section. Status reflects real render state once the Remotion MP4 export has been triggered.","properties":{"status":{"type":"string","enum":["not_rendered","queued","rendering","complete","stale","needs_recording","failed"],"description":"Render pipeline stage. `not_rendered` until POST /artifacts/{id}/render-video or POST /artifacts/{id}/finalize?mode=video is called. `complete` = a current MP4 is available; `stale` = the read changed after the last render (re-render to refresh); `needs_recording` = finalize audio first."},"renderUrl":{"type":"string","description":"POST to trigger the opt-in Remotion MP4 render via the render-video endpoint (separate credit charge)."},"url":{"oneOf":[{"type":"string","format":"uri"},{"type":"null"}],"description":"Durable MP4 URL. Populated when `status` is `complete` (or `stale`)."}}},"expiresAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]}}},"StoryArtifactEnvelope":{"type":"object","required":["artifact"],"additionalProperties":false,"properties":{"artifact":{"$ref":"#/components/schemas/StoryArtifact"}}},"StoryArtifactListResponse":{"type":"object","required":["artifacts"],"additionalProperties":false,"properties":{"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/StoryArtifact"}}}},"ArtifactScriptEntry":{"type":"object","required":["entryIndex","character","text","isNarration","sceneHeading","parenthetical","page"],"additionalProperties":false,"properties":{"entryIndex":{"type":"integer","minimum":0},"character":{"type":"string"},"text":{"type":"string"},"isNarration":{"type":"boolean"},"sceneHeading":{"oneOf":[{"type":"string"},{"type":"null"}]},"parenthetical":{"oneOf":[{"type":"string"},{"type":"null"}]},"page":{"oneOf":[{"type":"integer","minimum":1},{"type":"null"}]}}},"ArtifactScriptContent":{"type":"object","required":["artifactId","scriptUploadId","title","scope","pageCount","totalEntries","pages","scenes","characters","selection"],"additionalProperties":false,"description":"Readable script-content view for a table_read artifact. Entries are the same parsed backing entries used by theater playback, music, SFX, and coverage.","properties":{"artifactId":{"type":"string"},"scriptUploadId":{"type":"string"},"title":{"oneOf":[{"type":"string"},{"type":"null"}]},"scope":{"type":"string","enum":["all","page","scene","character","range"]},"pageCount":{"type":"integer","minimum":0},"totalEntries":{"type":"integer","minimum":0},"pages":{"type":"array","items":{"type":"object","required":["pageNumber","startEntry","endEntry","wordCount"],"properties":{"pageNumber":{"type":"integer","minimum":1},"startEntry":{"type":"integer","minimum":0},"endEntry":{"type":"integer","minimum":-1},"wordCount":{"type":"integer","minimum":0}}}},"scenes":{"type":"array","items":{"type":"object","required":["sceneIndex","heading","startEntry","endEntry"],"properties":{"sceneIndex":{"type":"integer","minimum":0},"heading":{"type":"string"},"startEntry":{"type":"integer","minimum":0},"endEntry":{"type":"integer","minimum":0}}}},"characters":{"type":"array","items":{"type":"object","required":["character","lineCount","wordCount"],"properties":{"character":{"type":"string"},"lineCount":{"type":"integer","minimum":0},"wordCount":{"type":"integer","minimum":0}}}},"selection":{"type":"object","required":["startEntry","endEntry","entries"],"properties":{"pageNumber":{"type":"integer","minimum":1},"sceneIndex":{"type":"integer","minimum":0},"character":{"type":"string"},"startEntry":{"type":"integer","minimum":0},"endEntry":{"type":"integer","minimum":-1},"entries":{"type":"array","items":{"$ref":"#/components/schemas/ArtifactScriptEntry"}}}}}},"ArtifactScriptContentEnvelope":{"type":"object","required":["script"],"additionalProperties":false,"properties":{"script":{"$ref":"#/components/schemas/ArtifactScriptContent"}}},"StoryJob":{"type":"object","required":["id","projectId","storyPlanId","status","artifactRequests","quote","creditReservationId","progress","modelUsed","failureCode","failureMessage","currentVersion","startedAt","completedAt","artifacts","createdAt","updatedAt"],"additionalProperties":false,"description":"A queued artifact-generation job against an APPROVED StoryPlan. Holds the reserved credit quote and the list of generated artifacts.","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"storyPlanId":{"type":"string"},"status":{"$ref":"#/components/schemas/StoryJobStatus"},"artifactRequests":{"type":"array","items":{"$ref":"#/components/schemas/StoryArtifactRequest"}},"quote":{"oneOf":[{"$ref":"#/components/schemas/StoryPlanQuote"},{"type":"null"}]},"creditReservationId":{"oneOf":[{"type":"string"},{"type":"null"}]},"progress":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"modelUsed":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureCode":{"oneOf":[{"type":"string"},{"type":"null"}]},"failureMessage":{"oneOf":[{"type":"string"},{"type":"null"}]},"currentVersion":{"type":"integer","minimum":1},"startedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"completedAt":{"oneOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/StoryArtifact"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"StoryJobCreate":{"type":"object","required":["storyPlanId"],"additionalProperties":false,"description":"Inputs for a new story job. The plan must be `APPROVED`. `artifactRequests` defaults to the plan's own requests when omitted; if supplied, it overrides them (max 6). `projectId`, if supplied, must match the plan's project.","properties":{"storyPlanId":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"projectId":{"type":"string","pattern":"^[a-z0-9]{20,}$"},"artifactRequests":{"type":"array","minItems":1,"maxItems":6,"items":{"$ref":"#/components/schemas/StoryArtifactRequest"}}}},"StoryJobEnvelope":{"type":"object","required":["job"],"additionalProperties":false,"properties":{"job":{"$ref":"#/components/schemas/StoryJob"}}},"StoryJobList":{"type":"object","required":["jobs","nextCursor"],"additionalProperties":false,"properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/StoryJob"}},"nextCursor":{"oneOf":[{"type":"string"},{"type":"null"}]}}},"CoverageStatus":{"type":"string","enum":["generating","complete","failed"],"description":"Lifecycle of an async coverage report. Poll GET /artifacts/{id}/coverage until `complete` or `failed`."},"CoverageDispatchAck":{"type":"object","required":["artifactId","reportId","status","version"],"additionalProperties":false,"description":"Acknowledgement that a coverage report has been queued. Poll GET /artifacts/{artifactId}/coverage with the returned `reportId`.","properties":{"artifactId":{"type":"string"},"reportId":{"type":"string"},"status":{"$ref":"#/components/schemas/CoverageStatus"},"version":{"type":"integer","minimum":1,"description":"Sequential coverage version for the backing script (increments per report)."}}},"CoverageReport":{"type":"object","required":["id","artifactId","status","version","focusPrompt","progressDetail","failureMessage","report","createdAt"],"additionalProperties":false,"description":"A coverage report for the screenplay backing a table read artifact. `report` carries the structured coverage payload only when `status: complete`.","properties":{"id":{"type":"string"},"artifactId":{"type":"string"},"status":{"$ref":"#/components/schemas/CoverageStatus"},"version":{"type":"integer","minimum":1},"focusPrompt":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"The optional focus lens supplied at generation time."},"progressDetail":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Human-readable progress while `status: generating`."},"failureMessage":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"Populated when `status: failed`."},"report":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}],"description":"Structured coverage payload (logline, premise, structure, characters, dialogue, pacing, marketFit, overallScore, topFixes, strengths, comparable, recommendation). `null` unless `status: complete`."},"createdAt":{"type":"string","format":"date-time"}}},"CoverageReportEnvelope":{"type":"object","required":["coverage"],"additionalProperties":false,"description":"Coverage read envelope. `coverage` is `null` when no coverage has been requested for the artifact yet.","properties":{"coverage":{"oneOf":[{"$ref":"#/components/schemas/CoverageReport"},{"type":"null"}]}}},"MusicStatus":{"type":"string","enum":["none","generating","ready"],"description":"Adaptive music readiness for a table read. `none` — no active music is present (including intentional 0% coverage); `generating` — scoring is still in progress; `ready` — at least one scene carries music."},"MusicGenerateRequest":{"type":"object","additionalProperties":false,"description":"Adaptive-music tuning. Omit the body entirely to (re)generate per-scene music without changing coverage. Provide `sceneIndex` to update one scene music direction instead.","properties":{"musicCoveragePercent":{"type":"number","description":"How much of the read should carry music. Accepts a fraction (0..1) or a whole percent (0..100); clamped into [0,1]."},"sceneIndex":{"type":"integer","minimum":0,"description":"Zero-based scene index to update. When present, this request mutates one scene direction instead of queueing full music regeneration."},"prompt":{"type":"string","maxLength":240,"description":"Optional scene-level Lyria prompt, e.g. \"heavier bass pulse, tense low strings\". Used with `sceneIndex`."},"summary":{"type":"string","maxLength":400,"description":"Optional plain-language scene music summary. Used with `sceneIndex`."},"enabled":{"type":"boolean","description":"Optional scene music toggle. `false` mutes music for the scene; `true` turns it back on. Used with `sceneIndex`."},"weight":{"type":"number","minimum":0,"maximum":2,"description":"Optional prompt weight for the scene-level prompt; defaults to 1 and is clamped by the API."}}},"MusicGenerateResponse":{"type":"object","required":["artifactId","status"],"additionalProperties":false,"description":"Acknowledgement that adaptive music (re)generation has been dispatched, or that one scene direction was updated.","properties":{"artifactId":{"type":"string"},"status":{"type":"string","enum":["generating","ready"]},"coveragePercent":{"oneOf":[{"type":"number"},{"type":"null"}],"description":"The clamped coverage percent applied (null when none was supplied and none was previously set)."},"totalScenes":{"type":"integer","description":"Number of scenes the read was decomposed into."},"sceneIndex":{"type":"integer","minimum":0,"description":"Present when a scene-level music update was applied."},"adaptiveSceneDirection":{"type":"object","additionalProperties":true,"description":"Present when a scene-level music update was applied. This is the live theater adaptive scene direction."}}},"MusicStatusEnvelope":{"type":"object","required":["music"],"additionalProperties":false,"description":"Adaptive music status envelope for a table read artifact.","properties":{"music":{"type":"object","required":["artifactId","status","scenesWithMusic","totalScenes","coveragePercent"],"additionalProperties":false,"properties":{"artifactId":{"type":"string"},"status":{"$ref":"#/components/schemas/MusicStatus"},"scenesWithMusic":{"type":"integer"},"totalScenes":{"type":"integer"},"coveragePercent":{"oneOf":[{"type":"number"},{"type":"null"}]}}}}},"VoiceModificationRequest":{"type":"object","required":["startEntryIndex","endEntryIndex"],"additionalProperties":false,"description":"Apply a voice effect to a contiguous range of dialogue entries. The autotune recipe is optional and partial — any omitted field falls back to the proven defaults (key D, scale minpent, strength 1.0, smooth 1, reverb chapel).","properties":{"startEntryIndex":{"type":"integer","minimum":0,"description":"First dialogue-entry index in the range (inclusive)."},"endEntryIndex":{"type":"integer","minimum":0,"description":"Last dialogue-entry index in the range (inclusive)."},"effect":{"type":"string","enum":["autotune"],"default":"autotune","description":"Voice effect to apply. Defaults to autotune (the only effect today)."},"params":{"type":"object","additionalProperties":false,"description":"Partial autotune recipe; omitted fields fall back to the defaults.","properties":{"key":{"type":"string","enum":["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"],"default":"D"},"scale":{"type":"string","enum":["major","minor","majpent","minpent","chromatic"],"default":"minpent"},"strength":{"type":"number","minimum":0,"maximum":1,"default":1,"description":"Pitch-correction strength, 0 (off) to 1 (full snap)."},"smooth":{"type":"integer","minimum":1,"default":1,"description":"Pitch-glide smoothing; higher = slower glide."},"reverb":{"type":"string","enum":["none","light","chapel"],"default":"chapel"}}}}},"VoiceModificationResponse":{"type":"object","required":["artifactId","modificationId","status"],"additionalProperties":false,"description":"Acknowledgement that a voice modification has been dispatched. Poll the read; the rendered audio is projected onto entries once `status` reaches `ready`.","properties":{"artifactId":{"type":"string"},"modificationId":{"type":"string"},"status":{"type":"string","enum":["queued","rendering","ready","failed"],"description":"Lifecycle of the modification record."}}},"SfxCue":{"type":"object","required":["id","label","entryIndex","soundUrl","isDraft"],"additionalProperties":true,"description":"Timed sound-effect cue on the read backing a table-read artifact.","properties":{"id":{"type":"string"},"label":{"type":"string"},"entryIndex":{"type":"integer","minimum":0},"soundUrl":{"oneOf":[{"type":"string","format":"uri"},{"type":"null"}]},"isDraft":{"type":"boolean","description":"True when sound generation has not produced a reusable sound URL yet."}}},"SfxMutationResponse":{"type":"object","additionalProperties":false,"description":"Result of adding, updating, or removing a sound-effect cue.","properties":{"artifactId":{"type":"string"},"cue":{"$ref":"#/components/schemas/SfxCue"},"removed":{"type":"boolean"},"id":{"type":"string"},"label":{"type":"string"}}},"SfxListResponse":{"type":"object","required":["sfx"],"additionalProperties":false,"properties":{"sfx":{"type":"object","required":["artifactId","cues"],"additionalProperties":false,"properties":{"artifactId":{"type":"string"},"cues":{"type":"array","items":{"$ref":"#/components/schemas/SfxCue"}}}}}},"AvatarRecastRequest":{"type":"object","required":["character"],"additionalProperties":false,"description":"Regenerate one character's avatar portrait. The render reuses the artifact's resolved avatar style unless `style` is supplied, which sets the script-wide avatar style for this and all subsequent renders.","properties":{"character":{"type":"string","minLength":1,"description":"Character cue whose avatar to regenerate, e.g. \"NARRATOR\" or \"MAYA\"."},"refinement":{"type":"string","description":"Optional freeform art-direction nudge for this render, e.g. \"older, weathered, dim backlight\"."},"style":{"type":"string","description":"Optional; sets the script-wide avatar style (persists and applies to this and subsequent renders). To restyle the whole cast at once, use POST /artifacts/{id}/cast with `avatarStyle`."},"referenceImageUrl":{"type":"string","format":"uri","description":"Optional already-uploaded image URL (http or https) to seed the portrait."}}},"AvatarOp":{"type":"object","required":["character","status","chatToolJobId"],"additionalProperties":false,"description":"A queued per-character avatar render. Poll GET /artifacts/{artifactId}/cast and watch the character's `avatarStatus` until it reaches `ready`.","properties":{"character":{"type":"string"},"status":{"type":"string","enum":["queued","rendering","ready","failed"],"description":"Lifecycle of the avatar render op."},"chatToolJobId":{"type":"string","description":"Id of the queued `regenerate_avatar` job backing this render."}}},"AvatarRecastResponse":{"type":"object","required":["artifactId","character","status","chatToolJobId"],"additionalProperties":false,"description":"Acknowledgement that a single-character avatar regeneration has been dispatched. Poll GET /artifacts/{artifactId}/cast until the character's `avatarStatus` reaches `ready`.","properties":{"artifactId":{"type":"string"},"character":{"type":"string"},"status":{"type":"string","enum":["queued","rendering","ready","failed"],"description":"Lifecycle of the avatar render op."},"chatToolJobId":{"type":"string","description":"Id of the queued `regenerate_avatar` job backing this render."}}},"CastEntryUpdate":{"type":"object","required":["character"],"additionalProperties":false,"description":"One per-character cast update: reassign the voice, queue an avatar re-render, or both.","properties":{"character":{"type":"string","minLength":1,"description":"Character cue to update."},"voiceId":{"type":"string","description":"New voice id to assign."},"voiceName":{"type":"string","description":"Human-readable voice name for display."},"gender":{"type":"string","description":"Optional voice gender hint. Preserved from the prior assignment when omitted."},"provider":{"type":"string","description":"Optional TTS provider, e.g. cartesia, openai, hume."},"avatarRefinement":{"type":"string","description":"Optional per-character art-direction nudge for this character's avatar render."},"regenerateAvatar":{"type":"boolean","description":"Set true to queue an avatar re-render for this character."}}},"CastBatchRequest":{"type":"object","additionalProperties":false,"description":"Batch cast update. Provide at least one `entries` item OR a top-level `avatarStyle` — a body with neither is rejected as `validation_failed`. A top-level `avatarStyle` restyles ALL portraits.","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/CastEntryUpdate"},"description":"Per-character updates. Optional when a top-level `avatarStyle` is supplied."},"avatarStyle":{"type":"string","description":"Optional portrait art-direction applied across the whole cast — re-renders every character against the new style."}},"anyOf":[{"required":["entries"]},{"required":["avatarStyle"]}]},"CastBatchResponse":{"type":"object","required":["artifactId","voiceMap","avatars"],"additionalProperties":false,"description":"Result of a batch cast update. Voice reassignments are already applied in `voiceMap`; each item in `avatars` is a queued avatar render to poll.","properties":{"artifactId":{"type":"string"},"voiceMap":{"type":"object","additionalProperties":true,"description":"Full updated cast: character → { voiceId, voiceName, gender, provider }."},"avatars":{"type":"array","items":{"$ref":"#/components/schemas/AvatarOp"},"description":"One op per queued avatar render."}}},"EnrichedCastEntry":{"type":"object","required":["character","voiceId","voiceName","gender","provider","avatarUrl","avatarStyle","avatarStatus"],"additionalProperties":false,"description":"One character's assigned voice merged with its avatar state.","properties":{"character":{"type":"string"},"voiceId":{"oneOf":[{"type":"string"},{"type":"null"}]},"voiceName":{"oneOf":[{"type":"string"},{"type":"null"}]},"gender":{"oneOf":[{"type":"string"},{"type":"null"}]},"provider":{"oneOf":[{"type":"string"},{"type":"null"}]},"avatarUrl":{"oneOf":[{"type":"string","format":"uri"},{"type":"null"}]},"avatarStyle":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"The art-direction this character's avatar was rendered against."},"avatarStatus":{"type":"string","enum":["none","queued","rendering","ready","failed"],"description":"`none` (no avatar), `queued` / `rendering` (a render is in flight), `ready`, or `failed`. Poll until `ready` after a render."}}},"CastEnvelope":{"type":"object","required":["artifactId","cast","avatarStyle"],"additionalProperties":false,"description":"Enriched cast for a table read artifact: each character's voice merged with its avatar state, plus the resolved cast-wide avatar style.","properties":{"artifactId":{"type":"string"},"cast":{"type":"array","items":{"$ref":"#/components/schemas/EnrichedCastEntry"}},"avatarStyle":{"oneOf":[{"type":"string"},{"type":"null"}],"description":"The artifact's resolved cast-wide avatar art-direction (the `table_read_avatar_style` sidecar). `null` when no style is resolved."}}},"ArtifactRenderAck":{"type":"object","required":["artifactId","render"],"additionalProperties":false,"description":"Acknowledgement that the opt-in MP4 render has been requested for an artifact. `render` carries the share-render status returned by the table-read share renderer.","properties":{"artifactId":{"type":"string"},"render":{"type":"object","additionalProperties":true}}}}},"x-status":"foundation","x-api-version":"v1","x-scopes":{"story:read":"Read story projects, plans, jobs, artifacts, and shared discovery surfaces.","story:write":"Create or modify story projects, plans, and jobs.","source:read":"Read source packs and extraction metadata.","source:write":"Create or modify sources and trigger extraction.","artifact:read":"Read generated artifacts, coverage, adaptive music, sound effects, and durable render status.","artifact:publish":"Mutating + billable artifact ops: refine table reads, recast voices, regenerate adaptive music, manage sound-effect cues, and finalize/render durable MP3/MP4 outputs.","credits:read":"Read Studio Credit balance and quota.","webhook:write":"Manage webhook endpoints and signing secrets."},"x-error-codes":[{"code":"authentication_required","description":"No bearer API key was supplied in the Authorization header."},{"code":"invalid_api_key","description":"The API key is malformed, unknown, or does not match the stored secret."},{"code":"api_key_revoked","description":"The API key has been revoked by an account owner."},{"code":"api_key_expired","description":"The API key has passed its configured expiration timestamp."},{"code":"account_disabled","description":"The account that owns this API key has been disabled."},{"code":"ip_not_allowed","description":"The request originated from an IP that is not in the key allowlist."},{"code":"insufficient_scope","description":"The API key does not include the scope required for this endpoint."},{"code":"rate_limited","description":"The per-key + per-IP rate limit for this route family was exceeded."},{"code":"idempotency_key_required","description":"The endpoint requires an Idempotency-Key header for safe retries."},{"code":"idempotency_conflict","description":"A request with the supplied Idempotency-Key is already processing or used incompatible parameters."},{"code":"validation_failed","description":"The request payload failed validation. The error message names the offending field."},{"code":"project_not_found","description":"The story project does not exist, was deleted, or is owned by another account."},{"code":"source_not_found","description":"The story source does not exist, was deleted, or is owned by another account."},{"code":"source_type_unsupported","description":"The requested source type is not yet supported by this API phase. Check `/api/v1/capabilities`."},{"code":"source_too_large","description":"The source payload exceeds the documented size limit. See `details.maxBytes`."},{"code":"source_fetch_failed","description":"A URL or PDF source could not be safely fetched (SSRF block, redirect limit, size limit, content-type mismatch, or upstream error)."},{"code":"story_plan_not_found","description":"The story plan does not exist, was deleted, or is owned by another account."},{"code":"story_plan_state_invalid","description":"The story plan is in a state that does not allow the requested operation. See `details.status` and `details.allowed`."},{"code":"story_plan_failed","description":"StoryPlan generation or refinement failed. See `details` for the underlying reason when available."},{"code":"story_job_not_found","description":"The story job does not exist, was deleted, or is owned by another account."},{"code":"story_job_state_invalid","description":"The story job is in a state that does not allow the requested operation (e.g. canceling a job that has already reached a terminal state). See `details.status` and `details.allowed`."},{"code":"artifact_not_found","description":"The story artifact does not exist or is owned by another account."},{"code":"artifact_not_ready","description":"The artifact is not in a state where this operation is valid yet (e.g. requesting an MP4 render before the share link exists). See `details.status`."},{"code":"artifact_generation_failed","description":"The artifact adapter failed to produce a shareable output. The job and artifact rows stay inspectable; see the artifact `failureCode` / `failureMessage`."},{"code":"insufficient_credits","description":"The account does not have enough Studio Credits to reserve this job. See `details.required` and `details.available`."},{"code":"internal_error","description":"The Sleeper Hit Story API failed unexpectedly. Retrying with the same Idempotency-Key is safe."}],"x-rate-limits":{"default":{"requestsPerMinute":120,"scope":"per API key + IP + route family"},"elevated":{"requestsPerMinute":600,"scope":"per API key + IP + route family"}}}