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.
Response 200
Named projects and unassigned Studio story summaries. Fields below are inside data.
projectsarray of objectrequired3 item attributes
idstringrequireduuidnamestringrequiredproject_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.
sectionobjectrequired8 child attributes
idstringrequireduuidproject_idstringrequireduuidtitlestringrequireddocument_versionintegerrequired≥ 1source_text_lengthintegerrequired≥ 0Length of the section's exact source-text projection in UTF-16 code units.
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": [
{
"id": "8f14e45f-ceea-4e7a-9c2d-3b1a5f2e7c10",
"name": "My integration",
"project_kind": "named"
}
],
"unassigned_sections": [
{
"project_id": "c9f0f895-fb98-4b91-9e3f-1d2a6b7c8d20",
"section": {
"id": "45c48cce-2e2d-4fbd-8a1c-5e6f7a8b9c30",
"project_id": "d3d94468-02a4-4c5e-b6f7-0a1b2c3d4e40",
"title": "Chapter One",
"document_version": 1,
"source_text_length": 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.
projectobjectrequired3 child attributes
idstringrequireduuidnamestringrequiredproject_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": {
"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 counts of the sections, script blocks, generated audio, and active share links that deleting the project would remove. Call it before deleting.
Path parameters
projectIdstringrequireduuid
Response 200
Project deletion preview. Fields below are inside data.
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
Errors 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": "req_01J9Z3K8QF4"
}Delete a project
DELETE/studio/projects/{projectId}
studio:writeDeletes a project with all of its sections, in-progress work, playback, and share links. This cannot be undone through the API.
Path parameters
projectIdstringrequireduuid
Request body application/json
schema_version1requiredexpected_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_voicesbooleanrequired
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": 1,
"expected_project_kind": "standalone",
"delete_ai_designed_voices": true
}'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": 1,
"expected_project_kind": "standalone",
"delete_ai_designed_voices": true
}),
});
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": 1,
"expected_project_kind": "standalone",
"delete_ai_designed_voices": True
},
)
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"
}