{"version":"v1","status":"foundation","auth":{"schemes":["bearer_api_key"],"scopes":["story:read","story:write","source:read","source:write","artifact:read","artifact:publish","publishing:read","publishing:write","publishing:publish","publishing:connect","publishing:analytics","app:connect","credits:read","webhook:write","account:read","account:write"],"supportsIdempotency":true},"developmentFlow":{"warnings":[{"id":"series_bible_before_screenplay","stage":"before_screenplay_generation","severity":"warning","blocking":false,"title":"Complete the Series Bible before screenplay pages","guidance":"Before planning or generating a screenplay-backed table_read artifact, read, save, or generate the project Series Bible and confirm the format, audience, season arc, episode map, character roster, canon/world rules, visual style, audio style, and open questions are usable. The planner and screenplay adapter auto-load the saved Series Bible as project canon; if the user proceeds anyway, carry the missing areas into the StoryPlan review instead of silently ignoring them.","recommendedActions":["GET /story-projects/{projectId}/series-bible to inspect current canon.","PATCH or POST /story-projects/{projectId}/series-bible to save or generate the full project bible.","GET or POST /story-projects/{projectId}/series-bible/coverage to inspect or generate the versioned foundational craft gate.","Carry relevant canon into the StoryPlan creativeBrief and approval review before create_job."],"surfaces":["ui","api","cli","mcp"]},{"id":"project_stage_before_generation","stage":"before_planning_or_generation","severity":"warning","blocking":true,"title":"Finish the project stage a refusal names — do not retry it","guidance":"Planning, drafting episodes, casting, keeping or rendering mood board frames, writing the episode map, and generating project media are refused until the project reaches the stage each one needs. The refusal is a 409 `project_precondition_failed` whose `error.details.stage` names the stage the project has not finished (premise, premise_coverage, planning, full_coverage) with `missingFields`, `canPlan` and `canStartEpisode`. It is state, not a transient fault: nothing was created or charged, and the identical call refuses the same way until the stage is done — a recurring caller must stop and finish the stage (or tell the writer) rather than retry on a schedule.","recommendedActions":["Before a run that fetches, uploads, plans or creates jobs, GET /story-projects/{projectId} and read project.workspaceGate; skip the run when canPlan or canStartEpisode is false. The read is free and queues nothing.","On 409 project_precondition_failed, read error.details.stage and finish that stage before calling again.","premise / planning: develop the Series Bible (PATCH or POST /story-projects/{projectId}/series-bible).","premise_coverage / full_coverage: POST then poll /story-projects/{projectId}/series-bible/coverage."],"surfaces":["ui","api","cli","mcp"]},{"id":"cast_before_table_read","stage":"before_planning_or_generation","severity":"warning","blocking":true,"title":"A table read performs the project cast — complete it first","guidance":"A table_read job never casts. From a plan it starts only when the project cast canon, plus the request's own voiceMap, covers every canon character: a voice for everyone, and a narrated read also needs a narrator voice. Pictures are not a read requirement: a headshot, body shot or sheet is drawn for renders, and the read shows each character's initials until it exists. A job that adopts a finalized screenplay (scriptUploadId) needs that screenplay's episode cast complete. The refusal is a 409 `cast_precondition_failed` before anything is created or charged, with `details.reason` and `details.missing`. Once the job has written its screenplay, a speaker the cast does not cover fails the job (failureCode `cast_precondition_failed`, a contract failure) before any audio, and its credits are released.","recommendedActions":["Before a recurring run creates a table_read job, GET /story-projects/{projectId} and read project.tableReadReadiness; skip the run when ready is false and complete the members it names.","Save each character's voice (and, unless audioOnly, the art) on the cast canon: PATCH /story-projects/{projectId}/cast-canon (MCP save_cast_canon; CLI cast-canon save), or cast them on the Casting step.","Or pass a voiceMap on the table_read request for voices the canon lacks (saved IDs from GET /voices).","To perform a finalized, cast-ready screenplay as written, set scriptUploadId on the table_read request."],"surfaces":["ui","api","cli","mcp"]},{"id":"mood_board_before_storyboard_rendering","stage":"before_storyboard_rendering","severity":"warning","blocking":true,"title":"Complete the Series Bible Mood board before rendering","guidance":"Image and video rendering are locked until the Series Bible has explicit production medium, visual style, character design, visual truths, production design, color palette, lighting rules, camera language, and location truths. Read document.readiness.moodBoard on the shared Series Bible response and resolve every missing field before rendering.","recommendedActions":["GET /story-projects/{projectId}/series-bible and inspect document.readiness.moodBoard.","PATCH or POST /story-projects/{projectId}/series-bible to resolve every missing visual field.","Render stills or video only after document.readiness.moodBoard.ready is true."],"surfaces":["ui","api","cli","mcp"]},{"id":"coverage_round_after_screenplay","stage":"after_screenplay_exists","severity":"warning","blocking":false,"title":"Run one coverage round after the screenplay exists","guidance":"Once a screenplay-backed table_read artifact exists, read or poll the first coverage report before treating the script as ready for finalization, pitch packaging, or later installment generation. Table-read jobs queue the initial coverage pass when pages are created; request a new report only if none exists or the user wants another pass. Use the report, especially top fixes, to drive one revision pass. This is guidance, not an enforcement gate.","recommendedActions":["GET /artifacts/{artifactId}/coverage after the table_read artifact is READY.","POST /artifacts/{artifactId}/coverage only if no report exists or the user wants another pass.","Poll GET /artifacts/{artifactId}/coverage until status is complete.","Use the coverage report for a revision/refine pass before finalize, pitch deck, or next-episode work."],"surfaces":["ui","api","cli","mcp"]}]},"projects":{"available":true,"crudScopes":{"read":"story:read","write":"story:write"},"documents":{"seriesBible":{"available":true,"scopes":{"read":"story:read","write":"story:write","generate":"story:write"},"promptSystem":"SidecarPrompt","sidecar":"story_api_series_bible","note":"GET/PATCH/POST /story-projects/{id}/series-bible reads, saves, or sidecar-generates a project-level bible shared by UI, API, CLI, and MCP. POST answers at once with status generating and writes it in the background; GET reports the run as `generation` (generating, ready or failed) beside the Bible as saved. GET/POST /story-projects/{id}/series-bible/coverage adds a versioned, 72-exemplar qualitative foundation; series script coverage waits for a current passing report. Every Bible response includes document.readiness.development and document.readiness.moodBoard. Visual readiness is a hard gate for storyboard creation and rendering."}}},"seasonRuns":{"available":true,"scopes":{"read":"story:read","map":"story:write","approve":"story:write","end":"story:write","delete":"story:write","create":["story:write","publishing:write"],"advance":["story:write","artifact:publish","publishing:write","publishing:publish"]},"statuses":["mapping","approved","running","blocked","complete","ended"],"episodeStatuses":["mapping","ready_to_plan","recording","planning","plan_review","generating","coverage","needs_revision","coverage_passed","finalizing","ready_to_publish","publishing","published","blocked"],"coverageGate":{"minimumOverallScore":8,"minimumCategoryScore":7,"allowedRecommendations":["RECOMMEND","CONSIDER"],"maxRevisionAttempts":3},"humanApprovals":["Confirm every imported Series Bible episode brief before locking the map.","Approve the complete season map before any episode planning starts.","Review and approve each generated StoryPlan through the existing plan approval endpoint.","Approve a replacement planning attempt after a plan fails or becomes rejected or superseded.","Approve retrying the same current CoverageReport after coverage generation fails.","Approve the exact revision instruction after any failed coverage gate.","Approve RSS publication after coverage passes and the durable MP3 is ready.","A product season (one video per live feature): approve the map and its price (`spendQuote.sentence`) once; nothing else is asked unless an episode is held.","End a season run only once the user agrees (POST /season-runs/{runId}/end with userConfirmed: true): it is `ended` for good, nothing it held can spend, and only a new run makes the season again.","Delete a finished or ended season run only once the user agrees (DELETE /season-runs/{runId}): it goes with its episode rows, its videos and releases stay; refused while it is live, once it published, or while anything it started still runs."],"note":"On a story project, season runs are resumable orchestration, not an unattended generate-all shortcut. On a product project the run makes one video per live (shipped or beta) feature of the season by itself once the map and its price are approved: it records a feature that has no recording yet (only the recordings the approved price named), plans the cut, approves it (free: recordings and title cards render nothing), waits for its video review, finishes it within the approved most per video, and leaves it waiting in Publishing; an episode it cannot finish alone is `blocked` with a sentence saying what fixes it, and the run picks it back up by itself once fixed. A story run processes episodes in order, exposes one durable next action at a time, and fails closed until overall coverage is at least 8/10, each required category is at least 7/10, and the recommendation is RECOMMEND or CONSIDER. These defaults are hard floors: custom policy may require higher scores, fewer recommendations, or 1–3 revisions, but cannot weaken the gate. A failed coverage generation can be explicitly retried on the same report id/version without creating another report. Coverage attempts and human-approved revision instructions are retained for audit."},"plans":{"available":true,"statuses":["PENDING","RUNNING","REQUIRES_APPROVAL","READY","APPROVED","REJECTED","FAILED","SUPERSEDED"],"autoApprove":true,"artifactTypes":["pitch_deck","table_read","trailer"],"beatRoles":["hook","context","conflict","proof","turn","payoff","cta"],"quote":{"currency":"studio_credits","planningBaseCredits":0,"perSourceDigestCredits":0},"note":"POST /story-projects/{id}/story-plans runs an async planner backed by DB sidecar `story_api_plan`. Non-blocking development gate: before planning a screenplay/table_read, read or generate the project Series Bible and review the plan against it. The planner auto-loads the saved Series Bible as canonical project truth. Table-read plans require a `scriptBlueprint` review step and cannot use `autoApprove`; generate one table_read script per plan/job. Explicit approval through POST /story-plans/{planId}/approve requires userConfirmed=true after the user reviews that exact plan — except for the ONE API key holding a show's standing approval, for a table-read plan in that show's project created after the grant. Table-read artifact requests may set `narrationPolicy` (`auto | include | suppress`) so narrator/no-narrator is a user-level creation choice, not implied by mode. Source IDs must be READY. Every plan carries a deterministic quote; planning and source digest lines are included/unmetered, while artifact/add-on lines reserve at job/refine/finalize time."},"sources":{"types":[{"type":"text","availability":"available","maxInlineBytes":1000000},{"type":"markdown","availability":"available","maxInlineBytes":1000000},{"type":"url","availability":"available","note":"SSRF-safe fetch over http/https with private/link-local/metadata IP blocking, 3-redirect cap, 10 MB body cap, and a text content-type allowlist (text/html, text/plain, text/markdown, application/json, application/xhtml+xml). Extraction runs async via pg-boss; poll the source until `status: READY`."},{"type":"pdf","availability":"available","note":"Submit a public http/https URL pointing at a PDF (application/pdf). The fetcher applies the same SSRF + redirect + size guards as the `url` type; the worker extracts text and records `metadata.pageCount`. Extraction runs async via pg-boss; poll until `status: READY`."}],"retentionPolicies":["EXTRACTED_ONLY","STORE_ORIGINAL","METADATA_ONLY"],"digest":{"availability":"available","statuses":["NOT_STARTED","PENDING","RUNNING","READY","FAILED","SKIPPED"],"fields":["summary","thesis","claims","evidence","gaps","rightsNotes"],"note":"After extraction, the API runs a structured-output LLM digest backed by the DB sidecar `story_api_source_digest`. Returns SKIPPED when the sidecar/provider is not configured or no extracted text is available (e.g. retention=METADATA_ONLY), and by design for a full-context producer item (`digestFailureMessage: \"Used in full by its episode plan.\"`: its plan reads every character). SKIPPED is terminal and not a fault: stop polling, the source is usable. Poll `digestStatus` on the source."},"origin":{"field":"originatedAt","note":"When the source material itself was made (ISO 8601; a thread's posting time), stored as `metadata.originatedAt` and returned as `originatedAt`. A project with `seasonCadence: \"monthly\"` files the episode made from the source into the season for that month (UTC), falling back to the source's `createdAt`."},"identity":{"fields":["producer","externalId"],"reservedProducerPrefix":"integration:","note":"A producer that sends one source per item names it with `producer` + `externalId` (both or neither). A project holds one live source per item: a repeat returns it with `deduplicated: true` and stores nothing, even while it is still PENDING/EXTRACTING; deleting the source (or its extraction FAILING) frees the item to be sent again, so DELETE one that never becomes READY. Producer items are episode material, not canon: Bible generation never reads them, a season pins one only when it was curated into that season (items sent over the API never are), and a full-context producer item is not digested (`digestStatus: SKIPPED`, terminal by design). For `hackernewsradio`, `externalId` is the numeric Hacker News story id and the thread-completeness declaration is enforced. `integration:<integrationId>` identifies items kept from a connected product and is reserved."},"note":"Inline text + markdown, async URL ingestion, async PDF extraction, and LLM-driven source digest all ship today. Source content runs through the digest after extraction; consume `digest` on the source envelope."},"artifacts":{"table_read":{"outputs":["theater_url","theater_fullscreen_url","live_audio","mp3_recording","video"],"availability":"available","modes":["documentary","podcast","drama"],"narrationPolicies":["auto","include","suppress"],"preassignedCast":{"availability":"available","requestField":"artifactRequests[].voiceMap","voicesEndpoint":"GET /voices","requiredEntryFields":["voiceId"],"optionalEntryFields":["voiceName","gender","provider"],"supportedProviders":["cartesia","elevenlabs","openai","deepgram","hume"],"note":"Pass a character-to-voice map on the table_read job request for voices the project cast canon does not carry. Use saved voice IDs owned by the caller from GET /voices (CLI: voices list; MCP: list_voices). A canon voice wins over a map entry for the same person."},"castRule":{"availability":"available","readinessField":"GET /story-projects/{projectId} → project.tableReadReadiness","refusal":"cast_precondition_failed","note":"Canon-cast: a read from a plan starts when the project cast canon (+ voiceMap) covers every canon character — a voice for everyone; pictures are drawn for renders and never hold a read shut. An uncast speaker in the written screenplay fails the job before any audio and releases its credits."},"prepare":{"availability":"available","endpoint":"POST /scripts/{scriptUploadId}/table-read/prepare","note":"Prepare an episode's table read once its cast is complete: it charges the read's table-read credit (priced by the screenplay's pages: the short or long table-read price GET /credits lists), once per screenplay version, and adds the read's scene sound effects and music. METERED, PRICED FIRST: TWO CALLS on the API and the CLI — without `confirmed` it only prices it; with `confirmed: true` it charges and starts — and two tools on MCP and in chat (quote_prepare_table_read, then prepare_table_read with `confirmed: true`). A read already prepared, or being prepared, answers prepared / preparing and costs nothing, so a retry never charges twice. The Writers Lab's \"Continue to Table Read\" on Casting is the same operation. CLI: script table-read prepare <scriptUploadId> [--confirm]; MCP: prepare_table_read."},"adoptScript":{"availability":"available","requestField":"artifactRequests[].scriptUploadId","note":"Perform a finalized, cast-ready screenplay version from the plan's project as written: no screenplay is generated, no coverage rewrite runs and no second coverage report is queued. Refused beside voiceMap, notes, durationSeconds, coverageTarget, coverageMaxRewrites, punchUp or neverSay. Its table read is charged once per screenplay version, both ways: a screenplay already prepared (POST /scripts/{scriptUploadId}/table-read/prepare, or the Writing Lab) is quoted at 0 for the read. CLI: jobs create --script <scriptUploadId>; MCP: create_job artifactRequests[].scriptUploadId."},"punchUp":{"availability":"available","requestFields":["artifactRequests[].punchUp","artifactRequests[].neverSay","artifactRequests[].firstLineClean"],"defaultEnabled":false,"note":"For table_read on a job that writes its screenplay: true runs a punch-up pass over the finished draft before it is scored or performed, one scene at a time. Every exchange is made to land, bits are topped, the register is brought up to what the notes and the Series Bible ask for (swearing included when they ask for it), and speeches, explained jokes and dead air are cut, at the same length or shorter. A scene keeps its punch-up only if every verbatim quote survives, it adds no neverSay term, it keeps its slugline, it adds no speaker and it grows by no more than 15%. A scene that only ran long is asked once to cut back to its length; any other failure, or a second miss, and the written scene stands. Default false. Not combined with scriptUploadId. Progress reports stage `punching_up`, then how many scenes kept their punch-up. With firstLineClean the episode's first spoken line stays free of swearing. CLI: --artifact type=table_read,punchUp=true,neverSay=TERM|TERM,firstLineClean=true; MCP: create_job artifactRequests[].punchUp / neverSay / firstLineClean."},"optInOutputs":["mp3_recording","video"],"note":"An approved StoryPlan generates one live, shareable table-read script/installment at a time. The artifact manifest returns stable theater/audio URLs, script content is readable via GET /artifacts/{artifactId}/script, and post-creation mutations are available through refine, voice, music, SFX, coverage, and finalize endpoints. Non-blocking development gate: once screenplay pages exist, read/poll the initial coverage report before finalization, pitch packaging, or later installment generation; table-read jobs queue that first pass automatically. Read /api/v1/agent-guidance for the current backend-managed agent workflow guidance."},"pitch_deck":{"outputs":["video","pdf"],"durationSeconds":{"min":10,"max":180},"availability":"available","price":{"currency":"studio_credits","rate":{"renderResolution":"480p","renderPerSecond":1,"defaultDeliveryResolution":"720p","delivery":{"720p":{"finish":1,"total":2},"1080p":{"finish":4,"total":5}}},"summary":"A pitch deck is priced by the second: 1 credit a second to render at 480p, plus 1 credit a second to finish at 720p (2 credits a second in all; a 1080p finish is 4 credits a second instead). Planning is free. The exact price is quoted per shot once the plan is ready, before anything renders; location plates and on-screen text are itemized in the same quotes."},"note":"Generated from an APPROVED StoryPlan and a completed table_read artifact for the same plan/project so the pitch journey reuses canonical script text, the cast's body figures, and their voices. A deck job always stops at its plan (`progress.pitchDeckJobId`, `progress.nextStep: approve_pitch_deck_plan`) and moves draft → rendering → review → finishing → complete under /pitch-decks. There are no stills and no storyboard or draft approval. Approving the plan (POST /pitch-decks/{jobId}/plan/approve) is priced first: the first call compiles and voices the chapters and quotes the 480p render; `confirmed: true` renders every call at 480p, a line spoken by a cast member in their own recorded voice and a narrated line laid over the cut. A take that re-voices a line or opens off its planned framing is retaken once automatically at our cost; after that a new take is the writer's priced retake (POST /pitch-decks/{jobId}/chapters/render-clip). In review, read GET /pitch-decks/{jobId}/chapters, choose takes (POST /pitch-decks/{jobId}/chapters/{chapterIndex}/select-take; a re-voiced take is never selected), stage a re-voiced line as a voice-over in a new take (POST /pitch-decks/{jobId}/chapters/{chapterIndex}/stage-voice-over, priced like a new take), repair one take in place (POST /pitch-decks/{jobId}/chapters/{chapterIndex}/repair-take, priced first), trim it to a span (POST /pitch-decks/{jobId}/chapters/{chapterIndex}/trim-take, free; never into a spoken line) or keep it as it is (POST /pitch-decks/{jobId}/chapters/{chapterIndex}/take-repair-override, free) — every take is watched with its sound and remedied by rule at our cost first (a cut nobody asked for at an end of an otherwise good take is trimmed off, free), and edit, add or remove chapters; then finish (POST /pitch-decks/{jobId}/finish, priced first), which upscales the selected takes to the delivery class — 720p by default, 1080p on request — and stitches the finished master. Every stitched master is watched with its sound, free to the writer, and scored on its quality and its adherence to the shot list (GET /pitch-decks/{jobId}/video-coverage; POST covers it again): while the preview fails that bar the finish is refused unless the writer chooses to finish anyway (`acknowledgeVideoCoverage`). A deck that failed while it was being assembled (the preview or the finished master) is put back together free with POST /pitch-decks/{jobId}/reassemble, from the takes it already has; in review or complete the same call rebuilds its master free with the current sound rules, and the deck stays where it is. Only a complete deck exports; its artifact manifest returns share.url (present only once the deck is PUBLISHED — decks arrive unpublished and publishing is its own call), video.status/url/durationMs, and pdf.status/url (the PDF only when the request asked for one), and the source table-read ids."},"trailer":{"outputs":["video"],"durationSeconds":{"min":10,"max":90},"availability":"available","price":{"currency":"studio_credits","rate":{"renderResolution":"480p","renderPerSecond":1,"defaultDeliveryResolution":"720p","delivery":{"720p":{"finish":1,"total":2},"1080p":{"finish":4,"total":5}}},"summary":"A trailer is priced by the second: 1 credit a second to render at 480p, plus 1 credit a second to finish at 720p (2 credits a second in all; a 1080p finish is 4 credits a second instead). Planning is free. The exact price is quoted per shot once the plan is ready, before anything renders; location plates and on-screen text are itemized in the same quotes."},"note":"Generated from an APPROVED StoryPlan. `aspectRatio` chooses the shape: 16:9 widescreen (the default), 9:16 vertical, or 1:1 — anything else is rejected because the renderer has no frame size for it. Every shot, the on-screen text composites and the stitched master follow the shape you ask for. The target duration is planned at CUT density: round(target / mean) beats of 4-30s each, with a ~5s mean trailer beat and a ~9s mean pitch-deck chapter (one render call, at most 10s), so a 90s trailer plans about 18 beats; each beat renders as calls of at most 10s. A trailer moves draft → rendering → review → finishing → complete under /trailers. Approving the plan (POST /trailers/{jobId}/plan/approve) is priced first: the first call compiles and voices the beats and quotes the 480p render; `confirmed: true` renders every call at 480p with the cast's own recorded lines (every named character on camera is in the beat's cast, an identity reference each; unnamed people may fill the world). A take that replaces a line with its own reading, or opens off its planned framing, is retaken once automatically at our cost; after that a new take is the writer's priced retake (POST /trailers/{jobId}/beats/render-clip). A moderation refusal is released — nothing is charged — and never retried. In review, read GET /trailers/{jobId}/beats, choose takes (POST /trailers/{jobId}/beats/{beatIndex}/select-take; a re-voiced take is never selected), stage a re-voiced line as a voice-over in a new take (POST /trailers/{jobId}/beats/{beatIndex}/stage-voice-over, priced like a new take), repair one take in place (POST /trailers/{jobId}/beats/{beatIndex}/repair-take, priced first), trim it to a span (POST /trailers/{jobId}/beats/{beatIndex}/trim-take, free; never into a spoken line) or keep it as it is (POST /trailers/{jobId}/beats/{beatIndex}/take-repair-override, free) — every take is watched with its sound and remedied by rule at our cost first (a cut nobody asked for at an end of an otherwise good take is trimmed off, free), and edit beats; then finish (POST /trailers/{jobId}/finish, priced first), which upscales the selected takes to the delivery class — 720p by default, 1080p on request — and stitches the finished master. Every stitched master is watched with its sound, free to the writer, and scored on its quality and its adherence to the shot list (GET /trailers/{jobId}/video-coverage; POST covers it again): while the preview fails that bar the finish is refused unless the writer chooses to finish anyway (`acknowledgeVideoCoverage`). A trailer that failed while it was being assembled (the preview or the finished master) is put back together free with POST /trailers/{jobId}/reassemble, from the takes it already has; in review or complete the same call rebuilds its master free with the current sound rules, and the trailer stays where it is. Only a complete trailer exports."}},"jobs":{"available":true,"statuses":["PENDING","RESERVED","RUNNING","PARTIAL","READY","FAILED","CANCELED"],"artifactStatuses":["PENDING","RUNNING","READY","FAILED"],"scopes":{"create":"story:write","read":"story:read","cancel":"story:write","artifactRead":"artifact:read","renderVideo":"artifact:publish","refine":"artifact:publish","recastVoice":"artifact:publish","modifyVoice":"artifact:publish","recastAvatar":"artifact:publish","updateCast":"artifact:publish","castRead":"artifact:read","finalize":"artifact:publish","scriptRead":"artifact:read","coverageGenerate":"story:write","coverageRead":"artifact:read","musicGenerate":"artifact:publish","musicRead":"artifact:read","sfxManage":"artifact:publish","sfxRead":"artifact:read","locationPlatesRead":"artifact:read","locationPlateRemake":"artifact:publish"},"note":"POST /story-jobs runs queued artifact generation against an APPROVED StoryPlan. Non-blocking development gates: complete/read the Series Bible before screenplay generation; the screenplay adapter auto-loads the saved bible as canonical project truth. After a table_read artifact is READY, read/poll the initial coverage report before finalization, pitch packaging, or later installment work. Table-read jobs queue that first coverage pass when pages are created. Table-read jobs require a reviewed scriptBlueprint and are limited to one table_read script at a time. Pitch-deck jobs require a completed table_read artifact for the same plan/project and return a shareable MP4 plus PDF in the artifact manifest. Credits are reserved at create time and settled when an artifact becomes shareable; canceling a job that has not started (PENDING, RESERVED, or FAILED before it ever started, which also stops it being resumed) releases the reservation; a RUNNING job, and a FAILED job that had started generating, cannot be canceled. All POST mutations require Idempotency-Key. Poll GET /story-jobs/{jobId} for status + progress + artifacts (each artifact carries its manifest URLs). Read individual artifacts via GET /story-jobs/{jobId}/artifacts and GET /artifacts/{artifactId}. Read actual table-read script content via GET /artifacts/{artifactId}/script (scope `artifact:read`). POST /artifacts/{artifactId}/refine (scope `artifact:publish`) revises a table read in place — two calls: price, then `confirmed: true` returns a storyJob to poll and reserves TABLE_READ_REFINE (8 credits). POST /artifacts/{artifactId}/voice (scope `artifact:publish`) recasts a single character voice in place (no new revision, no charge); the cast canon person follows unless `pinToCanon: false`, the cast is readable on `manifest.audio.voiceMap`, and POST /artifacts/{artifactId}/voice/restore puts the previous voice and recording back (free). POST /artifacts/{artifactId}/revoice (scope `artifact:publish`) re-voices ONE character in a finished read and keeps every other line as recorded — reserves TABLE_READ_REVOICE (3 credits), two calls: price, then `confirmed: true`. POST /story-projects/{projectId}/cast-voice/design designs a cast member's voice from a description (one casting voice per candidate, two calls) and POST /story-projects/{projectId}/cast-voice/choose pins one to the cast canon (free); PATCH /story-projects/{projectId}/cast-voice pins an existing voice on anyone on the project cast, in an episode or not (free). POST /artifacts/{artifactId}/finalize (scope `artifact:publish`) durably renders an MP3 or MP4 — both free. The opt-in MP4 at POST /artifacts/{artifactId}/render-video remains available as an alias for `finalize?mode=video`. POST /artifacts/{artifactId}/sfx (scope `artifact:publish`) adds, updates, mutes, retimes, regenerates, or removes a line-level sound-effect cue on the read in place (no new revision, no charge); GET /artifacts/{artifactId}/sfx (scope `artifact:read`) lists the cues. POST /artifacts/{artifactId}/avatar (scope `artifact:publish`) regenerates one character avatar in place (async, queue-backed; returns { character, status, chatToolJobId }). POST /artifacts/{artifactId}/cast (scope `artifact:publish`) batch-updates the cast — voice reassignments and/or per-character avatar renders plus an optional cast-wide avatarStyle restyle — returning the merged voiceMap and one avatar op per queued render. GET /artifacts/{artifactId}/cast (scope `artifact:read`) reads the enriched cast (each character voice merged with avatarUrl/avatarStyle/avatarStatus); poll it after avatar renders until avatarStatus is `ready`. POST /artifacts/{artifactId}/voice-modification (scope `artifact:publish`) applies a voice effect (autotune) to a contiguous range of dialogue entries in place (no new revision, no charge); it returns { modificationId, status } and the rendered audio is projected onto the read once status is `ready`."},"publishing":{"available":true,"media":["audio","video","short_form","text","mixed"],"providers":["rss","apple_podcasts","spotify","youtube","tiktok","instagram","linkedin","x","other"],"seriesStatuses":["draft","active","paused","archived"],"destinationStatuses":["draft","connected","needs_auth","needs_setup","disconnected","failed","archived"],"releaseStatuses":["ready","scheduled","publishing","published","failed","canceled","stale"],"assetStatuses":["ready","stale","failed"],"targetStatuses":["pending","queued","publishing","published","failed","skipped","needs_setup","stale"],"scopes":{"read":"publishing:read","write":"publishing:write","publish":"publishing:publish","connect":"publishing:connect","analytics":"publishing:analytics"},"podcastV1":{"available":true,"feedRoute":"/api/v1/publishing-feeds/{seriesSlug}.xml","assetSource":"Finalized table_read MP3 assets from manifest.audio.recordingUrl.","note":"RSS is first-party and does not require OAuth. Publishing does not create MP3s or MP4s; it promotes durable assets already produced by the artifact/table-read finalize surfaces. Apple Podcasts and Spotify can be tracked as destinations, but provider-specific submission automation is not implemented in v1."},"standingApproval":{"available":true,"field":"standingApprovalKeyId","recurringMedia":["audio"],"refusalReasons":["season_run","not_table_read","not_audio","no_standing_approval","not_bound_key","series_not_exercisable","created_before_grant"],"note":"A show's standing approval lets ONE API key approve its table-read plans (POST /story-plans/{planId}/approve) and publish its audio releases (POST /publishing-releases/{releaseId}/publish) without userConfirmed, for plans and releases created after the grant: never season runs, never while the show is paused or archived. Grant with PATCH /publishing-series/{seriesId} { standingApprovalKeyId: \"<keyId>\" | \"self\", userConfirmed: true }: the app holding the key makes the show from then on (one producer per show; a show Sleeper was making turns off); null revokes it. Turning a show made in Sleeper on (POST /publishing-series/{seriesId}/show/state) grants Sleeper's own approval, by series and with no key, and turning it off ends it; on is refused with 409 show_made_outside while a key holds the approval. Every other caller still needs userConfirmed. A call no grant covers is a 400 validation_failed whose details.reason says why; none of them clears on retry. Every use is recorded: the plan's approvedVia / approvedUnderSeriesId and the release's publishedVia / publishedByKeyId, and a job from a standing-approved plan may request only table_read. Shows are audio-only."},"promptSystem":"SidecarPrompt","sidecars":["story_api_publishing_series_direction","story_api_publishing_release_metadata"],"note":"Publishing is owned by StoryProject and Series Bible context. A PublishingSeries defines medium, format, prompt direction, and destinations; a show made from it is its own resource (/publishing-series/{seriesId}/show). A PublishingRelease promotes an already-finalized StoryArtifact asset, preserving links back to StoryPlan, StoryJob, StoryArtifact, release assets, and provider targets. Iteration happens in Table Read; Publishing schedules and distributes the durable asset the user promotes. Mutating operations require Idempotency-Key."},"brandCoverage":{"available":true,"scopes":{"create":"story:write","read":"story:read"},"dimensions":["thesis","narrative","audience","language","evidence","positioning","cta","brandFit"],"recommendations":["SHIP","REFINE","REWORK"],"promptSystem":"SidecarPrompt","sidecar":"brand_coverage_format","maxContentChars":60000,"note":"POST /brand-coverage (story:write) analyzes a piece of brand / marketing storytelling — landing copy, a brand film, a founder pitch, a case study, a keynote — across eight dimensions (thesis, narrative, audience, language, evidence, positioning, cta, brandFit). Brand, screenplay, and Series Bible reports share one unified coverage lifecycle while retaining rubric-specific payloads and routes. Returns { reportId, status: \"generating\" }; poll GET /brand-coverage/{reportId} (story:read) until status is \"complete\", then read `report` (recommendation SHIP/REFINE/REWORK + per-dimension scores/analysis + topFixes/strengths/comparable). Content is capped at 60k chars; async, queue-backed. Companion \"create\" endpoint: POST /brand-narrative (story:write) turns a brief + optional generation controls (contentType, audience, industry, channel, brandVoice, lengthTarget, sourceFacts) into a finished piece of brand storytelling held to the same craft bar (one thesis, a real arc, specificity over hype, an earned CTA; never fabricates beyond the supplied facts). Accepts an optional `storytellerSubset` to override the signed-in writer's saved storyteller focus for this one piece (key like \"advertiser\"/\"founder\"/\"brand_marketer\" or natural language; invalid values fall through to no per-piece focus rather than silently re-using the saved profile). Synchronous, returns { narrative, modelId }."},"docs":{"capabilityManifest":{"url":"/capabilities.json","availability":"available"},"agentGuidance":{"url":"/api/v1/agent-guidance","availability":"available"},"openapi":{"url":"/api/v1/openapi.json","availability":"available"},"examples":{"url":"/api/v1/examples","availability":"available"},"llms":{"url":"/llms.txt","availability":"available"},"llmsFull":{"url":"/llms-full.txt","availability":"available"},"docsSite":{"url":"/docs","availability":"available"},"agentIndex":{"url":"/agent-index.json","availability":"available"},"searchIndex":{"url":"/docs/search-index.json","availability":"available"},"surfaceContract":{"url":"/docs/surface-contract.json","availability":"available"},"cliCommands":{"url":"/docs/cli-commands.json","availability":"available"},"mcpTools":{"url":"/docs/mcp-tools.json","availability":"available"},"sitemap":{"url":"/sitemap.xml","availability":"available"},"robots":{"url":"/robots.txt","availability":"available"}}}