# LyricWinter API and MCP documentation (full text) > LyricWinter turns stories into fully cast, multi-voice audio with distinct character voices, directed performances, and sound effects. Developers and AI agents can drive LyricWinter Studio through a beta REST API and a remote MCP server. Notes for agents and developers: - The LyricWinter REST API is in beta. Base URL: https://lyricwinter.com/api/v1. Authenticate with `Authorization: Bearer lw_...`; users create scoped keys at https://lyricwinter.com/dashboard under Developers. - Read the OpenAPI 3.1 contract at https://lyricwinter.com/api/v1/openapi.json instead of guessing endpoints, fields, or enum values. - The remote MCP server is https://lyricwinter.com/mcp (Streamable HTTP, OAuth 2.1 with PKCE, or a Studio-scoped API key as a bearer token). - Studio work is asynchronous: start a workflow, then poll it until its status is completed, partially_completed, failed, or cancelled. Creating audio consumes the account's balance, so never repeat a start request with a new client_mutation_id unless the user wants another run. - Every docs page has a Markdown version at the same URL plus `.md`. Requests that send `Accept: text/markdown` to a docs URL also receive Markdown. --- Source: https://lyricwinter.com/docs.md --- title: "LyricWinter API and MCP server" description: "Build with LyricWinter Studio from code or from an AI agent. The REST API (beta) and the remote MCP server turn stories into fully cast, multi-voice audio." canonical_url: https://lyricwinter.com/docs markdown_url: https://lyricwinter.com/docs.md last_updated: 2026-09-30 status: beta --- # LyricWinter API and MCP server > Build with LyricWinter Studio from code or from an AI agent. The REST API (beta) and the remote MCP server turn stories into fully cast, multi-voice audio. LyricWinter turns written stories into fully cast audio: every character gets a distinct voice, narration and dialogue are directed, and sound effects are placed automatically. Developers can build on LyricWinter Studio in two ways: - **The LyricWinter REST API (beta)** at `https://lyricwinter.com/api/v1`, for scripts, backends, and apps. - **The LyricWinter MCP server** at `https://lyricwinter.com/mcp`, for AI agents such as Claude, ChatGPT, Cursor, VS Code, and Codex. Both use the same Studio account, projects, and balance. Anything you create through one is visible in the other and in the [Studio web app](/studio). > [!NOTE] > The LyricWinter API and MCP server are in beta. They are ready to build on, but fields may be added and some behavior may change before general availability. Check these docs for the current contract. ## What can you build with the LyricWinter API? The LyricWinter API exposes the same Studio engine that powers the web and iOS apps: - **Import stories.** Create a standalone story or a multi-chapter project from plain text. - **Generate multi-voice audio.** One asynchronous workflow detects speakers, casts voices, directs emotion and delivery, places sound effects, and renders audio for up to 16 sections at a time. - **Read and edit the script.** Every section is a versioned document of narration and dialogue blocks that you can read and update without overwriting newer work. - **Play and export.** Stream per-block audio, compile a playback manifest, or export MP3, WAV, M4B, synchronized EPUB, or SRT files. - **Share.** Publish a section or a whole project at a stable public link. - **Bring your own sounds.** Upload sound effects and ambience, then let Create Audio place them. ## Should I use the REST API or the MCP server? | If you want to… | Use | | --- | --- | | Automate audio production from your own code or backend | [REST API](/docs/quickstart) | | Let an AI assistant create and manage Studio stories in conversation | [MCP server](/docs/mcp) | | Build a custom app or integration on top of Studio | [REST API](/docs/api) | | Connect Claude, ChatGPT, Cursor, VS Code, or Codex to your account | [MCP server](/docs/mcp) | The MCP server is a thin, authenticated layer over the REST API. It exposes 13 [tools](/docs/mcp-tools) for the most common Studio tasks. The REST API covers more, including exports, sharing, and the sound library. ## How does LyricWinter Studio work? 1. You create a **project** and add **sections** (chapters or scenes) of story text. 2. A **workflow** prepares each section: it splits the text into narration and dialogue blocks, attributes every line to a **speaker**, and casts a voice for each character. 3. The same workflow directs each performance and generates audio. You poll the workflow until it finishes. 4. You play the result block by block, compile a playback manifest, or export a file. Read [Studio concepts](/docs/studio-concepts) for the full model, including versions and safe retries. ## How much does it cost? API and MCP usage draws from the same word balance as the Studio app. Reading data is free. Creating audio consumes words from the account that owns the project. See [pricing](/pricing) for plans and word packs, and check your balance in the [dashboard](/dashboard). ## Machine-readable resources - **OpenAPI 3.1 contract:** [`https://lyricwinter.com/api/v1/openapi.json`](https://lyricwinter.com/api/v1/openapi.json) - **Docs index for LLMs:** [`https://lyricwinter.com/llms.txt`](https://lyricwinter.com/llms.txt), with the full text at [`/llms-full.txt`](https://lyricwinter.com/llms-full.txt) - **Markdown for any page:** add `.md` to the URL, for example [`/docs/quickstart.md`](/docs/quickstart.md) - **MCP OAuth metadata:** [`/.well-known/oauth-protected-resource/mcp`](https://lyricwinter.com/.well-known/oauth-protected-resource/mcp) ## Get help Email [support@lyricwinter.com](mailto:support@lyricwinter.com) and include the `request_id` from any failing response. --- Source: https://lyricwinter.com/docs/quickstart.md --- title: "Quickstart: create Studio audio with the LyricWinter API" description: "Create an API key, add a story, generate multi-voice audio, and fetch playback with the LyricWinter REST API in about ten minutes." canonical_url: https://lyricwinter.com/docs/quickstart markdown_url: https://lyricwinter.com/docs/quickstart.md last_updated: 2026-09-30 status: beta --- # Quickstart: create Studio audio with the LyricWinter API > Create an API key, add a story, generate multi-voice audio, and fetch playback with the LyricWinter REST API in about ten minutes. This quickstart turns a short story into multi-voice audio with the LyricWinter REST API. You will create an API key, add a story, start one Create Audio workflow, wait for it to finish, and fetch playable audio. It takes about ten minutes, most of it waiting for audio. ## Before you begin - A LyricWinter account with words in its balance. Creating audio draws from the same balance as the Studio app. See [pricing](/pricing). - `curl` and a terminal. The last section also shows the same flow in JavaScript and Python. ## Step 1: Create an API key 1. Open your [LyricWinter dashboard](/dashboard) and expand **Developers**. 2. Create a key with the `studio:read`, `studio:write`, and `account:read` scopes. 3. Copy the key. It starts with `lw_` and is shown only once. Export it so the examples below can use it: ```bash export LYRICWINTER_API_KEY="lw_..." ``` Check that the key works: ```bash curl https://lyricwinter.com/api/v1/me \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` Every successful response is wrapped in `{ "data": ..., "request_id": "..." }`. See [Authentication](/docs/authentication) for scopes and key management. ## Step 2: Create a story `POST /studio/sections` creates a standalone Studio story with one section of text. Studio mutations carry two UUIDs: - `actor_session_id` identifies your client session. Generate one per process and reuse it. - `client_mutation_id` identifies this one change. Reuse it only to retry the exact same request. ```bash export ACTOR_SESSION_ID=$(uuidgen | tr 'A-Z' 'a-z') curl -X POST https://lyricwinter.com/api/v1/studio/sections \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "'"$ACTOR_SESSION_ID"'", "client_mutation_id": "'"$(uuidgen | tr 'A-Z' 'a-z')"'", "title": "The Lighthouse", "raw_text": "The storm had not let up for three days. \"Someone has to light the lamp,\" Mira said. Her brother shook his head. \"Not in this wind.\"" }' ``` The response is `201 Created`. It also includes the section's full `document`. Save `data.project_id` and `data.section.id`: ```json { "data": { "project_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "section": { "id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "project_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "title": "The Lighthouse", "document_version": 1 }, "section_order_version": 1, "speaker_registry_version": 1, "idempotent_replay": false }, "request_id": "req_01J9Z3K8QF4" } ``` > [!TIP] > UUIDs must be lowercase. On macOS, `uuidgen` prints uppercase, so pipe it through `tr 'A-Z' 'a-z'`. ## Step 3: Start Create Audio A `create_audio` workflow does everything in one run: it structures the text into narration and dialogue, attributes lines to speakers, casts a voice for each character, directs emotion and delivery, adds sound effects, and generates the audio. ```bash export PROJECT_ID="" export SECTION_ID="" curl -X POST "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/workflows" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 2, "workflow_kind": "create_audio", "actor_session_id": "'"$ACTOR_SESSION_ID"'", "client_mutation_id": "'"$(uuidgen | tr 'A-Z' 'a-z')"'", "section_ids": ["'"$SECTION_ID"'"], "include_generated_sfx": true, "place_project_sounds": true }' ``` The response is `202 Accepted` with the workflow run in `data.run`. Save `data.run.id`. > [!WARNING] > Create Audio consumes words. If a request times out, retry with the **same** `client_mutation_id`. A new ID starts a second, separately billed run. ## Step 4: Wait for the workflow to finish Poll `GET /studio/workflows/{workflowRunId}` every few seconds until `data.run.status` is terminal: | Status | Meaning | | --- | --- | | `queued`, `running`, `canceling` | Still working. Keep polling. | | `completed` | Every step succeeded. | | `partially_completed` | Some steps failed. `data.run.resumable` tells you whether you can resume. | | `failed` | No step succeeded. Check `data.run.error_summary`. | | `cancelled` | The run was cancelled. | ```bash export WORKFLOW_ID="" curl "https://lyricwinter.com/api/v1/studio/workflows/$WORKFLOW_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` `data.run.current_stage` shows what is happening now, such as `casting_voices` or `generating_audio`, and `data.run.completed_step_count` out of `data.run.step_count` gives a progress fraction. ## Step 5: Play the audio `GET /studio/sections/{sectionId}/media` returns one entry per document block, in order. Each block with audio has a short-lived signed `playback_url`: ```bash curl "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/media" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ```json { "data": { "section_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "playback_duration_ms": 14820, "blocks": [ { "block_id": "45c48cce-2e2d-4fbd-8a1c-5e6f7a8b9c30", "freshness": "current", "activity": "idle", "playback_url": "https://storage.googleapis.com/…", "playback_url_expires_at": "2026-09-30T17:15:00.000Z", "content_type": "audio/mpeg" } ] }, "request_id": "req_01J9Z3K8QF4" } ``` Play the blocks in order for the full section, or compile gapless playback with [`GET /studio/sections/{sectionId}/playback-manifest`](/docs/api/media). ## Step 6: Export a file (optional) Exports render the section into one file: `mp3`, `wav`, `m4b`, `epub` (synchronized), or `srt`. An export is pinned to the versions you pass, so read them first: 1. `GET /studio/sections/{sectionId}/document` returns `data.document.document_version` and `data.document.speaker_registry_version`. 2. `GET /studio/projects/{projectId}/mastering-profile` returns `data.mastering_version`. ```bash curl -X POST https://lyricwinter.com/api/v1/studio/exports \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "section_id": "'"$SECTION_ID"'", "document_version": 7, "speaker_registry_version": 3, "mastering_version": 1, "format": "mp3" }' ``` Poll `GET /studio/exports/{exportId}` until `data.status` is `completed`, then download `data.artifact.download_url`. ## Put it together The same flow as one script: ```javascript title="create-audio.mjs" const API = "https://lyricwinter.com/api/v1"; const headers = { Authorization: `Bearer ${process.env.LYRICWINTER_API_KEY}`, "Content-Type": "application/json", }; const actorSessionId = crypto.randomUUID(); async function call(method, path, body) { const response = await fetch(`${API}${path}`, { method, headers, body: body && JSON.stringify(body) }); const payload = await response.json(); if (!response.ok) throw new Error(`${payload.error.code}: ${payload.error.message} (${payload.request_id})`); return payload.data; } const story = await call("POST", "/studio/sections", { schema_version: 1, actor_session_id: actorSessionId, client_mutation_id: crypto.randomUUID(), title: "The Lighthouse", raw_text: 'The storm had not let up for three days. "Someone has to light the lamp," Mira said.', }); const { run } = await call("POST", `/studio/projects/${story.project_id}/workflows`, { schema_version: 2, workflow_kind: "create_audio", actor_session_id: actorSessionId, client_mutation_id: crypto.randomUUID(), section_ids: [story.section.id], }); const terminal = new Set(["completed", "partially_completed", "failed", "cancelled"]); let status = run.status; while (!terminal.has(status)) { await new Promise((resolve) => setTimeout(resolve, 3000)); const workflow = await call("GET", `/studio/workflows/${run.id}`); status = workflow.run.status; console.log(status, workflow.run.current_stage ?? ""); } const media = await call("GET", `/studio/sections/${story.section.id}/media`); console.log(media.blocks.map((block) => block.playback_url).filter(Boolean)); ``` ```python title="create_audio.py" import os, time, uuid import requests API = "https://lyricwinter.com/api/v1" HEADERS = {"Authorization": f"Bearer {os.environ['LYRICWINTER_API_KEY']}"} actor_session_id = str(uuid.uuid4()) def call(method, path, body=None): response = requests.request(method, f"{API}{path}", headers=HEADERS, json=body, timeout=60) payload = response.json() if not response.ok: raise RuntimeError(f"{payload['error']['code']}: {payload['error']['message']} ({payload.get('request_id')})") return payload["data"] story = call("POST", "/studio/sections", { "schema_version": 1, "actor_session_id": actor_session_id, "client_mutation_id": str(uuid.uuid4()), "title": "The Lighthouse", "raw_text": 'The storm had not let up for three days. "Someone has to light the lamp," Mira said.', }) run = call("POST", f"/studio/projects/{story['project_id']}/workflows", { "schema_version": 2, "workflow_kind": "create_audio", "actor_session_id": actor_session_id, "client_mutation_id": str(uuid.uuid4()), "section_ids": [story["section"]["id"]], })["run"] status = run["status"] while status not in {"completed", "partially_completed", "failed", "cancelled"}: time.sleep(3) status = call("GET", f"/studio/workflows/{run['id']}")["run"]["status"] print(status) media = call("GET", f"/studio/sections/{story['section']['id']}/media") print([block["playback_url"] for block in media["blocks"] if block["playback_url"]]) ``` ## Next steps - Learn how projects, sections, documents, and versions fit together in [Studio concepts](/docs/studio-concepts). - Handle conflicts, retries, and errors with [Errors and retries](/docs/errors-and-retries). - Browse every endpoint in the [API reference](/docs/api). - Let an AI agent do all of this for you with the [MCP server](/docs/mcp). --- Source: https://lyricwinter.com/docs/authentication.md --- title: "Authentication and API key scopes" description: "Authenticate LyricWinter API requests with scoped lw_ API keys, choose the right scopes, and understand how MCP clients connect through OAuth 2.1." canonical_url: https://lyricwinter.com/docs/authentication markdown_url: https://lyricwinter.com/docs/authentication.md last_updated: 2026-09-30 status: beta --- # Authentication and API key scopes > Authenticate LyricWinter API requests with scoped lw_ API keys, choose the right scopes, and understand how MCP clients connect through OAuth 2.1. The LyricWinter API authenticates every request with a scoped API key sent as a bearer token. MCP clients can use the same kind of key or sign in through OAuth 2.1, which issues a short-lived Studio key on the user's behalf. ```http Authorization: Bearer lw_... ``` Requests without a valid key return `401 UNAUTHORIZED`. A valid key that lacks the scope an endpoint needs returns `403 FORBIDDEN` with a message such as `API key is missing required scope: studio:write`. ## How do I create a LyricWinter API key? 1. Sign in and open the [dashboard](/dashboard). 2. Expand **Developers** and choose the scopes the key needs. 3. Pick an expiry: never, 7 days, 30 days, 90 days, or 1 year. 4. Create the key and copy it immediately. LyricWinter stores only a hash, so the raw key is shown once. Keys start with `lw_`. Treat them like passwords: keep them in a secret manager or environment variable, never in source control or client-side code. You can also manage keys over the API with `GET /api-keys`, `POST /api-keys`, and `DELETE /api-keys/{apiKeyId}`, which need the `api_keys:read` or `api_keys:write` scope. See the [Account API](/docs/api/account). ## Which scopes does a LyricWinter API key need? Give each key the smallest set of scopes it needs. The scopes used by the endpoints in these docs are: | Scope | Grants | | --- | --- | | `studio:read` | Read Studio projects, sections, documents, speakers, workflows, media, playback, exports, sharing state, and sounds. | | `studio:write` | Create and change Studio content, start and cancel workflows, create exports, publish links, and upload sounds. Includes paid operations. | | `account:read` | Read the account profile with `GET /me`. | | `api_keys:read` | List API keys. | | `api_keys:write` | Create and revoke API keys. | Each endpoint in the [API reference](/docs/api) lists its required scope. The dashboard also offers scopes for other LyricWinter products; they are not needed for Studio. > [!WARNING] > `studio:write` can start Create Audio workflows, which consume words from the account's balance. Give it only to code you trust to spend that balance. ## How do I revoke a key? Revoke a key from the dashboard's **Developers** panel or with `DELETE /api-keys/{apiKeyId}`. Revocation takes effect on the next request. Expired keys stop working automatically. ## How do MCP clients authenticate? The [LyricWinter MCP server](/docs/mcp) accepts two kinds of credentials: - **OAuth 2.1 (recommended).** The client discovers LyricWinter's authorization server from the MCP server's protected-resource metadata, then runs the authorization code flow with PKCE. The user signs in to LyricWinter and approves `studio:read` and, optionally, `studio:write`. LyricWinter issues a one-hour access token and a rotating 30-day refresh token for that client. - **A Studio-scoped API key.** For local development or clients without OAuth support, send a key with `studio:read` and `studio:write` as `Authorization: Bearer lw_...`. OAuth access tokens are bound to the MCP server they were issued for. Each person's connection only reaches their own LyricWinter account. To disconnect an MCP client, revoke its key in the dashboard; that also blocks its refresh token. | OAuth metadata | URL | | --- | --- | | Protected resource | `https://lyricwinter.com/.well-known/oauth-protected-resource/mcp` | | Authorization server | `https://lyricwinter.com/.well-known/oauth-authorization-server` | | Authorization endpoint | `https://lyricwinter.com/oauth/authorize` | | Token endpoint | `https://lyricwinter.com/oauth/token` | | Dynamic client registration | `https://lyricwinter.com/oauth/register` | LyricWinter supports public clients (`token_endpoint_auth_method: none`), PKCE with `S256`, the `authorization_code` and `refresh_token` grants, and dynamic client registration with up to five redirect URIs per client. ## Does the API support browser (CORS) requests? No. Call the LyricWinter API from a server, script, or native app, and keep API keys out of browsers. The public [OpenAPI contract](https://lyricwinter.com/api/v1/openapi.json) is the exception: it allows cross-origin reads so that API tools can load it. --- Source: https://lyricwinter.com/docs/studio-concepts.md --- title: "Studio concepts: projects, sections, documents, and workflows" description: "How LyricWinter Studio models stories as projects, sections, versioned documents, speakers, and asynchronous workflows, and how to edit them safely over the API." canonical_url: https://lyricwinter.com/docs/studio-concepts markdown_url: https://lyricwinter.com/docs/studio-concepts.md last_updated: 2026-09-30 status: beta --- # Studio concepts: projects, sections, documents, and workflows > How LyricWinter Studio models stories as projects, sections, versioned documents, speakers, and asynchronous workflows, and how to edit them safely over the API. LyricWinter Studio models a story as a **project** made of ordered **sections**. Each section has a versioned **document** of narration, dialogue, and sound-effect blocks, each spoken line belongs to a project-wide **speaker**, and long-running work such as generating audio happens in asynchronous **workflows**. This page explains each concept and how to change them safely through the LyricWinter API. ## Projects and unassigned stories A **project** is a named container for related sections, such as the chapters of a book. All sections in a project share one cast, so a character keeps the same voice from chapter to chapter. A **standalone story** is a single section that is not yet part of a named project. `POST /studio/sections` creates one. `GET /studio/projects` returns named projects in `projects` and standalone stories in `unassigned_sections`. A standalone story still has a `project_id`, and every project-scoped endpoint works with it. To turn a standalone story into a named project, call `PATCH /studio/projects/{projectId}` with a name. ## Sections A **section** is one chapter, scene, or episode of story text. Sections in a project are ordered. Add one with `POST /studio/projects/{projectId}/sections`, move one with `PATCH /studio/projects/{projectId}/sections/{sectionId}`, and delete one with `DELETE`. Deletions cannot be undone through the API, so call the matching `deletion-info` endpoint first to see what will be removed. ## Documents and blocks Every section has a **document**: the canonical, structured script of the section. Read it with `GET /studio/sections/{sectionId}/document`. A document contains: - `document_version`, which increases on every change. - `speakers`, the project's current speaker registry, and `speaker_registry_version`. - `blocks`, the script in order. Each block has a `kind`: | Block kind | What it is | | --- | --- | | `narration` | Narrated prose, voiced by the narrator. | | `speech` | A line of dialogue attributed to a speaker. | | `sfx` | A sound effect placed in the timeline. | | `note` | A note kept in the script but not voiced. | | `excluded` | Source text kept in the script but intentionally left out of the audio. | | `unparsed` | Text that has not been structured into narration or dialogue yet. It is not voiced until it is. | A new section starts as unstructured text. A `prepare_sections` or `create_audio` workflow turns it into narration and speech blocks. For a finished speaker-labeled script, `POST /studio/sections/{sectionId}/script` creates speech blocks directly, without a prepare workflow or a hand-built AST. Use one `SPEAKER: spoken text` line per turn. A `[direction]` tag at the start of a line steers the whole turn, and a tag mid-line, as in `TORVALD: [softly] Go, Ingvild. [whispers] Please.`, anchors a direction where it appears; both become tracked directions and are removed from the spoken text. A separate `(silence 4s)` or `(silence 4000ms)` line inserts exact silence after the preceding turn. Supply a `speakers` map whose keys exactly match every label and whose values contain `voice_id` and a complete `generation_profile`. For example, `MOM: [quiet, unsteady] I saw his face.\n(silence 4s)\nMOM: I know.` compiles into two speech blocks with a timed-silence point on the first. Direction uses the selected model's supported control, such as an Inworld turn instruction or an ElevenLabs audio tag; models without a suitable whole-turn or mid-line control reject the direction. The route replaces the section's script, preserves its title and section settings, and returns the canonical document. A matching speaker name reuses and recasts that speaker across the project. It makes no generation or provider calls. Unmatched lines, missing speaker mappings, and unsupported directions fail with `400` rather than being dropped. ## Speakers and casting A **speaker** is a character or narrator in a project. The speaker registry is shared by every section in the project and has its own `speaker_registry_version`. Each speaker has a `voice_id` and a generation profile that controls how its lines are performed. `GET /studio/projects/{projectId}/speakers` lists speakers with how often each one appears. Create Audio casts voices automatically; you can recast in the [Studio app](/studio). ### Preparing a speaker for ElevenLabs v4 Casting and voice preparation are separate. A voice's `default_model` or completed ElevenLabs engine sample does not change a Studio speaker's saved `generation_profile`. There is currently no project-level default generation profile; when no speaker or block override selects a model, Studio uses `inworld-2`. Select `provider: "elevenlabs"` and `model: "eleven_v4"` in the speaker's casting update (or a block override) before checking its readiness. 1. Read `GET /studio/sections/{sectionId}/generation-availability` and find the speech or narration block. Its `generation.requested_profile` identifies the effective model. `generation.status` is `available`, `materialization_required`, or `unavailable`; `generation.options` reports alternatives. 2. If ElevenLabs v4 is `materialization_required`, prepare the assigned voice through `POST /studio/projects/{projectId}/speakers/{speakerId}/voice-materialization-jobs` with `schema_version: 1`, a new `client_mutation_id`, `materialization_kind: "provider_asset"`, `provider: "elevenlabs"`, and `model: "eleven_v4"`. Poll `GET /studio/voice-materialization-jobs/{jobId}` until it completes, then read generation availability again. 3. If you also want to audition the voice, `POST /voices/{voiceId}/engine-samples` with `model: "eleven_v4"` and the voice's current `expected_acoustic_revision` synthesizes the fixed comparison phrase. A successfully completed sample can publish a reusable ElevenLabs provider asset, so it may make Studio ready too. The sample's cached audio is not itself Studio's readiness authority: Studio requires an active provider asset for the voice's current acoustic revision. Always re-read generation availability before generating. For a materializable voice, `generation.reason_code` and the matching option's `reason_code` distinguish `provider_asset_missing` from `acoustic_revision_changed` when a prior-revision provider asset exists. Editing or redesigning a voice can advance its acoustic revision; the old asset then cannot generate new Studio audio. An asset can also be retired or evicted. In either case, prepare the current revision and recheck. Other unavailable conditions use the human-readable `reason` and `diagnostics`; absence of `reason_code` does not mean the block is ready. `POST /ai-voices` currently creates Inworld-designed voices. It has no ElevenLabs Voice Design selector. Preparing such a voice for ElevenLabs v4 changes its synthesis asset, not the design provider or the speaker's selected profile. ## Versions and optimistic concurrency Studio never silently overwrites newer work. Every mutable resource carries a version: | Version | Protects | Where to read it | | --- | --- | --- | | `document_version` | One section's document | `GET /studio/sections/{sectionId}/document` | | `speaker_registry_version` | The project's speakers and casting | The same document response | | `section_order_version` | The order of sections in a project | `GET /studio/projects/{projectId}/sections` | | `mastering_version` | Loudness and mastering settings | `GET /studio/projects/{projectId}/mastering-profile` | When you change something, send the version you last read. If someone else changed it first, the request fails with `409 CONFLICT` instead of overwriting their work. Read the resource again, reapply your change, and retry. Anything that plays or exports audio, such as the playback manifest and exports, is also pinned to these versions, so a stale version returns `409` rather than outdated audio. ## Mutation IDs and actor sessions Studio write requests include two UUIDs in the JSON body: - **`actor_session_id`** identifies the client session making changes. Generate one when your process starts and reuse it for all requests from that process. - **`client_mutation_id`** identifies one intended change. Generate a new one for each new change, and reuse it only when retrying the exact same request after a timeout or network error. Replaying a `client_mutation_id` with the same body returns the original result. Some creation endpoints return `200` with `idempotent_replay: true` instead of `201`. Reusing it with a different body returns `409`. See [Errors and retries](/docs/errors-and-retries). ## Workflows A **workflow** is a durable, asynchronous job over one or more sections. Start one with `POST /studio/projects/{projectId}/workflows` and poll `GET /studio/workflows/{workflowRunId}`. There are two kinds: | `workflow_kind` | What it does | Cost | Sections per run | | --- | --- | --- | --- | | `prepare_sections` | Structures text into narration and dialogue blocks. Use it to review the script before generating audio. | No audio is generated. | Up to 100 | | `create_audio` | Runs the full pipeline: prepare, assign speakers, cast voices, direct emotion and delivery, place sounds, and generate audio. | Consumes words. | Up to 12 with both sound options on, 14 with one, 16 with none | A Create Audio run moves through these `current_stage` values: `preparing_sections`, `assigning_speakers`, `casting_voices`, `planning_emotion`, `directing_delivery`, `placing_project_sounds`, `directing_sfx`, `preparing_voices`, and `generating_audio`. Stages that are turned off are skipped. A run's `status` is one of `queued`, `running`, `canceling`, `completed`, `partially_completed`, `failed`, or `cancelled`. The last four are terminal. Cancel a run with `POST /studio/workflows/{workflowRunId}/cancel`. Cancellation is cooperative, so work that already started may finish first. When `resumable` is `true` on a failed or partially completed run, `POST /studio/workflows/{workflowRunId}/resume` starts a new linked run that picks up from each section's first failed stage instead of redoing finished work. If the balance runs out, the audio step fails with `error_code` `STUDIO_INSUFFICIENT_BALANCE`; add words, then resume. Create Audio has two independent sound options in `schema_version` 2: - `include_generated_sfx` adds AI-generated sound effects where the story calls for them. - `place_project_sounds` places sounds from your [sound library](/docs/api/sounds) that are linked to the project. Both default to `true`. ## Media, playback, and exports - **Media.** `GET /studio/sections/{sectionId}/media` returns one entry per block with its `freshness` (`absent`, `current`, `stale`, or `unsupported`), its `activity` (`idle`, `generating`, or `failed`), and a short-lived signed `playback_url` for the current audio. - **Playback manifest.** `GET /studio/sections/{sectionId}/playback-manifest` compiles the section's audio, gaps, and sound effects into one timeline for gapless playback. Pass the `document_version` and `speaker_registry_version` you read from the document. - **Exports.** `POST /studio/exports` renders a section into `mp3`, `wav`, `m4b`, synchronized `epub`, or `srt`. Poll `GET /studio/exports/{exportId}` and download `artifact.download_url` when `status` is `completed`. Signed URLs expire. Fetch fresh ones from the API instead of storing them. ## Sharing Publish a section with `POST /studio/sections/{sectionId}/share` or a whole project with `POST /studio/projects/{projectId}/share`. Each returns a stable public link that plays the current audio. `DELETE` on the same path revokes it. See the [Sharing API](/docs/api/sharing). --- Source: https://lyricwinter.com/docs/errors-and-retries.md --- title: "Errors, retries, and idempotency" description: "The LyricWinter API response envelope, error codes, request IDs, retry rules, idempotency keys, and client mutation IDs for safe retries of paid operations." canonical_url: https://lyricwinter.com/docs/errors-and-retries markdown_url: https://lyricwinter.com/docs/errors-and-retries.md last_updated: 2026-09-30 status: beta --- # Errors, retries, and idempotency > The LyricWinter API response envelope, error codes, request IDs, retry rules, idempotency keys, and client mutation IDs for safe retries of paid operations. Every LyricWinter API response uses a consistent JSON envelope, and every error carries a stable `code` and a `retryable` flag. Most paid or state-changing requests support safe retries with an idempotency key or a client mutation ID. This page explains how to read errors and when it is safe to retry. ## Response envelope Successful responses wrap the result in `data`: ```json { "data": { "id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10" }, "request_id": "req_01J9Z3K8QF4" } ``` List endpoints that paginate add a `pagination` object next to `data`. The public OpenAPI document at `/api/v1/openapi.json` is the one exception: it is returned unwrapped. ## Error format Errors use the same shape on every endpoint: ```json { "error": { "code": "STUDIO_DOCUMENT_CONFLICT", "message": "Studio changed before this save could commit.", "retryable": false }, "request_id": "req_01J9Z3K8QF4" } ``` - `code` is stable and safe to branch on. `message` is human-readable and may change. - `retryable` is `true` when repeating the same request later can succeed. - `details` is optional structured context, such as the conflicting version. - `request_id` is also sent as the `x-request-id` response header. Include it when you contact support. ## Error codes | HTTP status | `code` | Retryable | What to do | | --- | --- | --- | --- | | 400 | `BAD_REQUEST` | No | Fix the request. `message` names the invalid field. | | 401 | `UNAUTHORIZED` | No | Send a valid, unexpired, unrevoked API key. | | 403 | `FORBIDDEN` | No | The key is missing a required scope, or the resource belongs to another account. | | 404 | `NOT_FOUND` | No | Check the ID. Deleted resources return 404. | | 409 | `CONFLICT` or a Studio conflict code | No | Re-read the resource, then retry with the current version. | | 429 | `RATE_LIMITED` | Yes | Wait for the `Retry-After` header, then retry. | | 500 | `INTERNAL_SERVER_ERROR` | No | Retry once later; if it persists, contact support with the `request_id`. | | 502 | `UPSTREAM_UNAVAILABLE` | Yes | A provider is unavailable. Retry with backoff. | | 503 | `SERVICE_UNAVAILABLE` | Yes | Studio is temporarily unavailable. Retry with backoff. | Studio endpoints add more specific codes. Common ones: | `code` | Meaning | | --- | --- | | `STUDIO_DOCUMENT_CONFLICT` | The document changed since the version you sent. | | `STUDIO_SECTION_ORDER_CONFLICT` | The project's section order changed since the version you sent. | | `STUDIO_IDEMPOTENCY_KEY_REUSED` | A `client_mutation_id` was reused with a different request body. | | `STUDIO_GENERATION_PRICE_CHANGED` | The ElevenLabs v4 price changed before generation started. Review the updated quote, then retry. Status 409. | | `STUDIO_GENERATION_PRICE_REJECTED` | One or more generation targets has an invalid price. Refresh the quote before retrying. Status 409. | | `IDEMPOTENCY_KEY_REQUIRED` | The endpoint requires an `Idempotency-Key` header, for example `POST /studio/exports`. | Workflow steps report their own failures in `error_code` and `error_message` on each step. For example, `STUDIO_INSUFFICIENT_BALANCE` means the account ran out of words during generation. ## When is it safe to retry? - **Network errors and timeouts.** The request may or may not have been applied. Retry with the *same* idempotency key or `client_mutation_id`; never generate a new one for a retry. - **`retryable: true`.** Retry with exponential backoff, starting at about one second and capping at about a minute. - **`429 RATE_LIMITED`.** Wait at least the number of seconds in `Retry-After`. - **`409` conflicts.** Do not retry blindly. Re-read the resource, decide whether your change still applies, and send it with the current version and a new mutation ID. - **Other `4xx` errors.** Fix the request first. ## Idempotency The LyricWinter API uses two idempotency mechanisms. Each endpoint in the [API reference](/docs/api) says which one it uses. **`client_mutation_id` in the body (most Studio writes).** Send a new UUID for each intended change. Replaying the same ID with the same body returns the original result. Some creation endpoints signal a replay with `200` and `idempotent_replay: true` instead of `201`. Reusing an ID with a different body returns `409`. For `POST /studio/sections/{sectionId}/script`, retry an uncertain response with the same body and `client_mutation_id`. The API verifies the original command receipt before accepting a replay, even if the document versions advanced; the response contains the current canonical document, not a historical snapshot. A changed body returns `409`. If the original command can no longer be reconstructed after a capability update, the retry also returns `409` without importing again; read the document to confirm the outcome. This endpoint never starts paid work. **`Idempotency-Key` header (exports and some other endpoints).** Send a unique key of up to 255 characters per logical request. Replaying the same key with the same body returns the original result with the response header `Idempotency-Replayed: true`. Reusing a key with a different body returns `409`. > [!IMPORTANT] > Paid operations such as Create Audio charge once per distinct `client_mutation_id`. Keep the ID with your pending job so a retry after a crash reuses it. ## Polling long-running work Workflows and exports return `202 Accepted` and keep running after the response. Poll the returned resource instead of repeating the start request: - Poll every 2–3 seconds at first, then back off to every 5–10 seconds for long runs. - Stop when the status is terminal. For workflows that is `completed`, `partially_completed`, `failed`, or `cancelled`; for exports it is `completed`, `failed`, `cancelled`, or `superseded`. - Treat signed media URLs as short-lived. Fetch fresh ones when they expire instead of storing them. --- Source: https://lyricwinter.com/docs/api.md --- title: "LyricWinter REST API reference (beta)" description: "Base URL, authentication, response envelope, and every endpoint group in the LyricWinter REST API beta, generated from the public OpenAPI 3.1 contract." canonical_url: https://lyricwinter.com/docs/api markdown_url: https://lyricwinter.com/docs/api.md last_updated: 2026-09-30 status: beta --- # LyricWinter REST API reference (beta) > Base URL, authentication, response envelope, and every endpoint group in the LyricWinter REST API beta, generated from the public OpenAPI 3.1 contract. The LyricWinter REST API is a JSON-over-HTTPS API for LyricWinter Studio. It is in beta. This reference is generated from the public OpenAPI 3.1 contract, the same file that API tools and code generators can download. | Property | Value | | --- | --- | | Base URL | `https://lyricwinter.com/api/v1` | | Authentication | `Authorization: Bearer lw_...` ([details](/docs/authentication)) | | Request format | `Content-Type: application/json`, UTF-8 | | Response format | `{ "data": ..., "request_id": "req_..." }` ([details](/docs/errors-and-retries)) | | IDs | Lowercase UUIDs | | Timestamps | ISO 8601 in UTC | | OpenAPI contract | [`/api/v1/openapi.json`](https://lyricwinter.com/api/v1/openapi.json) | ## Use the OpenAPI contract Load `https://lyricwinter.com/api/v1/openapi.json` into Postman, Insomnia, Bruno, or an OpenAPI code generator to get a typed client for every endpoint below. Coding agents should read the contract instead of guessing field names. ```bash curl https://lyricwinter.com/api/v1/openapi.json -o lyricwinter-openapi.json ``` ## Conventions - **Scopes.** Each endpoint lists the API key scope it requires. Studio endpoints need `studio:read` or `studio:write`. - **Versions.** Writes that change shared state take the version you last read, such as `document_version`, and fail with `409` instead of overwriting newer work. See [Studio concepts](/docs/studio-concepts#versions-and-optimistic-concurrency). - **Safe retries.** Studio writes take a `client_mutation_id`; exports take an `Idempotency-Key` header. See [Errors and retries](/docs/errors-and-retries#idempotency). - **Asynchronous work.** Workflows and exports return `202 Accepted`. Poll the returned resource until it reaches a terminal status. - **Media.** Audio is served from short-lived signed URLs returned by the API. Fetch fresh URLs instead of storing them. ## Endpoint groups ### [Account and API keys](https://lyricwinter.com/docs/api/account) Check API health, read the account behind an API key, and create, list, or revoke scoped API keys. - `GET /health`: [Check API health](https://lyricwinter.com/docs/api/account#get-health) - `GET /me`: [Get the current account](https://lyricwinter.com/docs/api/account#get-current-account-profile) - `GET /api-keys`: [List API keys](https://lyricwinter.com/docs/api/account#list-api-keys) - `POST /api-keys`: [Create an API key](https://lyricwinter.com/docs/api/account#create-api-key) - `DELETE /api-keys/{apiKeyId}`: [Revoke an API key](https://lyricwinter.com/docs/api/account#revoke-api-key) ### [Projects](https://lyricwinter.com/docs/api/projects) A Studio project groups ordered sections (chapters) that share one cast of speakers. Standalone stories appear as unassigned stories until you turn them into a named project. - `GET /studio/projects`: [List projects and standalone stories](https://lyricwinter.com/docs/api/projects#list-studio-projects) - `PATCH /studio/projects/{projectId}`: [Turn a standalone story into a project](https://lyricwinter.com/docs/api/projects#promote-studio-project) - `GET /studio/projects/{projectId}/deletion-info`: [Preview project deletion](https://lyricwinter.com/docs/api/projects#get-studio-project-deletion-info) - `DELETE /studio/projects/{projectId}`: [Delete a project](https://lyricwinter.com/docs/api/projects#delete-studio-project) ### [Sections](https://lyricwinter.com/docs/api/sections) A section is one chapter or scene of story text inside a project. Create a standalone story with one section, add sections to a project, reorder them, or delete them. - `POST /studio/sections`: [Create a standalone story](https://lyricwinter.com/docs/api/sections#create-standalone-studio-section) - `GET /studio/projects/{projectId}/sections`: [List project sections](https://lyricwinter.com/docs/api/sections#list-studio-project-sections) - `POST /studio/projects/{projectId}/sections`: [Add a section to a project](https://lyricwinter.com/docs/api/sections#create-studio-section) - `PATCH /studio/projects/{projectId}/sections/{sectionId}`: [Move a section](https://lyricwinter.com/docs/api/sections#move-studio-section) - `GET /studio/projects/{projectId}/sections/{sectionId}/deletion-info`: [Preview section deletion](https://lyricwinter.com/docs/api/sections#get-studio-section-deletion-info) - `DELETE /studio/projects/{projectId}/sections/{sectionId}`: [Delete a section](https://lyricwinter.com/docs/api/sections#delete-studio-section) ### [Documents and speakers](https://lyricwinter.com/docs/api/documents) Every section has a versioned document of narration and dialogue blocks attributed to speakers. Read the document before editing it or generating audio, and list the project's speakers and casting. - `GET /studio/sections/{sectionId}/document`: [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) - `PATCH /studio/sections/{sectionId}/document`: [Edit a section document](https://lyricwinter.com/docs/api/documents#save-studio-document) - `POST /studio/sections/{sectionId}/script`: [Import a speaker-labeled script](https://lyricwinter.com/docs/api/documents#import-studio-script) - `GET /studio/projects/{projectId}/speakers`: [List project speakers](https://lyricwinter.com/docs/api/documents#get-studio-project-speaker-usage) ### [Workflows](https://lyricwinter.com/docs/api/workflows) Workflows are durable, asynchronous jobs. A prepare workflow structures text into narration and dialogue; a Create Audio workflow casts voices and generates multi-voice audio. Poll a workflow until it reaches a terminal status. - `POST /studio/projects/{projectId}/workflows`: [Start a workflow](https://lyricwinter.com/docs/api/workflows#create-studio-workflow) - `GET /studio/projects/{projectId}/workflows`: [Get the latest project workflow](https://lyricwinter.com/docs/api/workflows#get-latest-studio-workflow) - `GET /studio/workflows/{workflowRunId}`: [Get a workflow](https://lyricwinter.com/docs/api/workflows#get-studio-workflow) - `GET /studio/workflows/{workflowRunId}/progress`: [Get workflow audio progress](https://lyricwinter.com/docs/api/workflows#get-studio-workflow-progress) - `POST /studio/workflows/{workflowRunId}/cancel`: [Cancel a workflow](https://lyricwinter.com/docs/api/workflows#cancel-studio-workflow) - `POST /studio/workflows/{workflowRunId}/resume`: [Resume a failed workflow](https://lyricwinter.com/docs/api/workflows#resume-studio-workflow) ### [Media and playback](https://lyricwinter.com/docs/api/media) Read the generated audio state for a section, compile a playback manifest for the current document version, and mint short-lived URLs for individual media assets. - `GET /studio/sections/{sectionId}/media`: [Get section media](https://lyricwinter.com/docs/api/media#get-studio-section-media) - `GET /studio/sections/{sectionId}/playback-manifest`: [Get a playback manifest](https://lyricwinter.com/docs/api/media#get-studio-playback-manifest) - `POST /studio/media-assets/{assetId}/access-url`: [Create a media access URL](https://lyricwinter.com/docs/api/media#create-studio-media-access-url) ### [Exports](https://lyricwinter.com/docs/api/exports) Render a section into a downloadable MP3, WAV, M4B, synchronized EPUB, or SRT file. Exports are pinned to the current document, cast, and mastering versions and run asynchronously; poll an export until its file is ready. - `GET /studio/projects/{projectId}/mastering-profile`: [Get the mastering profile](https://lyricwinter.com/docs/api/exports#get-studio-mastering-profile) - `GET /studio/exports`: [List exports](https://lyricwinter.com/docs/api/exports#list-studio-exports) - `POST /studio/exports`: [Create an export](https://lyricwinter.com/docs/api/exports#create-studio-export) - `GET /studio/exports/{exportId}`: [Get an export](https://lyricwinter.com/docs/api/exports#get-studio-export) - `POST /studio/exports/{exportId}/cancel`: [Cancel an export](https://lyricwinter.com/docs/api/exports#cancel-studio-export) ### [Sharing](https://lyricwinter.com/docs/api/sharing) Publish a section or a whole project at a stable public link, read its sharing state, or revoke the link. - `GET /studio/sections/{sectionId}/share`: [Get section sharing](https://lyricwinter.com/docs/api/sharing#get-studio-public-share) - `POST /studio/sections/{sectionId}/share`: [Share a section](https://lyricwinter.com/docs/api/sharing#ensure-studio-public-share) - `DELETE /studio/sections/{sectionId}/share`: [Stop sharing a section](https://lyricwinter.com/docs/api/sharing#revoke-studio-public-share) - `GET /studio/projects/{projectId}/share`: [Get project sharing](https://lyricwinter.com/docs/api/sharing#get-studio-project-share-state) - `POST /studio/projects/{projectId}/share`: [Share a project](https://lyricwinter.com/docs/api/sharing#publish-studio-project) - `DELETE /studio/projects/{projectId}/share`: [Stop sharing a project](https://lyricwinter.com/docs/api/sharing#revoke-studio-project-share) ### [Sound library](https://lyricwinter.com/docs/api/sounds) Upload your own sound effects and ambience into the Studio sound library, manage their metadata, and link them to projects so Create Audio can place them automatically. - `GET /studio/sounds`: [List sounds](https://lyricwinter.com/docs/api/sounds#list-studio-sounds) - `POST /studio/sounds`: [Start a sound upload](https://lyricwinter.com/docs/api/sounds#create-studio-sound-upload) - `POST /studio/sounds/{soundId}/uploads/{uploadId}/complete`: [Complete a sound upload](https://lyricwinter.com/docs/api/sounds#complete-studio-sound-upload) - `GET /studio/sounds/{soundId}/uploads/latest`: [Get sound upload status](https://lyricwinter.com/docs/api/sounds#get-studio-sound-upload-status) - `GET /studio/sounds/{soundId}`: [Get a sound](https://lyricwinter.com/docs/api/sounds#get-studio-sound) - `PATCH /studio/sounds/{soundId}`: [Update a sound](https://lyricwinter.com/docs/api/sounds#update-studio-sound) - `DELETE /studio/sounds/{soundId}`: [Delete a sound](https://lyricwinter.com/docs/api/sounds#delete-studio-sound) - `POST /studio/sounds/{soundId}/preview-url`: [Create a sound preview URL](https://lyricwinter.com/docs/api/sounds#create-studio-sound-preview-url) - `GET /studio/projects/{projectId}/sounds`: [List project sounds](https://lyricwinter.com/docs/api/sounds#list-studio-project-sounds) - `POST /studio/projects/{projectId}/sounds`: [Link a sound to a project](https://lyricwinter.com/docs/api/sounds#add-studio-project-sound) - `DELETE /studio/projects/{projectId}/sounds/{soundId}`: [Unlink a sound from a project](https://lyricwinter.com/docs/api/sounds#remove-studio-project-sound) --- Source: https://lyricwinter.com/docs/api/account.md --- title: "Account and API keys API" description: "Check API health, read the account behind an API key, and create, list, or revoke scoped API keys." canonical_url: https://lyricwinter.com/docs/api/account markdown_url: https://lyricwinter.com/docs/api/account.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Account and API keys > Check API health, read the account behind an API key, and create, list, or revoke scoped API keys. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Check API health](https://lyricwinter.com/docs/api/account#get-health): `GET /health` - [Get the current account](https://lyricwinter.com/docs/api/account#get-current-account-profile): `GET /me` - [List API keys](https://lyricwinter.com/docs/api/account#list-api-keys): `GET /api-keys` - [Create an API key](https://lyricwinter.com/docs/api/account#create-api-key): `POST /api-keys` - [Revoke an API key](https://lyricwinter.com/docs/api/account#revoke-api-key): `DELETE /api-keys/{apiKeyId}` ### Check API health `GET /health` Authentication: none · Operation ID: `getHealth` Returns a static payload when the LyricWinter API is reachable. It needs no authentication and performs no account checks. #### Response 200 Stable API health response. Fields inside `data`: - `ok` (true; required) - `service` ("lyricwinter"; required) - `version` ("v1"; required) #### Example request ```bash curl "https://lyricwinter.com/api/v1/health" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Get the current account `GET /me` Scope: `account:read` · Operation ID: `getCurrentAccountProfile` Returns the profile of the account that owns the API key: its ID, username, display name, avatar URL, and email. #### Response 200 Current account profile. Fields inside `data`: - `profile` (object; required) - `id` (string; required) - `username` (string | null, 3–30 chars; required) - `name` (string | null, max 100 chars; required) - `image` (string | null, uri; required) - `email` (string | null, email; required) Errors: `401`, `403`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/me" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### List API keys `GET /api-keys` Scope: `api_keys:read` · Operation ID: `listApiKeys` Lists metadata for the account's API keys, including name, scopes, expiry, last use, and revocation time. Raw keys are never returned. #### Response 200 Caller-owned API key metadata without raw key material or token hashes. Fields inside `data`: - `api_keys` (array of object; required) - `id` (string, uuid; required) - `name` (string, 1–80 chars; required) - `token_prefix` (string, pattern ^lw_[A-Za-z0-9_-]{8,32}$; required): Safe display prefix for identifying a key. - `scopes` (array of string, one of `account:read`, `account:write`, `api_keys:read`, `api_keys:write`, `audio_runs:read`, `audio_runs:write`, `billing:read`, `billing:write`, `demo_videos:read`, `demo_videos:write`, `feedback:write`, `projects:read`, `projects:write`, `reactions:read`, `reactions:write`, `stories:read`, `stories:write`, `studio:read`, `studio:write`, `voice_pins:read`, `voice_pins:write`, `voice_actors:read`, `voice_shares:read`, `voice_shares:write`, `voices:read`, `voices:write`; required) - `expires_at` (string | null, date-time; required) - `last_used_at` (string | null, date-time; required) - `revoked_at` (string | null, date-time; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `401`, `403`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/api-keys" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Create an API key `POST /api-keys` Scope: `api_keys:write` · Operation ID: `createApiKey` Creates a scoped API key. The response contains `raw_key` exactly once; LyricWinter stores only a hash. When you call this endpoint with an API key, the new key can only have scopes that the calling key already holds. #### Request body (application/json, required) - `name` (string, 1–80 chars; required) - `scopes` (array of string, min 1 items, one of `account:read`, `account:write`, `api_keys:read`, `api_keys:write`, `audio_runs:read`, `audio_runs:write`, `billing:read`, `billing:write`, `demo_videos:read`, `demo_videos:write`, `feedback:write`, `projects:read`, `projects:write`, `reactions:read`, `reactions:write`, `stories:read`, `stories:write`, `studio:read`, `studio:write`, `voice_pins:read`, `voice_pins:write`, `voice_actors:read`, `voice_shares:read`, `voice_shares:write`, `voices:read`, `voices:write`; required): Duplicate scopes are accepted and deduplicated. - `expires_at` (string | null, date-time; optional): Optional expiration timestamp. Null or omitted creates a non-expiring key. #### Response 201 API key created. The raw key is returned only in this response. Fields inside `data`: - `api_key` (object; required) - `id` (string, uuid; required) - `name` (string, 1–80 chars; required) - `token_prefix` (string, pattern ^lw_[A-Za-z0-9_-]{8,32}$; required): Safe display prefix for identifying a key. - `scopes` (array of string, one of `account:read`, `account:write`, `api_keys:read`, `api_keys:write`, `audio_runs:read`, `audio_runs:write`, `billing:read`, `billing:write`, `demo_videos:read`, `demo_videos:write`, `feedback:write`, `projects:read`, `projects:write`, `reactions:read`, `reactions:write`, `stories:read`, `stories:write`, `studio:read`, `studio:write`, `voice_pins:read`, `voice_pins:write`, `voice_actors:read`, `voice_shares:read`, `voice_shares:write`, `voices:read`, `voices:write`; required) - `expires_at` (string | null, date-time; required) - `last_used_at` (string | null, date-time; required) - `revoked_at` (string | null, date-time; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `raw_key` (string, pattern ^lw_[A-Za-z0-9_-]{8,32}_[A-Za-z0-9_-]+$; required): Display-once raw API key. The server stores only a SHA-256 hash. Errors: `400`, `401`, `403`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/api-keys" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My integration", "scopes": [ "account:read" ] }' ``` ### Revoke an API key `DELETE /api-keys/{apiKeyId}` Scope: `api_keys:write` · Operation ID: `revokeApiKey` Revokes one of the account's API keys immediately. Revoking a key that is already revoked succeeds without changes. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `apiKeyId` | string, uuid | Yes | | #### Response 200 Caller-owned API key was revoked, or was already revoked. Fields inside `data`: - `ok` (true; required) - `api_key` (object; required) - `id` (string, uuid; required) - `name` (string, 1–80 chars; required) - `token_prefix` (string, pattern ^lw_[A-Za-z0-9_-]{8,32}$; required): Safe display prefix for identifying a key. - `scopes` (array of string, one of `account:read`, `account:write`, `api_keys:read`, `api_keys:write`, `audio_runs:read`, `audio_runs:write`, `billing:read`, `billing:write`, `demo_videos:read`, `demo_videos:write`, `feedback:write`, `projects:read`, `projects:write`, `reactions:read`, `reactions:write`, `stories:read`, `stories:write`, `studio:read`, `studio:write`, `voice_pins:read`, `voice_pins:write`, `voice_actors:read`, `voice_shares:read`, `voice_shares:write`, `voices:read`, `voices:write`; required) - `expires_at` (string | null, date-time; required) - `last_used_at` (string | null, date-time; required) - `revoked_at` (string | null, date-time; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `500` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/api-keys/$API_KEY_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` --- Source: https://lyricwinter.com/docs/api/projects.md --- title: "Projects API" description: "A Studio project groups ordered sections (chapters) that share one cast of speakers. Standalone stories appear as unassigned stories until you turn them into a named project." canonical_url: https://lyricwinter.com/docs/api/projects markdown_url: https://lyricwinter.com/docs/api/projects.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Projects > A Studio project groups ordered sections (chapters) that share one cast of speakers. Standalone stories appear as unassigned stories until you turn them into a named project. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [List projects and standalone stories](https://lyricwinter.com/docs/api/projects#list-studio-projects): `GET /studio/projects` - [Turn a standalone story into a project](https://lyricwinter.com/docs/api/projects#promote-studio-project): `PATCH /studio/projects/{projectId}` - [Preview project deletion](https://lyricwinter.com/docs/api/projects#get-studio-project-deletion-info): `GET /studio/projects/{projectId}/deletion-info` - [Delete a project](https://lyricwinter.com/docs/api/projects#delete-studio-project): `DELETE /studio/projects/{projectId}` ### List projects and standalone stories `GET /studio/projects` Scope: `studio:read` · Operation ID: `listStudioProjects` Returns named projects in `projects` and standalone stories in `unassigned_sections`. Every standalone story still has a `project_id` that works with project-scoped endpoints. #### Response 200 Named projects and unassigned Studio story summaries. Fields inside `data`: - `projects` (array of object; required) - `id` (string, uuid; required) - `name` (string; required) - `project_kind` ("named"; required) - `unassigned_sections` (array of object; required): Standalone Studio stories shown under the virtual Unassigned group, one section per hidden project. - `project_id` (string, uuid; required): Hidden standalone project container that owns the story. - `section` (object; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `title` (string; required) - `document_version` (integer, ≥ 1; required) - `source_text_length` (integer, ≥ 0; required): Length of the section's exact source-text projection in UTF-16 code units. - `activity_status` (string, one of `blank`, `working`, `needs_audio`, `audio_ready`, `audio_failed`; required): Navigator state: working (active workflow or pending audio), audio_failed (latest audio attempt failed), audio_ready (every generatable block has current audio and no unparsed text remains), needs_audio (generatable or unparsed text without complete current audio), or blank (nothing to generate). - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `401`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Turn a standalone story into a project `PATCH /studio/projects/{projectId}` Scope: `studio:write` · Operation ID: `promoteStudioProject` Gives a standalone story a project name so you can add more sections to it. Its section, cast, audio, and share links are kept. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `name` (string, min 1 chars, pattern \S; required): Project name. Leading and trailing whitespace is trimmed; the trimmed name must be 1-120 characters and must not contain null characters or malformed Unicode. #### Response 200 The promoted named project, or the existing project when it is already named with the same name. Fields inside `data`: - `project` (object; required) - `id` (string, uuid; required) - `name` (string; required) - `project_kind` ("named"; required) Errors: `400`, `401`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X PATCH "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "name": "My integration" }' ``` ### Preview project deletion `GET /studio/projects/{projectId}/deletion-info` Scope: `studio:read` · Operation ID: `getStudioProjectDeletionInfo` Returns counts of the sections, script blocks, generated audio, and active share links that deleting the project would remove. Call it before deleting. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Response 200 Project deletion preview. Fields inside `data`: - `project_id` (string, uuid; required) - `section_count` (integer, ≥ 0; required) - `other_story_count` (integer, ≥ 0; required) - `block_count` (integer, ≥ 0; required) - `audio_run_count` (integer, ≥ 0; required) - `clip_count` (integer, ≥ 0; required) - `studio_media_count` (integer, ≥ 0; required) - `active_share_link_count` (integer, ≥ 0; required) - `ai_designed_voice_count` (integer, ≥ 0; required) Errors: `401`, `404`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/deletion-info" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Delete a project `DELETE /studio/projects/{projectId}` Scope: `studio:write` · Operation ID: `deleteStudioProject` Deletes a project with all of its sections, in-progress work, playback, and share links. This cannot be undone through the API. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `expected_project_kind` (string, one of `standalone`, `named`; required): The project kind the deleting client most recently observed. The server rejects deletion if the live kind has changed. - `delete_ai_designed_voices` (boolean; required) #### Response 200 Project deletion counts and optional AI-designed voice cleanup result. Fields inside `data`: - `success` (true; required) - `project_id` (string, uuid; required) - `mode` ("project_and_stories"; required) - `stories_deleted` (integer, ≥ 0; required) - `audio_runs_deleted` (integer, ≥ 0; required) - `clips_deleted` (integer, ≥ 0; required) - `public_stories_deleted` (integer, ≥ 0; required) - `shared_stories_deleted` (integer, ≥ 0; required) - `active_share_links_deleted` (integer, ≥ 0; required) - `studio_sections_deleted` (integer, ≥ 0; required) - `studio_media_marked_for_deletion` (integer, ≥ 0; required) - `files_deleted` (integer, ≥ 0; required) - `ai_designed_voices_deleted` (integer, ≥ 0; required) - `ai_designed_voices_retained` (integer, ≥ 0; required) Errors: `400`, `401`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "expected_project_kind": "standalone", "delete_ai_designed_voices": true }' ``` --- Source: https://lyricwinter.com/docs/api/sections.md --- title: "Sections API" description: "A section is one chapter or scene of story text inside a project. Create a standalone story with one section, add sections to a project, reorder them, or delete them." canonical_url: https://lyricwinter.com/docs/api/sections markdown_url: https://lyricwinter.com/docs/api/sections.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Sections > A section is one chapter or scene of story text inside a project. Create a standalone story with one section, add sections to a project, reorder them, or delete them. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Create a standalone story](https://lyricwinter.com/docs/api/sections#create-standalone-studio-section): `POST /studio/sections` - [List project sections](https://lyricwinter.com/docs/api/sections#list-studio-project-sections): `GET /studio/projects/{projectId}/sections` - [Add a section to a project](https://lyricwinter.com/docs/api/sections#create-studio-section): `POST /studio/projects/{projectId}/sections` - [Move a section](https://lyricwinter.com/docs/api/sections#move-studio-section): `PATCH /studio/projects/{projectId}/sections/{sectionId}` - [Preview section deletion](https://lyricwinter.com/docs/api/sections#get-studio-section-deletion-info): `GET /studio/projects/{projectId}/sections/{sectionId}/deletion-info` - [Delete a section](https://lyricwinter.com/docs/api/sections#delete-studio-section): `DELETE /studio/projects/{projectId}/sections/{sectionId}` ### Create a standalone story `POST /studio/sections` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `createStandaloneStudioSection` Creates a standalone Studio story with one section containing the exact text you send. Returns `201` with the new project ID, section, and document, or `200` with `idempotent_replay: true` when the same `client_mutation_id` was already applied. #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required) - `client_mutation_id` (string, uuid; required): Idempotency key; a replay with the same canonical request returns the committed result. - `title` (string, max 500 chars; required): At most 500 UTF-16 code units. Must not contain null characters or malformed Unicode. - `raw_text` (string; required): Exact section text stored as one unparsed block. At most 8 MiB of UTF-8 (the whole request body is also limited to 8 MiB). Must not contain null characters or malformed Unicode. #### Response 200 Idempotent replay of the existing standalone story. Fields inside `data`: - `project_id` (string, uuid; required) - `section` (object; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `title` (string; required) - `document_version` (integer, ≥ 1; required) - `source_text_length` (integer, ≥ 0; required): Length of the section's exact source-text projection in UTF-16 code units. - `activity_status` (string, one of `blank`, `working`, `needs_audio`, `audio_ready`, `audio_failed`; required): Navigator state: working (active workflow or pending audio), audio_failed (latest audio attempt failed), audio_ready (every generatable block has current audio and no unparsed text remains), needs_audio (generatable or unparsed text without complete current audio), or blank (nothing to generate). - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. See [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) for its fields. - `section_order_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `idempotent_replay` (boolean; required): True when client_mutation_id matched an already committed creation (HTTP 200); false for a new section (HTTP 201). #### Response 201 Standalone Studio story created. Fields inside `data`: - `project_id` (string, uuid; required) - `section` (object; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `title` (string; required) - `document_version` (integer, ≥ 1; required) - `source_text_length` (integer, ≥ 0; required): Length of the section's exact source-text projection in UTF-16 code units. - `activity_status` (string, one of `blank`, `working`, `needs_audio`, `audio_ready`, `audio_failed`; required): Navigator state: working (active workflow or pending audio), audio_failed (latest audio attempt failed), audio_ready (every generatable block has current audio and no unparsed text remains), needs_audio (generatable or unparsed text without complete current audio), or blank (nothing to generate). - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. See [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) for its fields. - `section_order_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `idempotent_replay` (boolean; required): True when client_mutation_id matched an already committed creation (HTTP 200); false for a new section (HTTP 201). Errors: `400`, `401`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/sections" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "title": "Chapter One", "raw_text": "A bell rang. \"Who is there?\" Mira asked." }' ``` ### List project sections `GET /studio/projects/{projectId}/sections` Scope: `studio:read` · Operation ID: `listStudioProjectSections` Returns a project's sections in order with the project's `section_order_version` and `speaker_registry_version`. A project that has not been opened in Studio yet returns `initialized: false`. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Response 200 Studio project state and sections. Fields inside `data`: - `project_id` (string, uuid; required) - `initialized` (boolean; required): False when the owned project has no Studio state yet; the nullable fields are then null and sections is empty. - `casting_defaults_mode` (string | null, one of `apply_exact`, `suggest`, `ignore`; required) - `default_automation_profile` (string | null, one of `quick`, `balanced`, `directed`; required) - `section_order_version` (integer | null, ≥ 1; required) - `speaker_registry_version` (integer | null, ≥ 1; required) - `sections` (array of object; required): Active sections in project order. - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `title` (string; required) - `document_version` (integer, ≥ 1; required) - `source_text_length` (integer, ≥ 0; required): Length of the section's exact source-text projection in UTF-16 code units. - `activity_status` (string, one of `blank`, `working`, `needs_audio`, `audio_ready`, `audio_failed`; required): Navigator state: working (active workflow or pending audio), audio_failed (latest audio attempt failed), audio_ready (every generatable block has current audio and no unparsed text remains), needs_audio (generatable or unparsed text without complete current audio), or blank (nothing to generate). - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `400`, `401`, `403`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sections" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Add a section to a project `POST /studio/projects/{projectId}/sections` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `createStudioSection` Adds one section of unstructured text to a project. Send the `section_order_version` you last read as `base_section_order_version`; a stale version returns `409`. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required) - `client_mutation_id` (string, uuid; required): Idempotency key; a replay with the same canonical request returns the committed result. - `base_section_order_version` (integer | null, ≥ 1; required): Section-order version the client last observed; null when the project has not initialized Studio. - `title` (string, max 500 chars; required): At most 500 UTF-16 code units. Must not contain null characters or malformed Unicode. - `raw_text` (string; required): Exact section text stored as one unparsed block. At most 8 MiB of UTF-8 (the whole request body is also limited to 8 MiB). Must not contain null characters or malformed Unicode. #### Response 200 Idempotent replay of the existing section. Fields inside `data`: - `project_id` (string, uuid; required) - `section` (object; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `title` (string; required) - `document_version` (integer, ≥ 1; required) - `source_text_length` (integer, ≥ 0; required): Length of the section's exact source-text projection in UTF-16 code units. - `activity_status` (string, one of `blank`, `working`, `needs_audio`, `audio_ready`, `audio_failed`; required): Navigator state: working (active workflow or pending audio), audio_failed (latest audio attempt failed), audio_ready (every generatable block has current audio and no unparsed text remains), needs_audio (generatable or unparsed text without complete current audio), or blank (nothing to generate). - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. See [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) for its fields. - `section_order_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `idempotent_replay` (boolean; required): True when client_mutation_id matched an already committed creation (HTTP 200); false for a new section (HTTP 201). #### Response 201 Studio section created. Fields inside `data`: - `project_id` (string, uuid; required) - `section` (object; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `title` (string; required) - `document_version` (integer, ≥ 1; required) - `source_text_length` (integer, ≥ 0; required): Length of the section's exact source-text projection in UTF-16 code units. - `activity_status` (string, one of `blank`, `working`, `needs_audio`, `audio_ready`, `audio_failed`; required): Navigator state: working (active workflow or pending audio), audio_failed (latest audio attempt failed), audio_ready (every generatable block has current audio and no unparsed text remains), needs_audio (generatable or unparsed text without complete current audio), or blank (nothing to generate). - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. See [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) for its fields. - `section_order_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `idempotent_replay` (boolean; required): True when client_mutation_id matched an already committed creation (HTTP 200); false for a new section (HTTP 201). Errors: `400`, `401`, `403`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sections" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "base_section_order_version": 1, "title": "Chapter One", "raw_text": "A bell rang. \"Who is there?\" Mira asked." }' ``` ### Move a section `PATCH /studio/projects/{projectId}/sections/{sectionId}` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `moveStudioSection` Moves a section to a new position in the project. Send the current `section_order_version`; a stale version returns `409`. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | | `sectionId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required) - `client_mutation_id` (string, uuid; required): Idempotency key; a replay with the same canonical request returns the committed result. - `base_section_order_version` (integer, ≥ 1; required) - `to_index` (integer, 0–4999; required): Zero-based target index in the active section order. #### Response 200 Section move committed or replayed. Fields inside `data`: - `operation` (string, one of `move_section`, `delete_section`; required): move_section for PATCH and delete_section for DELETE. - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `section_order_version` (integer, ≥ 1; required): Section-order version committed by this mutation. - `idempotent_replay` (boolean; required) Errors: `400`, `401`, `403`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X PATCH "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sections/$SECTION_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "base_section_order_version": 1, "to_index": 1 }' ``` ### Preview section deletion `GET /studio/projects/{projectId}/sections/{sectionId}/deletion-info` Scope: `studio:read` · Operation ID: `getStudioSectionDeletionInfo` Returns counts of the script blocks, generated clips, and active share links that deleting the section would make inaccessible. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | | `sectionId` | string, uuid | Yes | | #### Response 200 Section deletion preview. Fields inside `data`: - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `block_count` (integer, ≥ 0; required) - `generated_clip_count` (integer, ≥ 0; required) - `active_share_link_count` (integer, ≥ 0; required) Errors: `400`, `401`, `403`, `404`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sections/$SECTION_ID/deletion-info" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Delete a section `DELETE /studio/projects/{projectId}/sections/{sectionId}` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `deleteStudioSection` Deletes a section after checking both the section order version and the document version. This cannot be undone through the API. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | | `sectionId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required) - `client_mutation_id` (string, uuid; required): Idempotency key; a replay with the same canonical request returns the committed result. - `base_section_order_version` (integer, ≥ 1; required) - `base_document_version` (integer, ≥ 1; required) #### Response 200 Section deletion committed or replayed. Fields inside `data`: - `operation` (string, one of `move_section`, `delete_section`; required): move_section for PATCH and delete_section for DELETE. - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `section_order_version` (integer, ≥ 1; required): Section-order version committed by this mutation. - `idempotent_replay` (boolean; required) Errors: `400`, `401`, `403`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sections/$SECTION_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "base_section_order_version": 1, "base_document_version": 1 }' ``` --- Source: https://lyricwinter.com/docs/api/documents.md --- title: "Documents and speakers API" description: "Every section has a versioned document of narration and dialogue blocks attributed to speakers. Read the document before editing it or generating audio, and list the project's speakers and casting." canonical_url: https://lyricwinter.com/docs/api/documents markdown_url: https://lyricwinter.com/docs/api/documents.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Documents and speakers > Every section has a versioned document of narration and dialogue blocks attributed to speakers. Read the document before editing it or generating audio, and list the project's speakers and casting. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document): `GET /studio/sections/{sectionId}/document` - [Edit a section document](https://lyricwinter.com/docs/api/documents#save-studio-document): `PATCH /studio/sections/{sectionId}/document` - [Import a speaker-labeled script](https://lyricwinter.com/docs/api/documents#import-studio-script): `POST /studio/sections/{sectionId}/script` - [List project speakers](https://lyricwinter.com/docs/api/documents#get-studio-project-speaker-usage): `GET /studio/projects/{projectId}/speakers` ### Get a section document `GET /studio/sections/{sectionId}/document` Scope: `studio:read` · Operation ID: `getStudioDocument` Returns the section's current document: its `document_version`, blocks, the project's speakers and `speaker_registry_version`, and whether section sound effects are enabled. Read it before editing the section or generating audio. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Response 200 Canonical Studio document and optional recovery draft. Fields inside `data`: - `actor_user_id` (string, uuid; required): Authenticated caller; clients partition local recovery state by this ID. - `project_id` (string, uuid; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. - `blocks` (array of object, max 100000 items; required) - One of: - **kind: "speech"** - `block_version` (integer, > 0; required) - `content` (object; required) - `annotations` (array of object; required) - One of: - **kind: "exclusion"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("exclusion"; required) - `reason` (string, one of `speaker_label`, `stage_direction`, `metadata`, `user`; required) - `start_utf16` (integer, ≥ 0; required) - **kind: "pronunciation"** - `alphabet` (string, one of `ipa`, `x-sampa`, `cmu`, `alias`; required) - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("pronunciation"; required) - `start_utf16` (integer, ≥ 0; required) - `value` (string, 1–500 chars; required) - **kind: "extension"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `payload` (object | null; required) - `start_utf16` (integer, ≥ 0; required) - `version` (integer, > 0; required) - `delivery_control_sets` (array of object, max 32 items; required) - `capability_revision` (string, 1–200 chars; required) - `directives` (array of StudioAstDeliveryDirective, max 256 items; required) - `format` (string, one of `eleven-v4-1`, `inworld-tts2-1`, `inworld-tts2-experimental-1`, `inworld-tts15-max-1`, `inworld-tts15-mini-1`, `fish-s2-inline-1`, `fish-s2.1-inline-1`, `cartesia-sonic35-1`, `openai-gpt4o-mini-tts-1`, `openai-tts1-hd-1`, `openai-tts1-1`, `smallest-lightning31-1`, `chatterbox-classic-1`, `zyphra-zonos2-cloud-1`, `f5tts-v1-1`, `misotts-8b-1`; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `model` (string, 1–200 chars; required) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `provider_text_override` (string, 1–65536 chars, pattern [\s\S]*\S[\s\S]*; optional) - `schema_version` (string, one of `studio-delivery-control-set-1`, `studio-delivery-control-set-2`; required) - `settings` (array of StudioAstDeliverySetting, max 32 items; required) - `source_fingerprint` (string | null, pattern ^[0-9a-f]{64}$; required) - `source_utf16_length` (integer | null, ≥ 0; required) - `points` (array of object; required) - One of: - **kind: "timed_silence"** - `duration_ms` (integer, 1–300000; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("timed_silence"; required) - `offset_utf16` (integer, ≥ 0; required) - **kind: "extension"** - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `offset_utf16` (integer, ≥ 0; required) - `payload` (object | null; required) - `version` (integer, > 0; required) - `schema_version` ("studio-ast-2"; required) - `text` (string, max 65536 chars; required): Exact block text; at most 65,536 UTF-16 code units. Annotation, point, and delivery offsets are UTF-16 offsets into this string. - `type` ("text"; required) - `generation_profile_override` (object | null; required) - `controls` (string, one of `strict`, `best_effort`; optional) - `fallback` (string, one of `strict`, `best_effort`; optional) - `model` (string, 1–200 chars; optional) - `options` (map of boolean | string | integer | number | array of StudioAstJsonValue | map of StudioAstJsonValue | null; optional) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; optional) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("speech"; required) - `placement` (any | null; required) - `speaker_id` (string | null, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - **kind: "narration"** - `block_version` (integer, > 0; required) - `content` (object; required) - `annotations` (array of object; required) - One of: - **kind: "exclusion"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("exclusion"; required) - `reason` (string, one of `speaker_label`, `stage_direction`, `metadata`, `user`; required) - `start_utf16` (integer, ≥ 0; required) - **kind: "pronunciation"** - `alphabet` (string, one of `ipa`, `x-sampa`, `cmu`, `alias`; required) - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("pronunciation"; required) - `start_utf16` (integer, ≥ 0; required) - `value` (string, 1–500 chars; required) - **kind: "extension"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `payload` (object | null; required) - `start_utf16` (integer, ≥ 0; required) - `version` (integer, > 0; required) - `delivery_control_sets` (array of object, max 32 items; required) - `capability_revision` (string, 1–200 chars; required) - `directives` (array of StudioAstDeliveryDirective, max 256 items; required) - `format` (string, one of `eleven-v4-1`, `inworld-tts2-1`, `inworld-tts2-experimental-1`, `inworld-tts15-max-1`, `inworld-tts15-mini-1`, `fish-s2-inline-1`, `fish-s2.1-inline-1`, `cartesia-sonic35-1`, `openai-gpt4o-mini-tts-1`, `openai-tts1-hd-1`, `openai-tts1-1`, `smallest-lightning31-1`, `chatterbox-classic-1`, `zyphra-zonos2-cloud-1`, `f5tts-v1-1`, `misotts-8b-1`; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `model` (string, 1–200 chars; required) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `provider_text_override` (string, 1–65536 chars, pattern [\s\S]*\S[\s\S]*; optional) - `schema_version` (string, one of `studio-delivery-control-set-1`, `studio-delivery-control-set-2`; required) - `settings` (array of StudioAstDeliverySetting, max 32 items; required) - `source_fingerprint` (string | null, pattern ^[0-9a-f]{64}$; required) - `source_utf16_length` (integer | null, ≥ 0; required) - `points` (array of object; required) - One of: - **kind: "timed_silence"** - `duration_ms` (integer, 1–300000; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("timed_silence"; required) - `offset_utf16` (integer, ≥ 0; required) - **kind: "extension"** - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `offset_utf16` (integer, ≥ 0; required) - `payload` (object | null; required) - `version` (integer, > 0; required) - `schema_version` ("studio-ast-2"; required) - `text` (string, max 65536 chars; required): Exact block text; at most 65,536 UTF-16 code units. Annotation, point, and delivery offsets are UTF-16 offsets into this string. - `type` ("text"; required) - `generation_profile_override` (object | null; required) - `controls` (string, one of `strict`, `best_effort`; optional) - `fallback` (string, one of `strict`, `best_effort`; optional) - `model` (string, 1–200 chars; optional) - `options` (map of boolean | string | integer | number | array of StudioAstJsonValue | map of StudioAstJsonValue | null; optional) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; optional) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("narration"; required) - `placement` (any | null; required) - `speaker_id` (string | null, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - **kind: "unparsed"** - `block_version` (integer, > 0; required) - `content` (object; required) - `annotations` (array of object; required) - One of: - **kind: "exclusion"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("exclusion"; required) - `reason` (string, one of `speaker_label`, `stage_direction`, `metadata`, `user`; required) - `start_utf16` (integer, ≥ 0; required) - **kind: "pronunciation"** - `alphabet` (string, one of `ipa`, `x-sampa`, `cmu`, `alias`; required) - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("pronunciation"; required) - `start_utf16` (integer, ≥ 0; required) - `value` (string, 1–500 chars; required) - **kind: "extension"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `payload` (object | null; required) - `start_utf16` (integer, ≥ 0; required) - `version` (integer, > 0; required) - `delivery_control_sets` (array of object, max 32 items; required) - `capability_revision` (string, 1–200 chars; required) - `directives` (array of StudioAstDeliveryDirective, max 256 items; required) - `format` (string, one of `eleven-v4-1`, `inworld-tts2-1`, `inworld-tts2-experimental-1`, `inworld-tts15-max-1`, `inworld-tts15-mini-1`, `fish-s2-inline-1`, `fish-s2.1-inline-1`, `cartesia-sonic35-1`, `openai-gpt4o-mini-tts-1`, `openai-tts1-hd-1`, `openai-tts1-1`, `smallest-lightning31-1`, `chatterbox-classic-1`, `zyphra-zonos2-cloud-1`, `f5tts-v1-1`, `misotts-8b-1`; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `model` (string, 1–200 chars; required) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `provider_text_override` (string, 1–65536 chars, pattern [\s\S]*\S[\s\S]*; optional) - `schema_version` (string, one of `studio-delivery-control-set-1`, `studio-delivery-control-set-2`; required) - `settings` (array of StudioAstDeliverySetting, max 32 items; required) - `source_fingerprint` (string | null, pattern ^[0-9a-f]{64}$; required) - `source_utf16_length` (integer | null, ≥ 0; required) - `points` (array of object; required) - One of: - **kind: "timed_silence"** - `duration_ms` (integer, 1–300000; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("timed_silence"; required) - `offset_utf16` (integer, ≥ 0; required) - **kind: "extension"** - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `offset_utf16` (integer, ≥ 0; required) - `payload` (object | null; required) - `version` (integer, > 0; required) - `schema_version` ("studio-ast-2"; required) - `text` (string, max 65536 chars; required): Exact block text; at most 65,536 UTF-16 code units. Annotation, point, and delivery offsets are UTF-16 offsets into this string. - `type` ("text"; required) - `generation_profile_override` (object | null; required) - `controls` (string, one of `strict`, `best_effort`; optional) - `fallback` (string, one of `strict`, `best_effort`; optional) - `model` (string, 1–200 chars; optional) - `options` (map of boolean | string | integer | number | array of StudioAstJsonValue | map of StudioAstJsonValue | null; optional) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; optional) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("unparsed"; required) - `placement` (any | null; required) - `speaker_id` (any | null; required) - **kind: "excluded"** - `block_version` (integer, > 0; required) - `content` (object; required) - `annotations` (array of object; required) - One of: - **kind: "exclusion"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("exclusion"; required) - `reason` (string, one of `speaker_label`, `stage_direction`, `metadata`, `user`; required) - `start_utf16` (integer, ≥ 0; required) - **kind: "pronunciation"** - `alphabet` (string, one of `ipa`, `x-sampa`, `cmu`, `alias`; required) - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("pronunciation"; required) - `start_utf16` (integer, ≥ 0; required) - `value` (string, 1–500 chars; required) - **kind: "extension"** - `end_utf16` (integer, ≥ 0; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `payload` (object | null; required) - `start_utf16` (integer, ≥ 0; required) - `version` (integer, > 0; required) - `delivery_control_sets` (array of object, max 32 items; required) - `capability_revision` (string, 1–200 chars; required) - `directives` (array of StudioAstDeliveryDirective, max 256 items; required) - `format` (string, one of `eleven-v4-1`, `inworld-tts2-1`, `inworld-tts2-experimental-1`, `inworld-tts15-max-1`, `inworld-tts15-mini-1`, `fish-s2-inline-1`, `fish-s2.1-inline-1`, `cartesia-sonic35-1`, `openai-gpt4o-mini-tts-1`, `openai-tts1-hd-1`, `openai-tts1-1`, `smallest-lightning31-1`, `chatterbox-classic-1`, `zyphra-zonos2-cloud-1`, `f5tts-v1-1`, `misotts-8b-1`; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `model` (string, 1–200 chars; required) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `provider_text_override` (string, 1–65536 chars, pattern [\s\S]*\S[\s\S]*; optional) - `schema_version` (string, one of `studio-delivery-control-set-1`, `studio-delivery-control-set-2`; required) - `settings` (array of StudioAstDeliverySetting, max 32 items; required) - `source_fingerprint` (string | null, pattern ^[0-9a-f]{64}$; required) - `source_utf16_length` (integer | null, ≥ 0; required) - `points` (array of object; required) - One of: - **kind: "timed_silence"** - `duration_ms` (integer, 1–300000; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("timed_silence"; required) - `offset_utf16` (integer, ≥ 0; required) - **kind: "extension"** - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("extension"; required) - `name` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `namespace` (string, pattern ^[a-z][a-z0-9_.-]{0,63}$; required) - `offset_utf16` (integer, ≥ 0; required) - `payload` (object | null; required) - `version` (integer, > 0; required) - `schema_version` ("studio-ast-2"; required) - `text` (string, max 65536 chars; required): Exact block text; at most 65,536 UTF-16 code units. Annotation, point, and delivery offsets are UTF-16 offsets into this string. - `type` ("text"; required) - `generation_profile_override` (object | null; required) - `controls` (string, one of `strict`, `best_effort`; optional) - `fallback` (string, one of `strict`, `best_effort`; optional) - `model` (string, 1–200 chars; optional) - `options` (map of boolean | string | integer | number | array of StudioAstJsonValue | map of StudioAstJsonValue | null; optional) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; optional) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("excluded"; required) - `placement` (any | null; required) - `speaker_id` (any | null; required) - **kind: "note"** - `block_version` (integer, > 0; required) - `content` (object; required) - `schema_version` ("studio-ast-2"; required) - `text` (string, max 20000 chars; required) - `type` ("note"; required) - `generation_profile_override` (object | null; required) - `controls` (string, one of `strict`, `best_effort`; optional) - `fallback` (string, one of `strict`, `best_effort`; optional) - `model` (string, 1–200 chars; optional) - `options` (map of boolean | string | integer | number | array of StudioAstJsonValue | map of StudioAstJsonValue | null; optional) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; optional) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("note"; required) - `placement` (any | null; required) - `speaker_id` (any | null; required) - **kind: "sfx"** - `block_version` (integer, > 0; required) - `content` (object; required) - `duration_ms` (integer, 100–300000; required) - `prompt` (string, 1–4000 chars; required) - `schema_version` ("studio-ast-2"; required) - `seed` (integer | null, ≥ -9007199254740991; required) - `type` ("sfx"; required) - `source` (object; optional): Present only when the SFX block plays an uploaded project sound; seed is then null. - `kind` ("project_sound"; required) - `sound_effect_id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `sound_effect_revision_id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `generation_profile_override` (object | null; required) - `controls` (string, one of `strict`, `best_effort`; optional) - `fallback` (string, one of `strict`, `best_effort`; optional) - `model` (string, 1–200 chars; optional) - `options` (map of boolean | string | integer | number | array of StudioAstJsonValue | map of StudioAstJsonValue | null; optional) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; optional) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `kind` ("sfx"; required) - `placement` (object; required) - `anchor` (object; required) - One of: - **kind: "block"** - `block_id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `fraction` (number | null, 0–1; required) - `kind` ("block"; required) - `position` (string, one of `start`, `fraction`, `end`, `after`; required) - **kind: "section"** - `kind` ("section"; required) - `position` (string, one of `start`, `end`; required) - **kind: "unplaced"** - `kind` ("unplaced"; required) - `fade_in_ms` (integer, 0–300000; required) - `fade_out_ms` (integer, 0–300000; required) - `gain_db` (number, -60–24; required) - `offset_ms` (integer, -300000–300000; required) - `speaker_id` (any | null; required) - `different_speaker_gap_ms` (integer, 0–1000; required): Silence in milliseconds inserted between adjacent spoken blocks with different speakers. - `document_version` (integer, > 0; required) - `sfx_enabled` (boolean; required): Whether authored section sound effects are audible in playback and included in rendered exports. Legacy sections without a stored value report true. - `same_speaker_gap_ms` (integer, 0–1000; required): Silence in milliseconds inserted between adjacent spoken blocks with the same speaker. - `schema_version` ("studio-ast-2"; required) - `section_id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required): Canonical lowercase UUID of the section. - `speaker_registry_version` (integer, > 0; required): Project-wide speaker registry version the speakers list was read at. - `speakers` (array of object; required): Every active speaker in the project registry, not only speakers referenced by this section. - `generation_profile` (object | null; required) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `model` (string, 1–200 chars; required) - `fallback` (string, one of `strict`, `best_effort`; required) - `controls` (string, one of `strict`, `best_effort`; required) - `options` (object; required) - `id` (string, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `name` (string, 1–200 chars; required) - `speaker_version` (integer, > 0; required) - `voice_id` (string | null, pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$; required) - `title` (string, max 500 chars; required) - `source_draft` (object | null; required): The caller's saved, uncommitted Advanced Source draft for this section, or null. - `source_text` (string; required) - `base_document_version` (integer, ≥ 1; required) - `base_speaker_registry_version` (integer, ≥ 1; required) - `base_speaker_versions` (map of integer; required): Speaker versions keyed by speaker ID when the draft began. - `diagnostics` (array of object, max 1000 items; required) - `code` (string, one of `INVALID_SOURCE`, `STALE_SOURCE`; required) - `message` (string, 1–2000 chars; required) - `updated_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/document" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Edit a section document `PATCH /studio/sections/{sectionId}/document` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `saveStudioDocument` Applies a batch of document commands against the versions you last read. The whole batch commits atomically; if any targeted version changed, the request fails with `409` and nothing is applied. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required) - `client_mutation_id` (string, uuid; required) - `base_document_version` (integer, ≥ 1; required) - `base_speaker_registry_version` (integer, ≥ 1; required) - `target_block_versions` (map of integer; required) - `target_speaker_versions` (map of integer; required) - `commands` (array of object, 1–1000 items; required) - One of: - **kind: "set_section_sfx_enabled"** - `kind` ("set_section_sfx_enabled"; required) - `sfx_enabled` (boolean; required): Whether authored section sound effects are audible in playback and included in rendered exports. Disabling preserves authored SFX blocks and renditions. - **Option 2** - `kind` (string; required) #### Response 200 Authoritative document after commit or idempotent replay. Fields inside `data`: - `actor_user_id` (string, uuid; required): Authenticated caller; clients partition local recovery state by this ID. - `project_id` (string, uuid; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. See [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) for its fields. - `source_draft` (object | null; required): The caller's saved, uncommitted Advanced Source draft for this section, or null. - `source_text` (string; required) - `base_document_version` (integer, ≥ 1; required) - `base_speaker_registry_version` (integer, ≥ 1; required) - `base_speaker_versions` (map of integer; required): Speaker versions keyed by speaker ID when the draft began. - `diagnostics` (array of object, max 1000 items; required) - `code` (string, one of `INVALID_SOURCE`, `STALE_SOURCE`; required) - `message` (string, 1–2000 chars; required) - `updated_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X PATCH "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/document" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "base_document_version": 1, "base_speaker_registry_version": 1, "target_block_versions": {}, "target_speaker_versions": {}, "commands": [ { "kind": "set_section_sfx_enabled", "sfx_enabled": true } ] }' ``` ### Import a speaker-labeled script `POST /studio/sections/{sectionId}/script` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `importStudioScript` Turn `SPEAKER: [optional direction] spoken text` lines into a Studio document without building an AST. Map each speaker label to a voice and generation profile. A separate `(silence 4s)` line adds exact silence after the preceding spoken line. Unsupported lines or directions fail rather than disappearing. This replaces the section script; it does not generate audio. Send the current document and speaker-registry versions. Reusing an existing speaker name updates that project-wide speaker's voice and profile. Retry uncertain responses with the same mutation ID and body; a receipt that cannot be verified returns `409` without importing again. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required) - `client_mutation_id` (string, uuid; required) - `base_document_version` (integer, ≥ 1; required) - `base_speaker_registry_version` (integer, ≥ 1; required) - `script` (string, min 1 chars; required): One SPEAKER: [optional direction] spoken text per nonblank line. A separate (silence 4s) or (silence 4000ms) line adds 1-300 seconds of silence after the preceding spoken line. Labels match speaker-map keys exactly. Unsupported lines are rejected. - `speakers` (map of object; required): Exact speaker-label keys used in the script, each with an explicit voice and generation profile. Existing project speakers with the same name are reused. - `voice_id` (string | null, uuid; required) - `generation_profile` (object; required) - `provider` (string, pattern ^[a-z][a-z0-9_-]{0,63}$; required) - `model` (string, 1–200 chars; required) - `fallback` (string, one of `strict`, `best_effort`; required) - `controls` (string, one of `strict`, `best_effort`; required) - `options` (object; required) #### Response 200 Canonical Studio document after the import. Fields inside `data`: - `actor_user_id` (string, uuid; required): Authenticated caller; clients partition local recovery state by this ID. - `project_id` (string, uuid; required) - `document` (object; required): The canonical structured script of one section (studio-ast-2): its versions, speakers, and ordered blocks. See [Get a section document](https://lyricwinter.com/docs/api/documents#get-studio-document) for its fields. - `source_draft` (object | null; required): The caller's saved, uncommitted Advanced Source draft for this section, or null. - `source_text` (string; required) - `base_document_version` (integer, ≥ 1; required) - `base_speaker_registry_version` (integer, ≥ 1; required) - `base_speaker_versions` (map of integer; required): Speaker versions keyed by speaker ID when the draft began. - `diagnostics` (array of object, max 1000 items; required) - `code` (string, one of `INVALID_SOURCE`, `STALE_SOURCE`; required) - `message` (string, 1–2000 chars; required) - `updated_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/script" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "base_document_version": 1, "base_speaker_registry_version": 1, "script": "string", "speakers": {} }' ``` ### List project speakers `GET /studio/projects/{projectId}/speakers` Scope: `studio:read` · Operation ID: `getStudioProjectSpeakerUsage` Returns the project's speakers with how many lines and sections each one appears in, plus the current `speaker_registry_version`. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Response 200 Project-wide character usage and current character registry authority. Fields inside `data`: - `project_id` (string, uuid; required) - `speaker_registry_version` (integer, ≥ 1; required) - `speakers` (array of object, max 10000 items; required): Every active project speaker, including unused speakers with zero counts. - `speaker_id` (string, uuid; required) - `line_count` (integer, ≥ 0; required): Active speech and narration blocks assigned to the speaker across active sections. - `block_override_count` (integer, ≥ 0; required): Of those blocks, how many carry a block-level generation profile override. - `section_count` (integer, ≥ 0; required): Active sections containing at least one of those blocks. Errors: `400`, `401`, `403`, `404`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/speakers" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` --- Source: https://lyricwinter.com/docs/api/workflows.md --- title: "Workflows API" description: "Workflows are durable, asynchronous jobs. A prepare workflow structures text into narration and dialogue; a Create Audio workflow casts voices and generates multi-voice audio. Poll a workflow until it reaches a terminal status." canonical_url: https://lyricwinter.com/docs/api/workflows markdown_url: https://lyricwinter.com/docs/api/workflows.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Workflows > Workflows are durable, asynchronous jobs. A prepare workflow structures text into narration and dialogue; a Create Audio workflow casts voices and generates multi-voice audio. Poll a workflow until it reaches a terminal status. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Start a workflow](https://lyricwinter.com/docs/api/workflows#create-studio-workflow): `POST /studio/projects/{projectId}/workflows` - [Get the latest project workflow](https://lyricwinter.com/docs/api/workflows#get-latest-studio-workflow): `GET /studio/projects/{projectId}/workflows` - [Get a workflow](https://lyricwinter.com/docs/api/workflows#get-studio-workflow): `GET /studio/workflows/{workflowRunId}` - [Get workflow audio progress](https://lyricwinter.com/docs/api/workflows#get-studio-workflow-progress): `GET /studio/workflows/{workflowRunId}/progress` - [Cancel a workflow](https://lyricwinter.com/docs/api/workflows#cancel-studio-workflow): `POST /studio/workflows/{workflowRunId}/cancel` - [Resume a failed workflow](https://lyricwinter.com/docs/api/workflows#resume-studio-workflow): `POST /studio/workflows/{workflowRunId}/resume` ### Start a workflow `POST /studio/projects/{projectId}/workflows` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `createStudioWorkflow` Starts a `prepare_sections` workflow, which structures text into narration and dialogue, or a `create_audio` workflow, which prepares text, casts voices, directs performances, adds sound effects, and generates audio. Returns `202` with the run; poll it until it reaches a terminal status. Replaying the same `client_mutation_id` returns the same workflow instead of starting another. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Request body (application/json, required) - One of: - **schema_version: 1, workflow_kind: "prepare_sections"**: Start ordered section preparation. - `schema_version` (1; required) - `workflow_kind` ("prepare_sections"; required) - `actor_session_id` (string, uuid; required): Client session that issued the request. - `client_mutation_id` (string, uuid; required): Idempotency key; the workflow ID is derived from it. Reusing it for different content returns 409. - `section_ids` (array of string, 1–100 items, unique; required): Sections to prepare, in order; values must be unique. - **schema_version: 2, workflow_kind: "create_audio"**: Create Audio v2. Generated sound effects and automatic placement of linked project sounds are independent; when both run, project sounds are placed first. Section limit: 12 with both sound lanes enabled (the default), 14 with one, 16 with none. A target_scope requires exactly one section and both lanes set to false. - `schema_version` (2; required) - `workflow_kind` ("create_audio"; required) - `actor_session_id` (string, uuid; required): Client session that issued the request. - `client_mutation_id` (string, uuid; required): Idempotency key; the workflow ID is derived from it. Reusing it for different content returns 409. - `section_ids` (array of string, 1–16 items, unique; required): Sections to create audio for; values must be unique. - `include_generated_sfx` (boolean, default true; optional): Enables the generated-SFX authoring lane. - `place_project_sounds` (boolean, default true; optional): Enables automatic matching and placement from ready, accessible sounds explicitly linked to this project. - `direct_emotion` (boolean, default true; optional): Enables automatic emotion: a direct_emotion step adds provider delivery directions to spoken blocks that have none for their engine. Direction text sent to the provider is billed (ElevenLabs v4 audio tags). For pre-run estimates, clients add about 18 ElevenLabs v4 tokens per untagged ElevenLabs v4 block while this is on; the charge is the tokens actually sent. Omitted means true. - `target_scope` (object | null; optional): Optional focused source-block scope. Null or omitted targets the selected sections. Focused requests must set both optional sound lanes to false. - `kind` ("source_blocks"; required) - `blocks` (array of object, 1–250 items, unique; required): Focused source blocks; block_id values must be unique. - `block_id` (string, uuid; required) - `base_block_version` (integer, ≥ 1; required) - `text_length_utf16` (integer, 1–1000000; required) - `text_hash` (string, pattern ^[0-9a-f]{64}$; required) #### Response 202 Durable project workflow accepted and first advancement attempted. Replaying the same client_mutation_id with the same request returns the same workflow. Fields inside `data`: - `run` (object; required): Durable Studio project workflow run. - `id` (string, uuid; required): Workflow run ID. - `project_id` (string, uuid; required) - `requested_by_user_id` (string, uuid; required) - `payer_user_id` (string | null, uuid; required): User billed for Create Audio work; null for prepare_sections workflows. - `workflow_kind` (string, one of `prepare_sections`, `create_audio`; required) - `automation_profile` (string | null, one of `quick`, `balanced`, `directed`; required): Create Audio profile label from profile_snapshot.profile; null for prepare_sections. - `profile_snapshot` (object; required): Empty object for prepare_sections workflows; the frozen Create Audio options for create_audio workflows. - `status` (string, one of `queued`, `running`, `completed`, `partially_completed`, `failed`, `canceling`, `cancelled`; required): Workflow run status, recomputed from step statuses after every step transition. Non-terminal: queued (unfinished steps remain and none is active), running (at least one step is active), canceling (cancellation was requested and unfinished steps remain). Terminal (completed_at is set and the status no longer changes): completed (every step succeeded or was skipped), partially_completed (at least one failed or conflict step and at least one succeeded or skipped step), failed (failed or conflict steps and no succeeded or skipped step), cancelled (all steps finished after cancellation was requested). - `current_stage` (string | null, one of `preparing_sections`, `assigning_speakers`, `casting_voices`, `planning_emotion`, `directing_delivery`, `placing_project_sounds`, `directing_sfx`, `preparing_voices`, `generating_audio`; required): Stage of the next unfinished step while the run is queued, running, or canceling. Null once the run is terminal, or when the next unfinished step is render_section. - `step_count` (integer, 1–100; required) - `completed_step_count` (integer, 0–100; required): Steps in a terminal status (succeeded, skipped, failed, conflict, cancelled). - `progress` (object; required): Step counts by status. Currently contains integer counts pending, active, succeeded (succeeded or skipped), failed (failed or conflict), cancelled, completed (all terminal), and total; treat unknown keys as additive. - `pending` (integer, ≥ 0; optional) - `active` (integer, ≥ 0; optional) - `succeeded` (integer, ≥ 0; optional) - `failed` (integer, ≥ 0; optional) - `cancelled` (integer, ≥ 0; optional) - `completed` (integer, ≥ 0; optional) - `total` (integer, ≥ 0; optional) - `error_summary` (array of object, max 100 items; required): One entry per failed or conflict step in step order. Entries currently contain step_id, section_id, step_kind, code, and message; treat unknown keys as additive. - `step_id` (string, uuid; optional) - `section_id` (string, uuid; optional) - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; optional): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `code` (string | null; optional) - `message` (string | null; optional) - `cancel_requested_at` (string | null, date-time; required): When cancellation was first requested. - `cancel_acknowledged_at` (string | null, date-time; required): When the run reached cancelled. - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required): When execution first started; null before then. - `completed_at` (string | null, date-time; required): Set when the run reaches a terminal status (completed, partially_completed, failed, cancelled); null otherwise. - `resumes_workflow_run_id` (string | null, uuid; required): Predecessor run this run resumes; null for an original run. - `resumable` (boolean; required): True when status is failed or partially_completed and at least one step is failed or conflict. Only resumable runs can be resumed. - `steps` (array of object, 1–100 items; required): All steps in ascending step_index order. - `id` (string, uuid; required) - `workflow_run_id` (string, uuid; required) - `section_id` (string, uuid; required) - `step_key` (string, 1–200 chars; required): Stable key of the step within its workflow. - `step_index` (integer, 0–99; required): Zero-based position; steps are returned in ascending step_index order. - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; required): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `status` (string, one of `pending`, `active`, `succeeded`, `skipped`, `failed`, `conflict`, `cancelled`; required): Step status. pending and active are non-terminal; succeeded, skipped, failed, conflict, and cancelled are terminal. The run counts skipped as success and conflict as failure. conflict means the section document changed underneath the step. - `requested_document_version` (integer, ≥ 1; required): Section document version the step was planned against. - `execution_document_version` (integer | null, ≥ 1; required): Section document version the step executed against, when recorded. - `assistant_batch_id` (string | null, uuid; required): Child assistant batch started by an assistant-driven step. - `render_run_id` (string | null, uuid; required): Render run started by a render_section step. - `result_summary` (object; required): Step-specific result data, typed by the contract as an open JSON object. generate_stale_audio steps may carry the StudioWorkflowAudioResultSummary fields; steps cancelled before starting carry cancelled_before_start: true. - `issue_kind` (string | null, one of `document_changed`, `service_out_of_sync`, `assistant_failed`, `workflow_failed`; required): Set for failed and conflict steps; null for every other status. - `error_code` (string | null, 1–200 chars; required) - `error_message` (string | null, max 10000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/workflows" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "workflow_kind": "prepare_sections", "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "section_ids": [ "45c48cce-2e2d-4fbd-8a1c-5e6f7a8b9c30" ] }' ``` ### Get the latest project workflow `GET /studio/projects/{projectId}/workflows` Scope: `studio:read` · Operation ID: `getLatestStudioWorkflow` Returns the project's most recent workflow with its steps, or `null` if none has run. Pass `section_id` to get the most recent workflow that includes that section. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `section_id` | string, uuid | No | Restrict to the workflow that most recently added a step for this section. Returns null when none exists or that workflow belongs to another project. | #### Response 200 Latest project workflow or null when none exists. Fields inside `data`: - `run` (object; required): Durable Studio project workflow run. - `id` (string, uuid; required): Workflow run ID. - `project_id` (string, uuid; required) - `requested_by_user_id` (string, uuid; required) - `payer_user_id` (string | null, uuid; required): User billed for Create Audio work; null for prepare_sections workflows. - `workflow_kind` (string, one of `prepare_sections`, `create_audio`; required) - `automation_profile` (string | null, one of `quick`, `balanced`, `directed`; required): Create Audio profile label from profile_snapshot.profile; null for prepare_sections. - `profile_snapshot` (object; required): Empty object for prepare_sections workflows; the frozen Create Audio options for create_audio workflows. - `status` (string, one of `queued`, `running`, `completed`, `partially_completed`, `failed`, `canceling`, `cancelled`; required): Workflow run status, recomputed from step statuses after every step transition. Non-terminal: queued (unfinished steps remain and none is active), running (at least one step is active), canceling (cancellation was requested and unfinished steps remain). Terminal (completed_at is set and the status no longer changes): completed (every step succeeded or was skipped), partially_completed (at least one failed or conflict step and at least one succeeded or skipped step), failed (failed or conflict steps and no succeeded or skipped step), cancelled (all steps finished after cancellation was requested). - `current_stage` (string | null, one of `preparing_sections`, `assigning_speakers`, `casting_voices`, `planning_emotion`, `directing_delivery`, `placing_project_sounds`, `directing_sfx`, `preparing_voices`, `generating_audio`; required): Stage of the next unfinished step while the run is queued, running, or canceling. Null once the run is terminal, or when the next unfinished step is render_section. - `step_count` (integer, 1–100; required) - `completed_step_count` (integer, 0–100; required): Steps in a terminal status (succeeded, skipped, failed, conflict, cancelled). - `progress` (object; required): Step counts by status. Currently contains integer counts pending, active, succeeded (succeeded or skipped), failed (failed or conflict), cancelled, completed (all terminal), and total; treat unknown keys as additive. - `pending` (integer, ≥ 0; optional) - `active` (integer, ≥ 0; optional) - `succeeded` (integer, ≥ 0; optional) - `failed` (integer, ≥ 0; optional) - `cancelled` (integer, ≥ 0; optional) - `completed` (integer, ≥ 0; optional) - `total` (integer, ≥ 0; optional) - `error_summary` (array of object, max 100 items; required): One entry per failed or conflict step in step order. Entries currently contain step_id, section_id, step_kind, code, and message; treat unknown keys as additive. - `step_id` (string, uuid; optional) - `section_id` (string, uuid; optional) - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; optional): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `code` (string | null; optional) - `message` (string | null; optional) - `cancel_requested_at` (string | null, date-time; required): When cancellation was first requested. - `cancel_acknowledged_at` (string | null, date-time; required): When the run reached cancelled. - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required): When execution first started; null before then. - `completed_at` (string | null, date-time; required): Set when the run reaches a terminal status (completed, partially_completed, failed, cancelled); null otherwise. - `resumes_workflow_run_id` (string | null, uuid; required): Predecessor run this run resumes; null for an original run. - `resumable` (boolean; required): True when status is failed or partially_completed and at least one step is failed or conflict. Only resumable runs can be resumed. - `steps` (array of object, 1–100 items; required): All steps in ascending step_index order. - `id` (string, uuid; required) - `workflow_run_id` (string, uuid; required) - `section_id` (string, uuid; required) - `step_key` (string, 1–200 chars; required): Stable key of the step within its workflow. - `step_index` (integer, 0–99; required): Zero-based position; steps are returned in ascending step_index order. - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; required): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `status` (string, one of `pending`, `active`, `succeeded`, `skipped`, `failed`, `conflict`, `cancelled`; required): Step status. pending and active are non-terminal; succeeded, skipped, failed, conflict, and cancelled are terminal. The run counts skipped as success and conflict as failure. conflict means the section document changed underneath the step. - `requested_document_version` (integer, ≥ 1; required): Section document version the step was planned against. - `execution_document_version` (integer | null, ≥ 1; required): Section document version the step executed against, when recorded. - `assistant_batch_id` (string | null, uuid; required): Child assistant batch started by an assistant-driven step. - `render_run_id` (string | null, uuid; required): Render run started by a render_section step. - `result_summary` (object; required): Step-specific result data, typed by the contract as an open JSON object. generate_stale_audio steps may carry the StudioWorkflowAudioResultSummary fields; steps cancelled before starting carry cancelled_before_start: true. - `issue_kind` (string | null, one of `document_changed`, `service_out_of_sync`, `assistant_failed`, `workflow_failed`; required): Set for failed and conflict steps; null for every other status. - `error_code` (string | null, 1–200 chars; required) - `error_message` (string | null, max 10000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) Errors: `400`, `401`, `403`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/workflows" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Get a workflow `GET /studio/workflows/{workflowRunId}` Scope: `studio:read` · Operation ID: `getStudioWorkflow` Returns a workflow run and its ordered steps. Poll this endpoint until `run.status` is `completed`, `partially_completed`, `failed`, or `cancelled`. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowRunId` | string, uuid | Yes | | #### Response 200 Current workflow and ordered section steps. Poll until run.status is terminal (completed, partially_completed, failed, cancelled). Fields inside `data`: - `run` (object; required): Durable Studio project workflow run. - `id` (string, uuid; required): Workflow run ID. - `project_id` (string, uuid; required) - `requested_by_user_id` (string, uuid; required) - `payer_user_id` (string | null, uuid; required): User billed for Create Audio work; null for prepare_sections workflows. - `workflow_kind` (string, one of `prepare_sections`, `create_audio`; required) - `automation_profile` (string | null, one of `quick`, `balanced`, `directed`; required): Create Audio profile label from profile_snapshot.profile; null for prepare_sections. - `profile_snapshot` (object; required): Empty object for prepare_sections workflows; the frozen Create Audio options for create_audio workflows. - `status` (string, one of `queued`, `running`, `completed`, `partially_completed`, `failed`, `canceling`, `cancelled`; required): Workflow run status, recomputed from step statuses after every step transition. Non-terminal: queued (unfinished steps remain and none is active), running (at least one step is active), canceling (cancellation was requested and unfinished steps remain). Terminal (completed_at is set and the status no longer changes): completed (every step succeeded or was skipped), partially_completed (at least one failed or conflict step and at least one succeeded or skipped step), failed (failed or conflict steps and no succeeded or skipped step), cancelled (all steps finished after cancellation was requested). - `current_stage` (string | null, one of `preparing_sections`, `assigning_speakers`, `casting_voices`, `planning_emotion`, `directing_delivery`, `placing_project_sounds`, `directing_sfx`, `preparing_voices`, `generating_audio`; required): Stage of the next unfinished step while the run is queued, running, or canceling. Null once the run is terminal, or when the next unfinished step is render_section. - `step_count` (integer, 1–100; required) - `completed_step_count` (integer, 0–100; required): Steps in a terminal status (succeeded, skipped, failed, conflict, cancelled). - `progress` (object; required): Step counts by status. Currently contains integer counts pending, active, succeeded (succeeded or skipped), failed (failed or conflict), cancelled, completed (all terminal), and total; treat unknown keys as additive. - `pending` (integer, ≥ 0; optional) - `active` (integer, ≥ 0; optional) - `succeeded` (integer, ≥ 0; optional) - `failed` (integer, ≥ 0; optional) - `cancelled` (integer, ≥ 0; optional) - `completed` (integer, ≥ 0; optional) - `total` (integer, ≥ 0; optional) - `error_summary` (array of object, max 100 items; required): One entry per failed or conflict step in step order. Entries currently contain step_id, section_id, step_kind, code, and message; treat unknown keys as additive. - `step_id` (string, uuid; optional) - `section_id` (string, uuid; optional) - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; optional): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `code` (string | null; optional) - `message` (string | null; optional) - `cancel_requested_at` (string | null, date-time; required): When cancellation was first requested. - `cancel_acknowledged_at` (string | null, date-time; required): When the run reached cancelled. - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required): When execution first started; null before then. - `completed_at` (string | null, date-time; required): Set when the run reaches a terminal status (completed, partially_completed, failed, cancelled); null otherwise. - `resumes_workflow_run_id` (string | null, uuid; required): Predecessor run this run resumes; null for an original run. - `resumable` (boolean; required): True when status is failed or partially_completed and at least one step is failed or conflict. Only resumable runs can be resumed. - `steps` (array of object, 1–100 items; required): All steps in ascending step_index order. - `id` (string, uuid; required) - `workflow_run_id` (string, uuid; required) - `section_id` (string, uuid; required) - `step_key` (string, 1–200 chars; required): Stable key of the step within its workflow. - `step_index` (integer, 0–99; required): Zero-based position; steps are returned in ascending step_index order. - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; required): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `status` (string, one of `pending`, `active`, `succeeded`, `skipped`, `failed`, `conflict`, `cancelled`; required): Step status. pending and active are non-terminal; succeeded, skipped, failed, conflict, and cancelled are terminal. The run counts skipped as success and conflict as failure. conflict means the section document changed underneath the step. - `requested_document_version` (integer, ≥ 1; required): Section document version the step was planned against. - `execution_document_version` (integer | null, ≥ 1; required): Section document version the step executed against, when recorded. - `assistant_batch_id` (string | null, uuid; required): Child assistant batch started by an assistant-driven step. - `render_run_id` (string | null, uuid; required): Render run started by a render_section step. - `result_summary` (object; required): Step-specific result data, typed by the contract as an open JSON object. generate_stale_audio steps may carry the StudioWorkflowAudioResultSummary fields; steps cancelled before starting carry cancelled_before_start: true. - `issue_kind` (string | null, one of `document_changed`, `service_out_of_sync`, `assistant_failed`, `workflow_failed`; required): Set for failed and conflict steps; null for every other status. - `error_code` (string | null, 1–200 chars; required) - `error_message` (string | null, max 10000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) Errors: `400`, `401`, `403`, `404`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/workflows/$WORKFLOW_RUN_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Get workflow audio progress `GET /studio/workflows/{workflowRunId}/progress` Scope: `studio:read` · Operation ID: `getStudioWorkflowProgress` Returns how many audio clips are ready out of the total for a Create Audio workflow. Use it for a progress bar while audio generates. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowRunId` | string, uuid | Yes | | #### Response 200 Current workflow audio counts. Fields inside `data`: - `schema_version` (integer, one of `1`; required) - `workflow_run_id` (string, uuid; required) - `ready` (integer, ≥ 0; required) - `total` (integer, ≥ 0; required) Errors: `400`, `401`, `403`, `404`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/workflows/$WORKFLOW_RUN_ID/progress" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Cancel a workflow `POST /studio/workflows/{workflowRunId}/cancel` Scope: `studio:write` · Operation ID: `cancelStudioWorkflow` Requests cancellation of a prepare or Create Audio workflow. Pending steps are cancelled immediately; running steps stop cooperatively and their late results are discarded. A finished workflow is returned unchanged. Send no request body. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowRunId` | string, uuid | Yes | | #### Response 200 Current workflow state, normally canceling or cancelled. Fields inside `data`: - `run` (object; required): Durable Studio project workflow run. - `id` (string, uuid; required): Workflow run ID. - `project_id` (string, uuid; required) - `requested_by_user_id` (string, uuid; required) - `payer_user_id` (string | null, uuid; required): User billed for Create Audio work; null for prepare_sections workflows. - `workflow_kind` (string, one of `prepare_sections`, `create_audio`; required) - `automation_profile` (string | null, one of `quick`, `balanced`, `directed`; required): Create Audio profile label from profile_snapshot.profile; null for prepare_sections. - `profile_snapshot` (object; required): Empty object for prepare_sections workflows; the frozen Create Audio options for create_audio workflows. - `status` (string, one of `queued`, `running`, `completed`, `partially_completed`, `failed`, `canceling`, `cancelled`; required): Workflow run status, recomputed from step statuses after every step transition. Non-terminal: queued (unfinished steps remain and none is active), running (at least one step is active), canceling (cancellation was requested and unfinished steps remain). Terminal (completed_at is set and the status no longer changes): completed (every step succeeded or was skipped), partially_completed (at least one failed or conflict step and at least one succeeded or skipped step), failed (failed or conflict steps and no succeeded or skipped step), cancelled (all steps finished after cancellation was requested). - `current_stage` (string | null, one of `preparing_sections`, `assigning_speakers`, `casting_voices`, `planning_emotion`, `directing_delivery`, `placing_project_sounds`, `directing_sfx`, `preparing_voices`, `generating_audio`; required): Stage of the next unfinished step while the run is queued, running, or canceling. Null once the run is terminal, or when the next unfinished step is render_section. - `step_count` (integer, 1–100; required) - `completed_step_count` (integer, 0–100; required): Steps in a terminal status (succeeded, skipped, failed, conflict, cancelled). - `progress` (object; required): Step counts by status. Currently contains integer counts pending, active, succeeded (succeeded or skipped), failed (failed or conflict), cancelled, completed (all terminal), and total; treat unknown keys as additive. - `pending` (integer, ≥ 0; optional) - `active` (integer, ≥ 0; optional) - `succeeded` (integer, ≥ 0; optional) - `failed` (integer, ≥ 0; optional) - `cancelled` (integer, ≥ 0; optional) - `completed` (integer, ≥ 0; optional) - `total` (integer, ≥ 0; optional) - `error_summary` (array of object, max 100 items; required): One entry per failed or conflict step in step order. Entries currently contain step_id, section_id, step_kind, code, and message; treat unknown keys as additive. - `step_id` (string, uuid; optional) - `section_id` (string, uuid; optional) - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; optional): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `code` (string | null; optional) - `message` (string | null; optional) - `cancel_requested_at` (string | null, date-time; required): When cancellation was first requested. - `cancel_acknowledged_at` (string | null, date-time; required): When the run reached cancelled. - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required): When execution first started; null before then. - `completed_at` (string | null, date-time; required): Set when the run reaches a terminal status (completed, partially_completed, failed, cancelled); null otherwise. - `resumes_workflow_run_id` (string | null, uuid; required): Predecessor run this run resumes; null for an original run. - `resumable` (boolean; required): True when status is failed or partially_completed and at least one step is failed or conflict. Only resumable runs can be resumed. - `steps` (array of object, 1–100 items; required): All steps in ascending step_index order. - `id` (string, uuid; required) - `workflow_run_id` (string, uuid; required) - `section_id` (string, uuid; required) - `step_key` (string, 1–200 chars; required): Stable key of the step within its workflow. - `step_index` (integer, 0–99; required): Zero-based position; steps are returned in ascending step_index order. - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; required): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `status` (string, one of `pending`, `active`, `succeeded`, `skipped`, `failed`, `conflict`, `cancelled`; required): Step status. pending and active are non-terminal; succeeded, skipped, failed, conflict, and cancelled are terminal. The run counts skipped as success and conflict as failure. conflict means the section document changed underneath the step. - `requested_document_version` (integer, ≥ 1; required): Section document version the step was planned against. - `execution_document_version` (integer | null, ≥ 1; required): Section document version the step executed against, when recorded. - `assistant_batch_id` (string | null, uuid; required): Child assistant batch started by an assistant-driven step. - `render_run_id` (string | null, uuid; required): Render run started by a render_section step. - `result_summary` (object; required): Step-specific result data, typed by the contract as an open JSON object. generate_stale_audio steps may carry the StudioWorkflowAudioResultSummary fields; steps cancelled before starting carry cancelled_before_start: true. - `issue_kind` (string | null, one of `document_changed`, `service_out_of_sync`, `assistant_failed`, `workflow_failed`; required): Set for failed and conflict steps; null for every other status. - `error_code` (string | null, 1–200 chars; required) - `error_message` (string | null, max 10000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/workflows/$WORKFLOW_RUN_ID/cancel" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Resume a failed workflow `POST /studio/workflows/{workflowRunId}/resume` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `resumeStudioWorkflow` Starts a new workflow linked to a failed or partially completed run. It picks up from each section's first failed stage instead of redoing finished work. Only runs with `resumable: true` can be resumed. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `workflowRunId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `actor_session_id` (string, uuid; required): Client session that issued the request. - `client_mutation_id` (string, uuid; required): Idempotency key; the successor workflow ID is derived from it. Reusing it for different content returns 409. #### Response 202 The linked successor workflow and its recovery steps. Fields inside `data`: - `run` (object; required): Durable Studio project workflow run. - `id` (string, uuid; required): Workflow run ID. - `project_id` (string, uuid; required) - `requested_by_user_id` (string, uuid; required) - `payer_user_id` (string | null, uuid; required): User billed for Create Audio work; null for prepare_sections workflows. - `workflow_kind` (string, one of `prepare_sections`, `create_audio`; required) - `automation_profile` (string | null, one of `quick`, `balanced`, `directed`; required): Create Audio profile label from profile_snapshot.profile; null for prepare_sections. - `profile_snapshot` (object; required): Empty object for prepare_sections workflows; the frozen Create Audio options for create_audio workflows. - `status` (string, one of `queued`, `running`, `completed`, `partially_completed`, `failed`, `canceling`, `cancelled`; required): Workflow run status, recomputed from step statuses after every step transition. Non-terminal: queued (unfinished steps remain and none is active), running (at least one step is active), canceling (cancellation was requested and unfinished steps remain). Terminal (completed_at is set and the status no longer changes): completed (every step succeeded or was skipped), partially_completed (at least one failed or conflict step and at least one succeeded or skipped step), failed (failed or conflict steps and no succeeded or skipped step), cancelled (all steps finished after cancellation was requested). - `current_stage` (string | null, one of `preparing_sections`, `assigning_speakers`, `casting_voices`, `planning_emotion`, `directing_delivery`, `placing_project_sounds`, `directing_sfx`, `preparing_voices`, `generating_audio`; required): Stage of the next unfinished step while the run is queued, running, or canceling. Null once the run is terminal, or when the next unfinished step is render_section. - `step_count` (integer, 1–100; required) - `completed_step_count` (integer, 0–100; required): Steps in a terminal status (succeeded, skipped, failed, conflict, cancelled). - `progress` (object; required): Step counts by status. Currently contains integer counts pending, active, succeeded (succeeded or skipped), failed (failed or conflict), cancelled, completed (all terminal), and total; treat unknown keys as additive. - `pending` (integer, ≥ 0; optional) - `active` (integer, ≥ 0; optional) - `succeeded` (integer, ≥ 0; optional) - `failed` (integer, ≥ 0; optional) - `cancelled` (integer, ≥ 0; optional) - `completed` (integer, ≥ 0; optional) - `total` (integer, ≥ 0; optional) - `error_summary` (array of object, max 100 items; required): One entry per failed or conflict step in step order. Entries currently contain step_id, section_id, step_kind, code, and message; treat unknown keys as additive. - `step_id` (string, uuid; optional) - `section_id` (string, uuid; optional) - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; optional): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `code` (string | null; optional) - `message` (string | null; optional) - `cancel_requested_at` (string | null, date-time; required): When cancellation was first requested. - `cancel_acknowledged_at` (string | null, date-time; required): When the run reached cancelled. - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required): When execution first started; null before then. - `completed_at` (string | null, date-time; required): Set when the run reaches a terminal status (completed, partially_completed, failed, cancelled); null otherwise. - `resumes_workflow_run_id` (string | null, uuid; required): Predecessor run this run resumes; null for an original run. - `resumable` (boolean; required): True when status is failed or partially_completed and at least one step is failed or conflict. Only resumable runs can be resumed. - `steps` (array of object, 1–100 items; required): All steps in ascending step_index order. - `id` (string, uuid; required) - `workflow_run_id` (string, uuid; required) - `section_id` (string, uuid; required) - `step_key` (string, 1–200 chars; required): Stable key of the step within its workflow. - `step_index` (integer, 0–99; required): Zero-based position; steps are returned in ascending step_index order. - `step_kind` (string, one of `prepare_section`, `assign_speakers`, `cast_voices`, `direct_emotion`, `direct_section_delivery`, `place_project_sounds`, `direct_sfx`, `prepare_voices`, `generate_stale_audio`, `render_section`; required): Kind of work a step performs. prepare_sections workflows contain only prepare_section steps. direct_section_delivery and render_section come from older Create Audio profiles (and successors resumed from them); new workflows do not plan them. - `status` (string, one of `pending`, `active`, `succeeded`, `skipped`, `failed`, `conflict`, `cancelled`; required): Step status. pending and active are non-terminal; succeeded, skipped, failed, conflict, and cancelled are terminal. The run counts skipped as success and conflict as failure. conflict means the section document changed underneath the step. - `requested_document_version` (integer, ≥ 1; required): Section document version the step was planned against. - `execution_document_version` (integer | null, ≥ 1; required): Section document version the step executed against, when recorded. - `assistant_batch_id` (string | null, uuid; required): Child assistant batch started by an assistant-driven step. - `render_run_id` (string | null, uuid; required): Render run started by a render_section step. - `result_summary` (object; required): Step-specific result data, typed by the contract as an open JSON object. generate_stale_audio steps may carry the StudioWorkflowAudioResultSummary fields; steps cancelled before starting carry cancelled_before_start: true. - `issue_kind` (string | null, one of `document_changed`, `service_out_of_sync`, `assistant_failed`, `workflow_failed`; required): Set for failed and conflict steps; null for every other status. - `error_code` (string | null, 1–200 chars; required) - `error_message` (string | null, max 10000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/workflows/$WORKFLOW_RUN_ID/resume" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "actor_session_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "client_mutation_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20" }' ``` --- Source: https://lyricwinter.com/docs/api/media.md --- title: "Media and playback API" description: "Read the generated audio state for a section, compile a playback manifest for the current document version, and mint short-lived URLs for individual media assets." canonical_url: https://lyricwinter.com/docs/api/media markdown_url: https://lyricwinter.com/docs/api/media.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Media and playback > Read the generated audio state for a section, compile a playback manifest for the current document version, and mint short-lived URLs for individual media assets. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Get section media](https://lyricwinter.com/docs/api/media#get-studio-section-media): `GET /studio/sections/{sectionId}/media` - [Get a playback manifest](https://lyricwinter.com/docs/api/media#get-studio-playback-manifest): `GET /studio/sections/{sectionId}/playback-manifest` - [Create a media access URL](https://lyricwinter.com/docs/api/media#create-studio-media-access-url): `POST /studio/media-assets/{assetId}/access-url` ### Get section media `GET /studio/sections/{sectionId}/media` Scope: `studio:read` · Operation ID: `getStudioSectionMedia` Returns one entry per document block with its audio `freshness`, generation `activity`, and a short-lived signed `playback_url` for the current audio, plus the section's playback duration. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Response 200 Current block media read model. Fields inside `data`: - `section_id` (string, uuid; required) - `playback_duration_ms` (integer | null, ≥ 0; required): Duration of the newest saved render manifest for the section's current document and speaker-registry versions; null when none exists. - `blocks` (array of object; required): One entry per document block, in document order. - `block_id` (string, uuid; required) - `freshness` (string, one of `absent`, `current`, `stale`, `unsupported`; required): absent: no active rendition. current: the active rendition matches the block's current generation intent. stale: it differs (see stale_reasons). unsupported: the block kind is not speech, narration, or sfx. - `activity` (string, one of `idle`, `generating`, `failed`; required): generating: a pending rendition is queued, running, or waiting to retry, or imported audio is being copied. failed: the latest request or import failed. idle otherwise. Independent of freshness. - `active_block_version` (integer | null, ≥ 1; required): Block version the active rendition was requested for. - `requested_block_version` (integer | null, ≥ 1; required): Block version of the pending rendition or import, if any. - `active_rendition_id` (string | null, uuid; required) - `audio_version_count` (integer, ≥ 0; required): Number of completed audio versions for the block. - `generation_batch_id` (string | null, uuid; required): Generation batch of the pending rendition. - `playback_url` (string | null, uri; required): Short-lived signed URL for the active rendition's audio; null without an active asset. - `playback_url_expires_at` (string | null, date-time; required) - `content_type` (string | null; required) - `size_bytes` (integer | null, ≥ 1; required) - `analysis_integrated_lufs` (number | null, -120–24; required) - `analysis_sample_peak_dbfs` (number | null, -200–24; required) - `provider` (string | null; required): Provider that actually produced the active rendition (stored metadata for imports). - `model` (string | null; required): Model that actually produced the active rendition (stored metadata for imports). - `error` (string | null; required): Latest generation or import error message. - `stale_reasons` (array of string, one of `block_kind`, `text`, `pronunciation`, `excluded_text`, `timing`, `voice`, `generation_profile`, `delivery_controls`, `sound_effect`; optional): Present for speech, narration, and sfx blocks; non-empty only when freshness is stale. Errors: `400`, `401`, `403`, `404`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/media" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Get a playback manifest `GET /studio/sections/{sectionId}/playback-manifest` Scope: `studio:read` · Operation ID: `getStudioPlaybackManifest` Compiles the section's current speech, pauses, and sound-effect overlays into one timeline with signed asset URLs for gapless playback. Pass the `document_version` and `speaker_registry_version` from the document; any other query parameter returns `400`. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `document_version` | integer, ≥ 1 | Yes | Saved section document version to compile. If it no longer matches the saved section, the request fails with 409 STUDIO_PLAYBACK_STALE. | | `speaker_registry_version` | integer, ≥ 1 | Yes | Saved speaker-registry (cast) version to compile. If it no longer matches, the request fails with 409 STUDIO_PLAYBACK_STALE. | #### Response 200 Current private playback manifest and signed asset URLs. Fields inside `data`: - `schema_version` (1; required) - `mastering_version` (integer, ≥ 1; required) - `manifest_hash` (string, pattern ^[a-f0-9]{64}$; required): SHA-256 of the canonical manifest. - `manifest` (object; required): Compiled playback timeline for one saved section document and speaker-registry version. - `schema_version` ("studio-render-manifest-2"; required) - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `mastering_profile_hash` (string, pattern ^[a-f0-9]{64}$; required) - `mastering_profile` (object; required) - `schema_version` (1; required) - `enabled` (boolean; required) - `target_lufs` (number, -30–-10; required) - `strength` (number, 0–1; required) - `max_boost_db` (number, 0–24; required) - `max_reduction_db` (number, 0–24; required) - `silence_threshold_lufs` (number, -120–-20; required) - `peak_ceiling_db` (number, -12–-0.1; required) - `speaker_adjustments_db` (map of number; required) - `sfx_bus_gain_db` (number, -24–24, default 0; required) - `completeness` (string, one of `partial`, `complete`; required): complete exactly when gaps is empty. - `duration_ms` (integer, ≥ 0; required): Latest end point of any sequential or overlay unit. - `sequential_units` (array of object, max 50000 items; required): Contiguous from 0 ms in canonical block order; unit IDs are unique across sequential and overlay units. - One of: - **unit_type: "asset"** - `unit_id` (string, uuid; required) - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `block_kind` (string, one of `speech`, `narration`; required) - `start_ms` (integer, ≥ 0; required) - `duration_ms` (integer, ≥ 1; required) - `rendition_id` (string, uuid; required) - `rendition_intent_hash` (string, pattern ^[a-f0-9]{64}$; required) - `unit_type` ("asset"; required) - `asset_id` (string, uuid; required): Key into the payload's assets array. - `asset_content_hash` (string, pattern ^[a-f0-9]{64}$; required) - `source_ranges` (array of object, 1–10000 items; required) - `start_utf16` (integer, ≥ 0; required) - `end_utf16` (integer, ≥ 0; required) - `speaker_id` (string, uuid; required) - `analysis_integrated_lufs` (number, -120–12; required) - `analysis_sample_peak_dbfs` (number, -200–12; required) - `effective_gain_db` (number, -24–24; required) - **unit_type: "silence"** - `unit_id` (string, uuid; required) - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `block_kind` (string, one of `speech`, `narration`; required) - `start_ms` (integer, ≥ 0; required) - `duration_ms` (integer, ≥ 1; required) - `rendition_id` (string, uuid; required) - `rendition_intent_hash` (string, pattern ^[a-f0-9]{64}$; required) - `unit_type` ("silence"; required) - **unit_type: "spacing", spacing_kind: "inter_passage"** - `unit_id` (string, uuid; required) - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `block_kind` (string, one of `speech`, `narration`; required) - `start_ms` (integer, ≥ 0; required) - `duration_ms` (integer, ≥ 1; required) - `unit_type` ("spacing"; required) - `spacing_kind` ("inter_passage"; required) - `overlay_units` (array of object, max 10000 items; required): Ordered by start_ms, block_index, block_id, unit_id. - `unit_type` ("sfx"; required) - `unit_id` (string, uuid; required) - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `intent_hash` (string, pattern ^[a-f0-9]{64}$; required) - `asset_id` (string, uuid; required): Key into the payload's assets array. - `asset_content_hash` (string, pattern ^[a-f0-9]{64}$; required) - `start_ms` (integer, ≥ 0; required) - `duration_ms` (integer, ≥ 1; required) - `gain_db` (number, -60–24; required) - `fade_in_ms` (integer, ≥ 0; required) - `fade_out_ms` (integer, ≥ 0; required) - `timed_text_units` (array of object, max 50000 items; required): Ordered by start_ms, block_index, block_id, unit_id. - `unit_id` (string, uuid; required) - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `speaker_id` (string, uuid; required) - `start_ms` (integer, ≥ 0; required) - `duration_ms` (integer, ≥ 1; required) - `text` (string, 1–100000 chars; required) - `text_hash` (string, pattern ^[a-f0-9]{64}$; required) - `source_ranges` (array of object, 1–10000 items; required) - `start_utf16` (integer, ≥ 0; required) - `end_utf16` (integer, ≥ 0; required) - `gaps` (array of object, max 50000 items; required): Ordered by block_index, media_kind, block_id. - One of: - **media_kind: "speech"** - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `media_kind` ("speech"; required) - `reason` (string, one of `missing`, `stale`, `failed`, `unsupported`, `unassigned`, `unresolved`; required) - `source_ranges` (array of object, 1–10000 items; required) - `start_utf16` (integer, ≥ 0; required) - `end_utf16` (integer, ≥ 0; required) - **media_kind: "sfx"** - `block_id` (string, uuid; required) - `block_index` (integer, ≥ 0; required) - `block_version` (integer, ≥ 1; required) - `media_kind` ("sfx"; required) - `reason` (string, one of `missing`, `stale`, `failed`, `unsupported`, `unplaced`; required) - `source_ranges` (array of any, max 0 items; required) - `assets` (array of object, max 60000 items; required): Exactly one entry per distinct asset_id referenced by asset sequential units and overlay units. - `asset_id` (string, uuid; required) - `url` (string, uri; required): Short-lived signed inline URL. - `expires_at` (string, date-time; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) Errors: `400`, `401`, `403`, `409`, `500`, `502`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/playback-manifest?document_version=1&speaker_registry_version=1" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Create a media access URL `POST /studio/media-assets/{assetId}/access-url` Scope: `studio:read` · Operation ID: `createStudioMediaAccessUrl` Returns a signed URL for one audio asset: six hours for inline playback or twelve hours for download. The URL supports HTTP range requests, so audio players can seek. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `assetId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` (1; required) - `disposition` (string, one of `inline`, `attachment`; required) - `filename` (string, 1–255 chars; optional) #### Response 200 Short-lived signed media URL and asset metadata. Fields inside `data`: - `schema_version` (1; required) - `asset_id` (string, uuid; required) - `url` (string, uri; required) - `expires_at` (string, date-time; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) - `disposition` (string, one of `inline`, `attachment`; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/media-assets/$ASSET_ID/access-url" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "disposition": "attachment", "filename": "Chapter-1.mp3" }' ``` --- Source: https://lyricwinter.com/docs/api/exports.md --- title: "Exports API" description: "Render a section into a downloadable MP3, WAV, M4B, synchronized EPUB, or SRT file. Exports are pinned to the current document, cast, and mastering versions and run asynchronously; poll an export until its file is ready." canonical_url: https://lyricwinter.com/docs/api/exports markdown_url: https://lyricwinter.com/docs/api/exports.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Exports > Render a section into a downloadable MP3, WAV, M4B, synchronized EPUB, or SRT file. Exports are pinned to the current document, cast, and mastering versions and run asynchronously; poll an export until its file is ready. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Get the mastering profile](https://lyricwinter.com/docs/api/exports#get-studio-mastering-profile): `GET /studio/projects/{projectId}/mastering-profile` - [List exports](https://lyricwinter.com/docs/api/exports#list-studio-exports): `GET /studio/exports` - [Create an export](https://lyricwinter.com/docs/api/exports#create-studio-export): `POST /studio/exports` - [Get an export](https://lyricwinter.com/docs/api/exports#get-studio-export): `GET /studio/exports/{exportId}` - [Cancel an export](https://lyricwinter.com/docs/api/exports#cancel-studio-export): `POST /studio/exports/{exportId}/cancel` ### Get the mastering profile `GET /studio/projects/{projectId}/mastering-profile` Scope: `studio:read` · Operation ID: `getStudioMasteringProfile` Returns the project's loudness and mastering settings and its `mastering_version`, which exports require. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Response 200 Current mastering authority and profile. Fields inside `data`: - `schema_version` (1; required) - `project_id` (string, uuid; required) - `project_kind` (string, one of `standalone`, `named`; required) - `mastering_version` (integer, ≥ 1; required) - `scope` (string, one of `document`, `project`; required) - `profile` (object; required) - `schema_version` (1; required) - `enabled` (boolean; required) - `target_lufs` (number, -30–-10; required) - `strength` (number, 0–1; required) - `max_boost_db` (number, 0–24; required) - `max_reduction_db` (number, 0–24; required) - `silence_threshold_lufs` (number, -120–-20; required) - `peak_ceiling_db` (number, -12–-0.1; required) - `speaker_adjustments_db` (map of number; required) - `sfx_bus_gain_db` (number, -24–24, default 0; required) - `sfx_adjustments` (object; optional) - `nonzero_count` (integer, ≥ 0; required) - `nonzero_section_count` (integer, ≥ 0; required) - `fingerprint` (string, pattern ^[a-f0-9]{64}$; required) Errors: `400`, `401`, `403`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/mastering-profile" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### List exports `GET /studio/exports` Scope: `studio:read` · Operation ID: `listStudioExports` Lists the account's exports, newest first. Completed exports include a download URL that is valid for twelve hours; the file itself is kept for thirty days. #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `section_id` | string, uuid | No | | | `format` | string, one of `mp3`, `wav`, `m4b`, `epub`, `srt` | No | | | `status` | string, one of `queued`, `running`, `retry_wait`, `canceling`, `completed`, `failed`, `cancelled`, `superseded` | No | | | `cursor` | integer, ≥ 0, default 0 | No | | | `limit` | integer, 1–100, default 25 | No | | #### Response 200 One export page. Fields inside `data`: - `schema_version` (1; required) - `exports` (array of object; required) - `schema_version` (1; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `format` (string, one of `mp3`, `wav`, `m4b`, `epub`, `srt`; required) - `status` (string, one of `queued`, `running`, `retry_wait`, `canceling`, `completed`, `failed`, `cancelled`, `superseded`; required) - `phase` (string, one of `queued`, `compiling`, `claiming_artifact`, `downloading`, `mixing`, `encoding`, `packaging`, `uploading`, `complete`, `failed`, `cancelled`; required) - `percent` (integer, 0–100; required) - `cache_result` (string, one of `miss`, `reused`, `coalesced`; required) - `authority` (object; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `is_current` (boolean; required) - `created_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) - `error` (object | null; required) - `code` (string; required) - `message` (string; required) - `artifact` (object | null; required) - `asset_id` (string, uuid; required) - `filename` (string, 1–255 chars; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) - `download_url` (string, uri; required) - `download_url_expires_at` (string, date-time; required) - `artifact_expires_at` (string, date-time; required) - `next_cursor` (string | null, min 1 chars; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/exports" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Create an export `POST /studio/exports` Scope: `studio:write` · Retries: send an `Idempotency-Key` header · Operation ID: `createStudioExport` Renders one section into `mp3`, `wav`, `m4b`, synchronized `epub`, or `srt`, pinned to the document, speaker registry, and mastering versions you send. Returns `202` for new work or `200` when an identical, unexpired file is reused. Requires an `Idempotency-Key` header. #### Headers | Name | Type | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | string, 1–255 chars | Yes | Opaque caller key. The same key and request replay safely; the same key with a different request conflicts. | #### Request body (application/json, required) - `schema_version` (1; required) - `section_id` (string, uuid; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `format` (string, one of `mp3`, `wav`, `m4b`, `epub`, `srt`; required) #### Response 200 An unexpired identical artifact was reused. Fields inside `data`: - `schema_version` (1; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `format` (string, one of `mp3`, `wav`, `m4b`, `epub`, `srt`; required) - `status` (string, one of `queued`, `running`, `retry_wait`, `canceling`, `completed`, `failed`, `cancelled`, `superseded`; required) - `phase` (string, one of `queued`, `compiling`, `claiming_artifact`, `downloading`, `mixing`, `encoding`, `packaging`, `uploading`, `complete`, `failed`, `cancelled`; required) - `percent` (integer, 0–100; required) - `cache_result` (string, one of `miss`, `reused`, `coalesced`; required) - `authority` (object; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `is_current` (boolean; required) - `created_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) - `error` (object | null; required) - `code` (string; required) - `message` (string; required) - `artifact` (object | null; required) - `asset_id` (string, uuid; required) - `filename` (string, 1–255 chars; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) - `download_url` (string, uri; required) - `download_url_expires_at` (string, date-time; required) - `artifact_expires_at` (string, date-time; required) #### Response 202 New durable export work accepted. Fields inside `data`: - `schema_version` (1; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `format` (string, one of `mp3`, `wav`, `m4b`, `epub`, `srt`; required) - `status` (string, one of `queued`, `running`, `retry_wait`, `canceling`, `completed`, `failed`, `cancelled`, `superseded`; required) - `phase` (string, one of `queued`, `compiling`, `claiming_artifact`, `downloading`, `mixing`, `encoding`, `packaging`, `uploading`, `complete`, `failed`, `cancelled`; required) - `percent` (integer, 0–100; required) - `cache_result` (string, one of `miss`, `reused`, `coalesced`; required) - `authority` (object; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `is_current` (boolean; required) - `created_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) - `error` (object | null; required) - `code` (string; required) - `message` (string; required) - `artifact` (object | null; required) - `asset_id` (string, uuid; required) - `filename` (string, 1–255 chars; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) - `download_url` (string, uri; required) - `download_url_expires_at` (string, date-time; required) - `artifact_expires_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `502`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/exports" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "schema_version": 1, "section_id": "11111111-1111-4111-8111-111111111111", "document_version": 7, "speaker_registry_version": 3, "mastering_version": 4, "format": "mp3" }' ``` ### Get an export `GET /studio/exports/{exportId}` Scope: `studio:read` · Operation ID: `getStudioExport` Returns an export's status, phase, percent complete, and, when `status` is `completed`, its downloadable `artifact`. Poll about once per second while it runs. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `exportId` | string, uuid | Yes | | #### Response 200 Current durable export state. Fields inside `data`: - `schema_version` (1; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `format` (string, one of `mp3`, `wav`, `m4b`, `epub`, `srt`; required) - `status` (string, one of `queued`, `running`, `retry_wait`, `canceling`, `completed`, `failed`, `cancelled`, `superseded`; required) - `phase` (string, one of `queued`, `compiling`, `claiming_artifact`, `downloading`, `mixing`, `encoding`, `packaging`, `uploading`, `complete`, `failed`, `cancelled`; required) - `percent` (integer, 0–100; required) - `cache_result` (string, one of `miss`, `reused`, `coalesced`; required) - `authority` (object; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `is_current` (boolean; required) - `created_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) - `error` (object | null; required) - `code` (string; required) - `message` (string; required) - `artifact` (object | null; required) - `asset_id` (string, uuid; required) - `filename` (string, 1–255 chars; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) - `download_url` (string, uri; required) - `download_url_expires_at` (string, date-time; required) - `artifact_expires_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/exports/$EXPORT_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Cancel an export `POST /studio/exports/{exportId}/cancel` Scope: `studio:write` · Operation ID: `cancelStudioExport` Cancels a queued export immediately or asks a running one to stop. Files already produced for other requests are not deleted. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `exportId` | string, uuid | Yes | | #### Response 200 Current cancelled or canceling export state. Fields inside `data`: - `schema_version` (1; required) - `id` (string, uuid; required) - `project_id` (string, uuid; required) - `section_id` (string, uuid; required) - `format` (string, one of `mp3`, `wav`, `m4b`, `epub`, `srt`; required) - `status` (string, one of `queued`, `running`, `retry_wait`, `canceling`, `completed`, `failed`, `cancelled`, `superseded`; required) - `phase` (string, one of `queued`, `compiling`, `claiming_artifact`, `downloading`, `mixing`, `encoding`, `packaging`, `uploading`, `complete`, `failed`, `cancelled`; required) - `percent` (integer, 0–100; required) - `cache_result` (string, one of `miss`, `reused`, `coalesced`; required) - `authority` (object; required) - `document_version` (integer, ≥ 1; required) - `speaker_registry_version` (integer, ≥ 1; required) - `mastering_version` (integer, ≥ 1; required) - `is_current` (boolean; required) - `created_at` (string, date-time; required) - `started_at` (string | null, date-time; required) - `completed_at` (string | null, date-time; required) - `error` (object | null; required) - `code` (string; required) - `message` (string; required) - `artifact` (object | null; required) - `asset_id` (string, uuid; required) - `filename` (string, 1–255 chars; required) - `content_type` (string, min 1 chars; required) - `size_bytes` (integer, ≥ 1; required) - `download_url` (string, uri; required) - `download_url_expires_at` (string, date-time; required) - `artifact_expires_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/exports/$EXPORT_ID/cancel" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` --- Source: https://lyricwinter.com/docs/api/sharing.md --- title: "Sharing API" description: "Publish a section or a whole project at a stable public link, read its sharing state, or revoke the link." canonical_url: https://lyricwinter.com/docs/api/sharing markdown_url: https://lyricwinter.com/docs/api/sharing.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Sharing > Publish a section or a whole project at a stable public link, read its sharing state, or revoke the link. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [Get section sharing](https://lyricwinter.com/docs/api/sharing#get-studio-public-share): `GET /studio/sections/{sectionId}/share` - [Share a section](https://lyricwinter.com/docs/api/sharing#ensure-studio-public-share): `POST /studio/sections/{sectionId}/share` - [Stop sharing a section](https://lyricwinter.com/docs/api/sharing#revoke-studio-public-share): `DELETE /studio/sections/{sectionId}/share` - [Get project sharing](https://lyricwinter.com/docs/api/sharing#get-studio-project-share-state): `GET /studio/projects/{projectId}/share` - [Share a project](https://lyricwinter.com/docs/api/sharing#publish-studio-project): `POST /studio/projects/{projectId}/share` - [Stop sharing a project](https://lyricwinter.com/docs/api/sharing#revoke-studio-project-share): `DELETE /studio/projects/{projectId}/share` ### Get section sharing `GET /studio/sections/{sectionId}/share` Scope: `studio:read` · Operation ID: `getStudioPublicShare` Returns whether the section is public and its active public link, if any. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Response 200 Current section sharing state. Fields inside `data`: - `schema_version` (1; required) - `section_id` (string, uuid; required) - `is_public` (boolean; required) - `share` (object | null; required): Active link; non-null exactly when is_public is true. - `id` (string, uuid; required) - `url` (string, uri; required): Public section page URL of the form /s/. - `created_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/share" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Share a section `POST /studio/sections/{sectionId}/share` Scope: `studio:write` · Operation ID: `ensureStudioPublicShare` Creates a stable public link for the section, or returns the existing one. The public page always plays the latest saved version. Send an empty JSON object as the body. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Request body (application/json, required) - object: Must be an empty JSON object. #### Response 201 Section published with its active link (also returned when already public). Fields inside `data`: - `schema_version` (1; required) - `section_id` (string, uuid; required) - `is_public` (boolean; required) - `share` (object | null; required): Active link; non-null exactly when is_public is true. - `id` (string, uuid; required) - `url` (string, uri; required): Public section page URL of the form /s/. - `created_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/share" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ### Stop sharing a section `DELETE /studio/sections/{sectionId}/share` Scope: `studio:write` · Operation ID: `revokeStudioPublicShare` Revokes the section's public link and makes the section private. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `sectionId` | string, uuid | Yes | | #### Response 200 Section is private and the prior link is revoked. Fields inside `data`: - `schema_version` (1; required) - `section_id` (string, uuid; required) - `is_public` (boolean; required) - `share` (object | null; required): Active link; non-null exactly when is_public is true. - `id` (string, uuid; required) - `url` (string, uri; required): Public section page URL of the form /s/. - `created_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/studio/sections/$SECTION_ID/share" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Get project sharing `GET /studio/projects/{projectId}/share` Scope: `studio:read` · Operation ID: `getStudioProjectShareState` Returns whether the project has a public link and the active link, if any. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Response 200 Current project public-link state. Fields inside `data`: - `schema_version` (1; required) - `project_id` (string, uuid; required) - `is_public` (boolean; required) - `share` (object | null; required): Active link; non-null exactly when is_public is true. - `id` (string, uuid; required) - `url` (string, uri; required): Public project page URL of the form /p/. - `created_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `500`, `503` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/share" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Share a project `POST /studio/projects/{projectId}/share` Scope: `studio:write` · Operation ID: `publishStudioProject` Creates a stable public link for the whole project, or returns the existing one. Send an empty JSON object as the body. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Request body (application/json, required) - object: Must be an empty JSON object. #### Response 201 Stable project public link. Fields inside `data`: - `schema_version` (1; required) - `project_id` (string, uuid; required) - `is_public` (boolean; required) - `share` (object | null; required): Active link; non-null exactly when is_public is true. - `id` (string, uuid; required) - `url` (string, uri; required): Public project page URL of the form /p/. - `created_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `409`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/share" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` ### Stop sharing a project `DELETE /studio/projects/{projectId}/share` Scope: `studio:write` · Operation ID: `revokeStudioProjectShare` Revokes the project's public link. Send no request body. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Response 200 Project sharing is revoked. Fields inside `data`: - `schema_version` (1; required) - `project_id` (string, uuid; required) - `is_public` (boolean; required) - `share` (object | null; required): Active link; non-null exactly when is_public is true. - `id` (string, uuid; required) - `url` (string, uri; required): Public project page URL of the form /p/. - `created_at` (string, date-time; required) Errors: `400`, `401`, `403`, `404`, `500`, `503` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/share" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` --- Source: https://lyricwinter.com/docs/api/sounds.md --- title: "Sound library API" description: "Upload your own sound effects and ambience into the Studio sound library, manage their metadata, and link them to projects so Create Audio can place them automatically." canonical_url: https://lyricwinter.com/docs/api/sounds markdown_url: https://lyricwinter.com/docs/api/sounds.md last_updated: 2026-09-30 status: beta --- # LyricWinter API reference: Sound library > Upload your own sound effects and ambience into the Studio sound library, manage their metadata, and link them to projects so Create Audio can place them automatically. The LyricWinter API is in beta. Base URL: `https://lyricwinter.com/api/v1`. Authenticate every request with `Authorization: Bearer lw_...`. Successful responses are wrapped as `{ "data": ..., "request_id": "req_..." }`. Machine-readable contract: https://lyricwinter.com/api/v1/openapi.json. ## Endpoints - [List sounds](https://lyricwinter.com/docs/api/sounds#list-studio-sounds): `GET /studio/sounds` - [Start a sound upload](https://lyricwinter.com/docs/api/sounds#create-studio-sound-upload): `POST /studio/sounds` - [Complete a sound upload](https://lyricwinter.com/docs/api/sounds#complete-studio-sound-upload): `POST /studio/sounds/{soundId}/uploads/{uploadId}/complete` - [Get sound upload status](https://lyricwinter.com/docs/api/sounds#get-studio-sound-upload-status): `GET /studio/sounds/{soundId}/uploads/latest` - [Get a sound](https://lyricwinter.com/docs/api/sounds#get-studio-sound): `GET /studio/sounds/{soundId}` - [Update a sound](https://lyricwinter.com/docs/api/sounds#update-studio-sound): `PATCH /studio/sounds/{soundId}` - [Delete a sound](https://lyricwinter.com/docs/api/sounds#delete-studio-sound): `DELETE /studio/sounds/{soundId}` - [Create a sound preview URL](https://lyricwinter.com/docs/api/sounds#create-studio-sound-preview-url): `POST /studio/sounds/{soundId}/preview-url` - [List project sounds](https://lyricwinter.com/docs/api/sounds#list-studio-project-sounds): `GET /studio/projects/{projectId}/sounds` - [Link a sound to a project](https://lyricwinter.com/docs/api/sounds#add-studio-project-sound): `POST /studio/projects/{projectId}/sounds` - [Unlink a sound from a project](https://lyricwinter.com/docs/api/sounds#remove-studio-project-sound): `DELETE /studio/projects/{projectId}/sounds/{soundId}` ### List sounds `GET /studio/sounds` Scope: `studio:read` · Operation ID: `listStudioSounds` Lists sounds in your library, sounds shared with you, or public sounds, filtered by `scope` and an optional search query. Results are cursor-paginated. #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `scope` | string, default "mine", one of `mine`, `shared`, `public` | No | | | `q` | string, max 200 chars | No | | | `project_id` | string, uuid | No | | | `in_project` | boolean | No | | | `limit` | integer, 1–100, default 30 | No | | | `cursor` | string, max 500 chars | No | | #### Response 200 Cursor-paginated sound library. Fields inside `data`: - `schema_version` ("studio-sound-library-1"; required) - `sounds` (array of object, max 100 items; required) - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `next_cursor` (string | null, 1–500 chars; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sounds" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Start a sound upload `POST /studio/sounds` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `createStudioSoundUpload` Creates a private sound and returns a signed upload session. Upload the file to the session URL, then call the complete endpoint. Requesting `is_public: true` returns `403` because public listing requires review. #### Request body (application/json, required) - `schema_version` ("studio-sound-upload-1"; required) - `client_mutation_id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `is_public` (boolean; required) - `rights_acknowledgment_version` ("studio-sound-rights-1"; required) - `file` (object; required) - `filename` (string, 1–255 chars; required) - `content_type` (string, one of `audio/wav`, `audio/x-wav`, `audio/mpeg`, `audio/ogg`, `audio/opus`, `audio/flac`, `audio/mp4`, `audio/aac`, `audio/webm`; required) - `size_bytes` (integer, 1–52428800; required) #### Response 201 Upload session created. Fields inside `data`: - `schema_version` ("studio-sound-upload-1"; required) - `sound` (object; required) - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `upload` (object; required) - `id` (string, uuid; required) - `url` (string, uri; required) - `method` ("PUT"; required) - `headers` (map of string; required) - `expires_at` (string, date-time; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/sounds" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": "studio-sound-upload-1", "client_mutation_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "name": "My integration", "description": "string", "tags": [ "string" ], "is_public": true, "rights_acknowledgment_version": "studio-sound-rights-1", "file": { "filename": "string", "content_type": "audio/wav", "size_bytes": 1 } }' ``` ### Complete a sound upload `POST /studio/sounds/{soundId}/uploads/{uploadId}/complete` Scope: `studio:write` · Operation ID: `completeStudioSoundUpload` Tells LyricWinter the file has been uploaded so it can process the sound. Poll the latest upload status until the sound is ready. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `soundId` | string, uuid | Yes | | | `uploadId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` ("studio-sound-upload-complete-1"; required) #### Response 200 Idempotent finalization result. Fields inside `data`: - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) #### Response 202 Sanitization accepted. Fields inside `data`: - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/sounds/$SOUND_ID/uploads/$UPLOAD_ID/complete" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": "studio-sound-upload-complete-1" }' ``` ### Get sound upload status `GET /studio/sounds/{soundId}/uploads/latest` Scope: `studio:read` · Operation ID: `getStudioSoundUploadStatus` Returns the processing status of the most recent upload for a sound you own. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `soundId` | string, uuid | Yes | | #### Response 200 Latest owner upload, independent of the playable revision; null when none exists. Fields inside `data`: - `schema_version` (string, one of `studio-sound-upload-status-1`; required) - `sound_id` (string, uuid; required) - `latest` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `is_replacement` (boolean; required) - `status` (string, one of `pending_upload`, `processing`, `ready`, `failed`, `expired`; required) - `completed_revision_id` (string | null, uuid; required) - `error` (object | null; required) - `code` (string | null; required) - `message` (string; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `expires_at` (string, date-time; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sounds/$SOUND_ID/uploads/latest" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Get a sound `GET /studio/sounds/{soundId}` Scope: `studio:read` · Operation ID: `getStudioSound` Returns one sound's metadata and processing state. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `soundId` | string, uuid | Yes | | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `project_id` | string, uuid | No | | #### Response 200 Accessible sound detail. Fields inside `data`: - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/sounds/$SOUND_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Update a sound `PATCH /studio/sounds/{soundId}` Scope: `studio:write` · Operation ID: `updateStudioSound` Updates a sound's name, description, or tags. A public sound must be made private before its metadata can change (`409`). #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `soundId` | string, uuid | Yes | | #### Request body (application/json, required) - any #### Response 200 Updated sound. Fields inside `data`: - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X PATCH "https://lyricwinter.com/api/v1/studio/sounds/$SOUND_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d 'null' ``` ### Delete a sound `DELETE /studio/sounds/{soundId}` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `deleteStudioSound` Deletes a sound you own. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `soundId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` ("studio-sound-mutation-1"; required) - `client_mutation_id` (string, uuid; required) - `expected_metadata_version` (integer, ≥ 1; required) #### Response 200 Logical sound deleted. Fields inside `data`: - `sound_effect_id` (string, uuid; required) - `deleted` (true; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/studio/sounds/$SOUND_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": "studio-sound-mutation-1", "client_mutation_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "expected_metadata_version": 1 }' ``` ### Create a sound preview URL `POST /studio/sounds/{soundId}/preview-url` Scope: `studio:read` · Operation ID: `createStudioSoundPreviewUrl` Returns a short-lived URL for listening to a sound. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `soundId` | string, uuid | Yes | | #### Response 200 Access-checked short-lived same-origin preview capability URL. Fields inside `data`: - `schema_version` ("studio-sound-preview-1"; required) - `url` (string, uri; required) - `expires_at` (string, date-time; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/sounds/$SOUND_ID/preview-url" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### List project sounds `GET /studio/projects/{projectId}/sounds` Scope: `studio:read` · Operation ID: `listStudioProjectSounds` Lists the sounds linked to a project. Create Audio can place linked sounds automatically when `place_project_sounds` is on. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `q` | string, max 200 chars | No | | | `limit` | integer, 1–100, default 50 | No | | | `cursor` | string, max 500 chars | No | | #### Response 200 Project sound registry page. Fields inside `data`: - `schema_version` ("studio-sound-library-1"; required) - `sounds` (array of object, max 100 items; required) - `id` (string, uuid; required) - `name` (string, 1–120 chars; required) - `description` (string, 1–1000 chars; required) - `tags` (array of string, 1–12 items, unique; required) - `status` (string, one of `uploading`, `processing`, `ready`, `failed`; required) - `is_public` (boolean; required) - `owner_display_name` (string | null, 1–200 chars; required) - `is_owned_by_user` (boolean; required) - `is_shared_with_user` (boolean; required) - `incoming_share` (object | null; required) - `id` (string, uuid; required) - `status` (string, one of `pending`, `accepted`, `declined`; required) - `is_in_project` (boolean; required) - `can_edit` (boolean; required) - `can_delete` (boolean; required) - `can_share` (boolean; required) - `metadata_version` (integer, ≥ 1; required) - `current_revision` (object | null; required) - `id` (string, uuid; required) - `revision_number` (integer, ≥ 1; required) - `duration_ms` (integer, 100–300000; required) - `mime_type` (string, 1–200 chars; required) - `file_size_bytes` (integer, 1–62914560; required) - `created_at` (string, date-time; required) - `error` (object | null; required) - `code` (string | null, max 200 chars; required) - `message` (string, 1–1000 chars; required) - `created_at` (string, date-time; required) - `updated_at` (string, date-time; required) - `next_cursor` (string | null, 1–500 chars; required) - `sound_source_registry_version` (integer, ≥ 1; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sounds" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" ``` ### Link a sound to a project `POST /studio/projects/{projectId}/sounds` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `addStudioProjectSound` Links a ready sound from your library to a project so Create Audio can place it. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` ("studio-project-sound-source-1"; required) - `client_mutation_id` (string, uuid; required) - `sound_effect_id` (string, uuid; required) - `expected_registry_version` (integer, ≥ 1; required) #### Response 201 Project sound membership updated. Fields inside `data`: - `schema_version` ("studio-project-sound-source-1"; required) - `project_id` (string, uuid; required) - `sound_effect_id` (string, uuid; required) - `linked` (boolean; required) - `sound_source_registry_version` (integer, ≥ 1; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X POST "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sounds" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": "studio-project-sound-source-1", "client_mutation_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "sound_effect_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "expected_registry_version": 1 }' ``` ### Unlink a sound from a project `DELETE /studio/projects/{projectId}/sounds/{soundId}` Scope: `studio:write` · Retries: reuse the same `client_mutation_id` · Operation ID: `removeStudioProjectSound` Removes a sound from a project. The sound stays in your library. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `projectId` | string, uuid | Yes | | | `soundId` | string, uuid | Yes | | #### Request body (application/json, required) - `schema_version` ("studio-project-sound-source-1"; required) - `client_mutation_id` (string, uuid; required) - `sound_effect_id` (string, uuid; required) - `expected_registry_version` (integer, ≥ 1; required) #### Response 200 Project sound membership updated. Fields inside `data`: - `schema_version` ("studio-project-sound-source-1"; required) - `project_id` (string, uuid; required) - `sound_effect_id` (string, uuid; required) - `linked` (boolean; required) - `sound_source_registry_version` (integer, ≥ 1; required) Errors: `default` return the standard error envelope. #### Example request ```bash curl -X DELETE "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/sounds/$SOUND_ID" \ -H "Authorization: Bearer $LYRICWINTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "schema_version": "studio-project-sound-source-1", "client_mutation_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10", "sound_effect_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20", "expected_registry_version": 1 }' ``` --- Source: https://lyricwinter.com/docs/mcp.md --- title: "LyricWinter MCP server: connect Claude, ChatGPT, Cursor, and other agents" description: "Connect any MCP client to the remote LyricWinter Studio MCP server at https://lyricwinter.com/mcp using Streamable HTTP with OAuth 2.1 or a scoped API key." canonical_url: https://lyricwinter.com/docs/mcp markdown_url: https://lyricwinter.com/docs/mcp.md last_updated: 2026-09-30 status: beta --- # LyricWinter MCP server: connect Claude, ChatGPT, Cursor, and other agents > Connect any MCP client to the remote LyricWinter Studio MCP server at https://lyricwinter.com/mcp using Streamable HTTP with OAuth 2.1 or a scoped API key. The LyricWinter MCP server lets AI agents work in LyricWinter Studio on your behalf. Connect any Model Context Protocol client to `https://lyricwinter.com/mcp` and the agent can list your projects, read story documents, create stories, generate multi-voice audio, and follow workflow progress. The server uses the Streamable HTTP transport and authenticates with OAuth 2.1 or a Studio-scoped API key. | Property | Value | | --- | --- | | Server URL | `https://lyricwinter.com/mcp` | | Transport | Streamable HTTP | | Authentication | OAuth 2.1 with PKCE, or `Authorization: Bearer lw_...` | | Scopes | `studio:read` (required), `studio:write` (to create stories and audio) | | Tools | [13 Studio tools](/docs/mcp-tools) | | Status | Beta | ## Connect Claude **Claude on the web and Claude Desktop.** Open **Settings → Connectors**, choose **Add custom connector**, and enter `https://lyricwinter.com/mcp`. Claude opens LyricWinter so you can sign in and approve access. **Claude Code.** Add the server, then run `/mcp` inside Claude Code and choose **Authenticate**: ```bash claude mcp add --transport http lyricwinter https://lyricwinter.com/mcp ``` ## Connect ChatGPT In ChatGPT, turn on developer mode for apps and connectors, then create a connector with the URL `https://lyricwinter.com/mcp` and OAuth authentication. ChatGPT sends you to LyricWinter to sign in and approve access. Refresh the connector after LyricWinter adds new tools. ## Connect VS Code Add the server to `.vscode/mcp.json` in your workspace, or to your user MCP configuration. VS Code prompts you to sign in when the server starts. ```json title=".vscode/mcp.json" { "servers": { "lyricwinter": { "type": "http", "url": "https://lyricwinter.com/mcp" } } } ``` ## Connect Cursor Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project). Use a Studio-scoped [API key](/docs/authentication) in the `Authorization` header: ```json title="~/.cursor/mcp.json" { "mcpServers": { "lyricwinter": { "url": "https://lyricwinter.com/mcp", "headers": { "Authorization": "Bearer lw_..." } } } } ``` ## Connect Codex Add the server to `~/.codex/config.toml`, then sign in: ```toml title="~/.codex/config.toml" [mcp_servers.lyricwinter] url = "https://lyricwinter.com/mcp" ``` ```bash codex mcp login lyricwinter ``` ## Connect other MCP clients Any client that supports remote MCP servers over Streamable HTTP can connect: 1. Point the client at `https://lyricwinter.com/mcp`. 2. If the client supports MCP authorization, it discovers LyricWinter's OAuth server from the `WWW-Authenticate` challenge and the protected-resource metadata at `https://lyricwinter.com/.well-known/oauth-protected-resource/mcp`, registers itself, and opens the LyricWinter sign-in page. 3. Otherwise, send a Studio-scoped API key as `Authorization: Bearer lw_...`. To test the server by hand, run the MCP Inspector and connect it to the same URL: ```bash npx @modelcontextprotocol/inspector ``` ## How does MCP authentication work? LyricWinter hosts its own OAuth 2.1 authorization server for the MCP server: 1. An unauthenticated request returns `401` with a `WWW-Authenticate` header that points to the protected-resource metadata. 2. The client registers through dynamic client registration (`/oauth/register`) and starts the authorization code flow with PKCE (`S256`). 3. You sign in to LyricWinter and approve `studio:read` and, if the client asks for it, `studio:write`. 4. LyricWinter issues a one-hour access token and a 30-day refresh token that rotates on every use. Each token only reaches the account that approved it. To disconnect a client, revoke its key in the **Developers** panel of your [dashboard](/dashboard). See [Authentication](/docs/authentication#how-do-mcp-clients-authenticate) for endpoints and supported parameters. Clients that cannot complete OAuth can send a LyricWinter API key with the `studio:read` and `studio:write` scopes instead. ## What can I ask an agent to do? Once connected, ask in plain language. For example: - "Show my LyricWinter Studio projects." - "Create a Studio story called *The Lighthouse* from this text and prepare it for audio." - "Add this chapter to my *Winter Road* project." - "Generate audio for chapters 1 through 3 and tell me when it's done." - "Which characters speak in this project, and how often?" The agent picks the right [tools](/docs/mcp-tools) and polls workflows until they finish. The tool descriptions tell agents to confirm with you before starting paid audio generation. ## Does the MCP server cost anything? Connecting and reading are free. `start_audio_workflow` runs Create Audio, which consumes words from your LyricWinter balance exactly as it does in the Studio app. The tool is marked as having side effects so that clients ask for confirmation, and its description tells agents to confirm scope and cost with you first. ## What the MCP server does not do yet The beta MCP server covers the core story-to-audio loop. These tasks are available in the [REST API](/docs/api) and the [Studio app](/studio) but not yet as MCP tools: - Editing document blocks and recasting speakers - Exports to MP3, WAV, M4B, EPUB, or SRT - Public share links - Uploading sounds or custom voices ## Troubleshooting | Symptom | Fix | | --- | --- | | The client says authorization is required or expired | Reconnect or re-authenticate the server in your client. Access tokens last one hour and refresh automatically when the client supports it. | | A tool returns "lacks Studio access" | The token or API key is missing `studio:read` or `studio:write`. Reconnect and approve both, or use a key with both scopes. | | `403 Origin is not allowed` | Browser-based clients must connect from `lyricwinter.com` or `chatgpt.com`. Use a desktop, CLI, or server-side client instead. | | A write tool timed out | The result includes a `mutation_id`. Call the tool again with the same `mutation_id` to retry safely without creating a duplicate or a second paid run. | --- Source: https://lyricwinter.com/docs/mcp-tools.md --- title: "LyricWinter MCP tools reference" description: "Every tool exposed by the LyricWinter Studio MCP server, with inputs, required scopes, side effects, and the recommended agent workflow." canonical_url: https://lyricwinter.com/docs/mcp-tools markdown_url: https://lyricwinter.com/docs/mcp-tools.md last_updated: 2026-09-30 status: beta --- # LyricWinter MCP tools reference > Every tool exposed by the LyricWinter Studio MCP server, with inputs, required scopes, side effects, and the recommended agent workflow. The LyricWinter MCP server exposes 13 tools. Read-only tools need the `studio:read` scope; tools that create or change Studio content need `studio:write`. Every tool calls the [LyricWinter REST API](/docs/api) as the connected user and returns its JSON response as text. ## Recommended agent workflow The server gives agents these instructions: use `list_projects` and `list_project_sections` to find IDs, read documents before changing them or generating audio, treat workflow starts as asynchronous and call `get_workflow` until they finish, and never repeat a paid audio start unless the user wants another run. A typical session: 1. `list_projects` to find the project, or `create_story` to start a new one. 2. `get_document` to read the script and its versions. 3. `start_audio_workflow` after the user confirms which sections to generate. 4. `get_workflow` until the run reaches `completed`, `partially_completed`, `failed`, or `cancelled`. 5. `get_section_media` or `get_playback_manifest` to return playable audio. ## Tool summary | Tool | Purpose | Scope | Side effects | | --- | --- | --- | --- | | `profile` | Identify the connected account | `studio:read` | None | | `list_projects` | List projects and standalone stories | `studio:read` | None | | `list_project_sections` | List a project's sections in order | `studio:read` | None | | `get_document` | Read a section's document | `studio:read` | None | | `list_project_speakers` | List a project's speakers | `studio:read` | None | | `create_story` | Create a standalone story | `studio:write` | Creates content | | `create_section` | Add a section to a project | `studio:write` | Creates content | | `prepare_sections` | Structure sections into a script | `studio:write` | Changes documents | | `start_audio_workflow` | Generate audio | `studio:write` | Consumes words | | `get_workflow` | Check workflow progress | `studio:read` | None | | `cancel_workflow` | Cancel a running workflow | `studio:write` | Stops work | | `get_section_media` | Get per-block audio for a section | `studio:read` | None | | `get_playback_manifest` | Get the compiled playback timeline | `studio:read` | None | ## Results and errors A successful tool returns the REST API's JSON response, including `data` and `request_id`. Tools that accept a `mutation_id` wrap it as `{ "result": ..., "mutation_id": "..." }` so the agent can retry the same change. A failed tool sets `isError` and returns the API error, plus the `mutation_id` when there is one. If LyricWinter could not confirm whether a write happened, the error says "No result was confirmed"; retry with the same `mutation_id`. ## Account ### `profile` Identifies the connected LyricWinter account. Returns `id`, `name`, and `email` as structured content. Takes no input. ## Projects and sections ### `list_projects` Lists the user's Studio projects and standalone stories with their IDs. Takes no input. Calls [`GET /studio/projects`](/docs/api/projects). ### `list_project_sections` Lists the sections of one project in order, including each section's ID and the project's `section_order_version`. Calls [`GET /studio/projects/{projectId}/sections`](/docs/api/sections). | Input | Type | Required | Description | | --- | --- | --- | --- | | `project_id` | UUID | Yes | The project to list. | ### `create_story` Creates a standalone Studio story from text. Calls [`POST /studio/sections`](/docs/api/sections). | Input | Type | Required | Description | | --- | --- | --- | --- | | `title` | string, 1–500 characters | Yes | Story title. | | `raw_text` | string, 1–100,000 characters | Yes | The story text. | | `mutation_id` | UUID | No | Reuse only when retrying the exact same creation. Generated if omitted. | ### `create_section` Adds a section to an existing project. Read the project's sections first to get its current `section_order_version`. Calls [`POST /studio/projects/{projectId}/sections`](/docs/api/sections). | Input | Type | Required | Description | | --- | --- | --- | --- | | `project_id` | UUID | Yes | The project to add to. | | `base_section_order_version` | integer or null | Yes | The `section_order_version` from `list_project_sections`. | | `title` | string, 1–500 characters | Yes | Section title. | | `raw_text` | string, 1–100,000 characters | Yes | The section text. | | `mutation_id` | UUID | No | Reuse only when retrying the exact same creation. | ## Documents and speakers ### `get_document` Reads a section's current document: its version, blocks, speakers, and edit targets. Agents should read the document before generating audio. Calls [`GET /studio/sections/{sectionId}/document`](/docs/api/documents). | Input | Type | Required | Description | | --- | --- | --- | --- | | `section_id` | UUID | Yes | The section to read. | ### `list_project_speakers` Lists the speakers used in a project and their casting status. Calls [`GET /studio/projects/{projectId}/speakers`](/docs/api/documents). | Input | Type | Required | Description | | --- | --- | --- | --- | | `project_id` | UUID | Yes | The project to inspect. | ## Workflows ### `prepare_sections` Starts an asynchronous workflow that structures sections into narration and dialogue without generating audio. Poll `get_workflow` for completion. Calls [`POST /studio/projects/{projectId}/workflows`](/docs/api/workflows) with `workflow_kind: "prepare_sections"`. | Input | Type | Required | Description | | --- | --- | --- | --- | | `project_id` | UUID | Yes | The project that owns the sections. | | `section_ids` | array of UUIDs, 1–100 | Yes | Sections to prepare. | | `mutation_id` | UUID | No | Reuse only when retrying the same request. | ### `start_audio_workflow` Starts the paid, asynchronous Create Audio workflow for selected sections. It prepares text, casts voices, directs performances, adds sound effects, and generates audio. Confirm the scope and cost with the user first, then poll `get_workflow`. Calls [`POST /studio/projects/{projectId}/workflows`](/docs/api/workflows) with `workflow_kind: "create_audio"`. | Input | Type | Required | Description | | --- | --- | --- | --- | | `project_id` | UUID | Yes | The project that owns the sections. | | `section_ids` | array of UUIDs, 1–12 | Yes | Sections to generate. | | `include_generated_sfx` | boolean | No | Add AI-generated sound effects. Defaults to `true`. | | `place_project_sounds` | boolean | No | Place sounds linked to the project. Defaults to `true`. | | `mutation_id` | UUID | No | Reuse only when retrying the same run. A new ID starts a new, separately billed run. | ### `get_workflow` Returns a workflow's status, current stage, step progress, warnings, and errors. Calls [`GET /studio/workflows/{workflowRunId}`](/docs/api/workflows). | Input | Type | Required | Description | | --- | --- | --- | --- | | `workflow_id` | UUID | Yes | The `run.id` returned when the workflow started. | ### `cancel_workflow` Requests cancellation of a running workflow. Agents should ask the user before cancelling. Calls [`POST /studio/workflows/{workflowRunId}/cancel`](/docs/api/workflows). | Input | Type | Required | Description | | --- | --- | --- | --- | | `workflow_id` | UUID | Yes | The workflow to cancel. | ## Media and playback ### `get_section_media` Returns the generated audio state of every block in a section, with short-lived signed playback URLs. Calls [`GET /studio/sections/{sectionId}/media`](/docs/api/media). | Input | Type | Required | Description | | --- | --- | --- | --- | | `section_id` | UUID | Yes | The section to inspect. | ### `get_playback_manifest` Returns the compiled playback timeline for a section. Pass the versions returned by `get_document`. Calls [`GET /studio/sections/{sectionId}/playback-manifest`](/docs/api/media). | Input | Type | Required | Description | | --- | --- | --- | --- | | `section_id` | UUID | Yes | The section to play. | | `document_version` | integer | Yes | `document.document_version` from `get_document`. | | `speaker_registry_version` | integer | Yes | `document.speaker_registry_version` from `get_document`. |