API reference
Projects API
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.
BetaUpdated
- Base URL
- https://lyricwinter.com/api/v1
- Authentication
- Authorization: Bearer lw_…
- Contract
- openapi.json
List projects and standalone stories
GET/studio/projects
studio:readReturns named projects in projects and standalone stories in unassigned_sections. Every standalone story still has a project_id that works with project-scoped endpoints. Section summaries include source_word_count when available: the word count of saved source text, including text excluded from narration.
Response 200
Named projects and unassigned Studio story summaries. Fields below are inside data.
projectsarray of objectrequired4 item attributes
last_active_atstring | nulldate-timeidstringrequireduuidnamestringrequiredproject_kind"named"required
unassigned_sectionsarray of objectrequiredStandalone Studio stories shown under the virtual Unassigned group, one section per hidden project.
2 item attributes
project_idstringrequireduuidHidden standalone project container that owns the story.
sectionobjectrequired11 child attributes
last_opened_atstring | nulldate-timehas_audiobooleanPresent, and true, only with activity_status needs_audio: at least one line already has finished audio, so the section needs updating or finishing rather than creating.
idstringrequireduuidproject_idstringrequireduuidtitlestringrequireddocument_versionintegerrequired≥ 1source_text_lengthintegerrequired≥ 0Length of the section's exact source-text projection in UTF-16 code units.
source_word_countinteger≥ 0Word count of the saved source text using Unicode word segmentation. Includes text excluded from narration; sound prompts and notes are not source text. Omitted when unavailable.
activity_statusstringrequiredNavigator 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).
One of
blankworkingneeds_audioaudio_readyaudio_failedcreated_atstringrequireddate-timeupdated_atstringrequireddate-time
Errors 401, 500 use the standard error envelope.
curl "https://lyricwinter.com/api/v1/studio/projects" \
-H "Authorization: Bearer $LYRICWINTER_API_KEY"const response = await fetch(`https://lyricwinter.com/api/v1/studio/projects`, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.LYRICWINTER_API_KEY}`,
},
});
const { data, error, request_id } = await response.json();import os
import requests
response = requests.request(
"GET",
f"https://lyricwinter.com/api/v1/studio/projects",
headers={
"Authorization": f"Bearer {os.environ['LYRICWINTER_API_KEY']}",
},
)
payload = response.json(){
"data": {
"projects": [
{
"last_active_at": "2026-09-30T17:00:00.000Z",
"id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10",
"name": "My integration",
"project_kind": "named"
}
],
"unassigned_sections": [
{
"project_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20",
"section": {
"last_opened_at": "2026-09-30T17:00:00.000Z",
"has_audio": true,
"id": "45c48cce-2e2d-4fbd-8a1c-5e6f7a8b9c30",
"project_id": "d3d94468-02a4-4c5e-b6f7-0a1b2c3d4e40",
"title": "Chapter One",
"document_version": 1,
"source_text_length": 1,
"source_word_count": 1,
"activity_status": "blank",
"created_at": "2026-09-30T17:00:00.000Z",
"updated_at": "2026-09-30T17:00:00.000Z"
}
}
]
},
"request_id": "req_01J9Z3K8QF4"
}Turn a standalone story into a project
PATCH/studio/projects/{projectId}
studio:writeGives 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
projectIdstringrequireduuid
Request body application/json
schema_version1requirednamestringrequiredmin 1 charspattern \SProject 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 below are inside data.
projectobjectrequired4 child attributes
last_active_atstring | nulldate-timeidstringrequireduuidnamestringrequiredproject_kind"named"required
Errors 400, 401, 404, 409, 500 use the standard error envelope.
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"
}'const projectId = "<projectId>";
const response = await fetch(`https://lyricwinter.com/api/v1/studio/projects/${projectId}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.LYRICWINTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"schema_version": 1,
"name": "My integration"
}),
});
const { data, error, request_id } = await response.json();import os
import requests
project_id = "<projectId>"
response = requests.request(
"PATCH",
f"https://lyricwinter.com/api/v1/studio/projects/{project_id}",
headers={
"Authorization": f"Bearer {os.environ['LYRICWINTER_API_KEY']}",
},
json={
"schema_version": 1,
"name": "My integration"
},
)
payload = response.json(){
"data": {
"project": {
"last_active_at": "2026-09-30T17:00:00.000Z",
"id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10",
"name": "My integration",
"project_kind": "named"
}
},
"request_id": "req_01J9Z3K8QF4"
}Preview project deletion
GET/studio/projects/{projectId}/deletion-info
studio:readReturns the contents deletion would remove. Add consent_version=1 to receive a deletion_revision, current project kind and display_name for confirmation. The display name is the current project name, or the current section title for a standalone story. Keep that revision with the confirmed deletion, including across interrupted requests.
Path parameters
projectIdstringrequireduuid
Query parameters
consent_versionstringSet to 1 to review the exact saved contents before confirmation.
One of
1
Response 200
Project deletion preview.
One of 2 shapes:
StudioProjectDeletionInfoEnvelope
dataobjectrequired9 child attributes
project_idstringrequireduuidsection_countintegerrequired≥ 0other_story_countintegerrequired≥ 0block_countintegerrequired≥ 0audio_run_countintegerrequired≥ 0clip_countintegerrequired≥ 0studio_media_countintegerrequired≥ 0active_share_link_countintegerrequired≥ 0ai_designed_voice_countintegerrequired≥ 0
request_idstring
StudioProjectDeletionReviewEnvelope
dataobjectrequired12 child attributes
project_idstringrequireduuidsection_countintegerrequired≥ 0other_story_countintegerrequired≥ 0block_countintegerrequired≥ 0audio_run_countintegerrequired≥ 0clip_countintegerrequired≥ 0studio_media_countintegerrequired≥ 0active_share_link_countintegerrequired≥ 0ai_designed_voice_countintegerrequired≥ 0deletion_revisionstringrequiredpattern ^pd1:[0-9a-f]{64}$project_kindstringrequiredOne of
standalonenameddisplay_namestringrequiredThe current named project name or standalone story section title, read with deletion_revision.
request_idstring
Errors 400, 401, 404, 500 use the standard error envelope.
curl "https://lyricwinter.com/api/v1/studio/projects/$PROJECT_ID/deletion-info" \
-H "Authorization: Bearer $LYRICWINTER_API_KEY"const projectId = "<projectId>";
const response = await fetch(`https://lyricwinter.com/api/v1/studio/projects/${projectId}/deletion-info`, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.LYRICWINTER_API_KEY}`,
},
});
const { data, error, request_id } = await response.json();import os
import requests
project_id = "<projectId>"
response = requests.request(
"GET",
f"https://lyricwinter.com/api/v1/studio/projects/{project_id}/deletion-info",
headers={
"Authorization": f"Bearer {os.environ['LYRICWINTER_API_KEY']}",
},
)
payload = response.json(){
"data": {
"project_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10",
"section_count": 1,
"other_story_count": 1,
"block_count": 1,
"audio_run_count": 1,
"clip_count": 1,
"studio_media_count": 1,
"active_share_link_count": 1,
"ai_designed_voice_count": 1
},
"request_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20"
}Delete a project
DELETE/studio/projects/{projectId}
studio:writeDeletes a project with all of its sections, in-progress work, playback and share links. Send schema_version: 2, the reviewed expected_project_kind and the exact expected_deletion_revision the caller confirmed. Changed contents or legacy consent return 409 PROJECT_DELETION_REVIEW_REQUIRED; obtain explicit renewed confirmation instead of silently adopting a new revision. 409 PROJECT_DELETION_BUSY allows a later user-initiated retry with the original consent; do not retry automatically. This cannot be undone through the API.
Path parameters
projectIdstringrequireduuid
Request body application/json
schema_version2requiredexpected_project_kindstringrequiredThe project kind the deleting client most recently observed. The server rejects deletion if the live kind has changed.
One of
standalonenameddelete_ai_designed_voicesbooleanrequiredexpected_deletion_revisionstringrequiredpattern ^pd1:[0-9a-f]{64}$The unchanged deletion_revision returned by the review the caller confirmed.
Response 200
Project deletion counts and optional AI-designed voice cleanup result. Fields below are inside data.
successtruerequiredproject_idstringrequireduuidmode"project_and_stories"requiredstories_deletedintegerrequired≥ 0audio_runs_deletedintegerrequired≥ 0clips_deletedintegerrequired≥ 0public_stories_deletedintegerrequired≥ 0shared_stories_deletedintegerrequired≥ 0active_share_links_deletedintegerrequired≥ 0studio_sections_deletedintegerrequired≥ 0studio_media_marked_for_deletionintegerrequired≥ 0files_deletedintegerrequired≥ 0ai_designed_voices_deletedintegerrequired≥ 0ai_designed_voices_retainedintegerrequired≥ 0
Errors 400, 401, 404, 409, 500 use the standard error envelope.
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": 2,
"expected_project_kind": "standalone",
"delete_ai_designed_voices": true,
"expected_deletion_revision": "string"
}'const projectId = "<projectId>";
const response = await fetch(`https://lyricwinter.com/api/v1/studio/projects/${projectId}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.LYRICWINTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"schema_version": 2,
"expected_project_kind": "standalone",
"delete_ai_designed_voices": true,
"expected_deletion_revision": "string"
}),
});
const { data, error, request_id } = await response.json();import os
import requests
project_id = "<projectId>"
response = requests.request(
"DELETE",
f"https://lyricwinter.com/api/v1/studio/projects/{project_id}",
headers={
"Authorization": f"Bearer {os.environ['LYRICWINTER_API_KEY']}",
},
json={
"schema_version": 2,
"expected_project_kind": "standalone",
"delete_ai_designed_voices": True,
"expected_deletion_revision": "string"
},
)
payload = response.json(){
"data": {
"success": true,
"project_id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10",
"mode": "project_and_stories",
"stories_deleted": 1,
"audio_runs_deleted": 1,
"clips_deleted": 1,
"public_stories_deleted": 1,
"shared_stories_deleted": 1,
"active_share_links_deleted": 1,
"studio_sections_deleted": 1,
"studio_media_marked_for_deletion": 1,
"files_deleted": 1,
"ai_designed_voices_deleted": 1,
"ai_designed_voices_retained": 1
},
"request_id": "req_01J9Z3K8QF4"
}