Appearance
Studio
Long-form projects: audiobooks and podcasts.
Audiobooks: a manuscript of chapters is narrated chapter by chapter in the background. Each finished chapter is a separate MP3 you can play while the rest of the book records.
Podcasts: a document or pasted text becomes a two-host conversation. Cinara writes the script first and waits for you to read and edit it; only recording is charged. See Podcasts.
Each read of the job records the next few parts, so the audiobook only moves forward while you poll.
Endpoints:
GET /v1/studio/optionsPOST /v1/studio/audiobooksGET /v1/studio/audiobooks/{id}GET /v1/studio/audiobooks/{id}/files/{name}POST /v1/studio/podcastsGET /v1/studio/podcasts/{id}POST /v1/studio/podcasts/{id}/scriptPOST /v1/studio/podcasts/{id}/recordGET /v1/studio/podcasts/{id}/files/{name}
Get studio options
GET /v1/studio/options
Response
200 OK
json
{
"limits": {
"characters": 500000,
"chapters": 200,
"chapterCharacters": 30000,
"paragraphCharacters": 20000,
"minSpeed": 0.8,
"maxSpeed": 1.2
},
"models": [
{ "id": "speech", "label": "Cinara Speech", "creditsPerCharacter": 1 },
{ "id": "speech-hd", "label": "Cinara Speech HD", "creditsPerCharacter": 2 }
],
"languages": ["auto", "English", "Hindi", "Spanish"],
"podcast": {
"limits": {
"minMinutes": 3,
"maxMinutes": 30,
"maxSourceCharacters": 200000,
"writerSourceCharacters": 40000,
"maxLines": 400,
"lineCharacters": 800,
"hostNameMax": 40,
"notesMax": 600,
"minSourceCharacters": 200
},
"credits": { "script": 100, "perCharacter": { "speech": 1, "speech-hd": 2 }, "musicPerTrack": 300, "musicTracks": 2 },
"defaults": { "minutes": 10, "model": "speech", "language": "auto", "music": true, "hosts": ["Maya", "Jake"] }
}
}languages is the same list as the speech options (shortened above). limits and models describe audiobooks; podcast holds the podcast limits, credit rates and defaults.
Credits
Free.
Example
bash
curl https://api.cinara.ai/v1/studio/options \
-H "Authorization: Bearer $CINARA_API_KEY"Create an audiobook
POST /v1/studio/audiobooks
Starts narrating a manuscript.
Needs a verified phone.
The workspace's pronunciation rules are applied.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
chapters | object[] | yes | The book, in order. Chapters with no text are skipped. |
chapters[].title | string | no | Up to 200 characters. Default Chapter <n>. |
chapters[].paragraphs | string[] | yes | The chapter's paragraphs, each up to 20,000 characters. Line breaks and runs of spaces inside a paragraph become single spaces. Blank paragraphs are dropped. |
voiceId | string | yes | The narrator: a library voice id, or a workspace voice's providerVoiceId. |
model | string | no | speech (default) or speech-hd. |
speed | number | no | 0.8–1.2. Default 1. |
language | string | no | A language from the speech options. Anything else is auto. |
readTitles | boolean | no | Read each chapter's title before it. Default true. |
title | string | no | The book's title, up to 200 characters. Default Untitled audiobook. |
author | string | no | Up to 120 characters. |
Limits:
- Chapters over 30,000 characters are split between paragraphs into
Title (part 2),Title (part 3)and so on. - Up to 200 chapters after splitting.
- Up to 500,000 spoken characters. A read title counts its length plus 1.
Response
201 Created
json
{
"generation": {
"id": "e9c3a7f1-4d8b-4b2e-a5c6-7f0d3b9e1a84",
"module": "studio",
"status": "running",
"input": {
"title": "The Lighthouse Keeper",
"author": "R. Ellison",
"fileName": "the-lighthouse-keeper.mp3",
"voiceId": "hearth",
"voiceName": "Hearth · Margaret",
"model": "speech",
"speed": 1,
"language": "English",
"readTitles": true,
"characters": 118420
},
"output": {
"phase": "speak",
"stage": "processing",
"stageLabel": "Recording chapter 1 of 12",
"chapters": [
{ "title": "The Harbour", "characters": 9812, "parts": 3, "doneParts": [] },
{ "title": "The Storm", "characters": 10240, "parts": 3, "doneParts": [] }
],
"partsTotal": 34,
"partsDone": 0
},
"credits": 118420,
"error": null,
"created_at": "2026-09-15T21:30:12.445019+00:00",
"completed_at": null
},
"credits": 81580
}output.chapters is shortened above. Each chapter lists its spoken characters, how many parts it's recorded in, and which parts are done.
Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Choose a voice model |
| 400 | invalid_request | Choose a narrator voice |
| 400 | invalid_request | Choose a speed from 0.8× to 1.2× |
| 400 | invalid_request | Keep each paragraph under 20,000 characters |
| 400 | invalid_request | Add the book's text |
| 400 | invalid_request | An audiobook can have up to 200 chapters |
| 400 | invalid_request | An audiobook can have up to 500,000 characters; this one has 512,044 |
| 402 | insufficient_credits | This needs 118,420 credits |
| 403 | phone_unverified | Verify your phone number to start creating. |
Credits
max(1, ceil(spoken characters × rate)), with 1 credit per character for speech and 2 for speech-hd. Charged in full when the audiobook starts and not adjusted afterwards. Refunded in full if the audiobook fails (after 4 failed attempts in a row).
Example
bash
curl -X POST https://api.cinara.ai/v1/studio/audiobooks \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "The Lighthouse Keeper",
"author": "R. Ellison",
"voiceId": "hearth",
"language": "English",
"chapters": [
{"title": "The Harbour", "paragraphs": ["The fog came in before the boats did.", "Nobody in the village remembered a summer like it."]},
{"title": "The Storm", "paragraphs": ["By midnight the wind had found every loose shutter on the hill."]}
]
}'Get an audiobook
GET /v1/studio/audiobooks/{id}
Reads an audiobook, records the next parts, and returns it with every finished chapter. Keep polling until status isn't running.
Response
200 OK
json
{
"generation": {
"id": "e9c3a7f1-4d8b-4b2e-a5c6-7f0d3b9e1a84",
"module": "studio",
"status": "running",
"input": { "title": "The Lighthouse Keeper", "voiceId": "hearth", "voiceName": "Hearth · Margaret", "characters": 118420, "…": "…" },
"output": {
"phase": "speak",
"stage": "processing",
"stageLabel": "Recording chapter 2 of 12",
"chapters": [
{
"title": "The Harbour",
"characters": 9812,
"parts": 3,
"doneParts": [0, 1, 2],
"size": 11203584,
"seconds": 700.2
},
{ "title": "The Storm", "characters": 10240, "parts": 3, "doneParts": [0] }
],
"partsTotal": 34,
"partsDone": 4
},
"credits": 118420,
"error": null,
"created_at": "2026-09-15T21:30:12.445019+00:00",
"completed_at": null
},
"files": [
{
"name": "chapter-1",
"label": "1. The Harbour",
"contentType": "audio/mpeg",
"size": 11203584,
"url": "https://api.cinara.ai/media/1789552290/Rt5xE…/workspaces/…/studio/…/chapter-001.mp3"
}
]
}| Field | Description |
|---|---|
output.partsDone / partsTotal | Overall progress. |
output.chapters[].seconds | A finished chapter's length. |
output.seconds | Once finished: the whole book's length. |
files | Every finished chapter, named chapter-<n> and labelled <n>. <title>, even while the rest records. Empty if the audiobook failed. |
A finished audiobook has phase: "done" and stageLabel: "Done". A failed one has error: "Audiobook failed."
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Not found |
Credits
Free to read.
Polling example
bash
while :; do
RES=$(curl -s "https://api.cinara.ai/v1/studio/audiobooks/$ID" -H "Authorization: Bearer $CINARA_API_KEY")
STATUS=$(echo "$RES" | jq -r .generation.status)
[ "$STATUS" != "running" ] && break
echo "$(echo "$RES" | jq -r '"\(.generation.output.partsDone) of \(.generation.output.partsTotal) parts"')"
sleep 3
done
echo "$RES" | jq -r '.files[] | "\(.name) \(.url)"'Download a chapter
GET /v1/studio/audiobooks/{id}/files/{name}
Downloads one chapter (chapter-1, chapter-2, …) as an attachment.
A chapter can be downloaded as soon as it's finished, while the rest of the book is still recording.
Response
200 OK with the MP3 and Content-Disposition: attachment; filename="the-lighthouse-keeper-chapter-1.mp3".
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Chapter not ready |
Credits
Free.
Example
bash
curl -L "https://api.cinara.ai/v1/studio/audiobooks/e9c3a7f1-4d8b-4b2e-a5c6-7f0d3b9e1a84/files/chapter-1" \
-H "Authorization: Bearer $CINARA_API_KEY" -o chapter-01.mp3Podcasts
A podcast is a two-host conversation about a document or pasted text. It runs in two steps, so a script you don't want costs almost nothing:
- Write.
POST /v1/studio/podcastsholds 100 credits and writes the script. - Review. The job waits in
review. Read the script, and edit it withPOST /v1/studio/podcasts/{id}/scriptas often as you like, for free. - Record.
POST /v1/studio/podcasts/{id}/recordreserves the speech and music credits and starts recording.
Each read of the job records the next few lines, exactly like audiobooks, so an episode only moves forward while you poll.
generation.output.phase says where it is: writing, review, recording or done.
Create a podcast
POST /v1/studio/podcasts
Writes the script. Nothing is recorded yet.
Needs a verified phone.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
source | object | yes | The text to talk about: {"kind": "text", "text": "…"}, or {"kind": "document", "fileName": "…", "chapters": [{"title": "…", "paragraphs": ["…"]}]} as read in your app. Chapters with no text are skipped. |
hosts | object[] | yes | Exactly two hosts, in order. Host A opens the episode. |
hosts[].name | string | yes | 1–40 characters. The two names must differ. |
hosts[].voiceId | string | yes | A library voice id, or a workspace voice's providerVoiceId. The two voices must differ. |
minutes | number | yes | The episode's target length, 3–30. |
model | string | no | speech (default) or speech-hd. |
language | string | no | A language from the speech options. Anything else is auto, which writes in English. |
music | boolean | no | Make an intro and an outro. Default true. |
notes | string | no | What the hosts should focus on. Up to 600 characters. |
title | string | no | Up to 200 characters. Cinara writes one when it's left out. |
Limits:
- The source is 200 to 200,000 characters. The writer is given the first 40,000.
- The script is up to 400 lines of up to 800 characters.
Response
201 Created
json
{
"generation": {
"id": "b7e2c9a4-1f6d-4e3b-a8c5-0d2f7b9e4a13",
"module": "podcast",
"status": "running",
"input": {
"title": null,
"fileName": "podcast.mp3",
"source": { "kind": "text", "fileName": null, "characters": 1211 },
"hosts": [
{ "name": "Maya", "voiceId": "rise", "voiceName": "Rise · Maya" },
{ "name": "Jake", "voiceId": "breeze", "voiceName": "Breeze · Jake" }
],
"minutes": 3,
"model": "speech",
"language": "auto",
"music": true,
"notes": null
},
"output": { "phase": "writing", "stage": "queued", "step": 1, "stageLabel": "Writing the script" },
"credits": 100,
"error": null,
"created_at": "2026-09-15T22:10:04.118320+00:00",
"completed_at": null
},
"credits": 39900
}Errors
| Status | error | Message |
|---|---|---|
| 400 | invalid_request | Choose a voice model |
| 400 | invalid_request | Choose two hosts |
| 400 | invalid_request | Name host 1 |
| 400 | invalid_request | Keep each host's name under 40 characters |
| 400 | invalid_request | Choose a voice for Maya |
| 400 | invalid_request | Give the two hosts different names |
| 400 | invalid_request | Give each host a different voice |
| 400 | invalid_request | Add the text to talk about |
| 400 | invalid_request | That document has no text to talk about |
| 400 | invalid_request | Add the text to talk about (at least 200 characters) |
| 400 | invalid_request | Keep the source under 200,000 characters; this one has 240,118 |
| 400 | invalid_request | Choose a length from 3 to 30 minutes |
| 400 | invalid_request | Keep the notes under 600 characters |
| 402 | insufficient_credits | Writing the script needs 100 credits |
| 403 | phone_unverified | Verify your phone number to start creating. |
| 502 | generation_failed | The script couldn't start. Your credits were refunded. |
Credits
From 100 credits: 100 are charged when the script starts, and a script whose writing costs more is charged the difference when it's written. Refunded if the script can't be written.
Example
bash
curl -X POST https://api.cinara.ai/v1/studio/podcasts \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {"kind": "text", "text": "Cinara turns a document into a two-host episode…"},
"hosts": [{"name": "Maya", "voiceId": "rise"}, {"name": "Jake", "voiceId": "breeze"}],
"minutes": 3
}'Get a podcast
GET /v1/studio/podcasts/{id}
Reads the episode, does the next piece of work, and returns it with its files. Keep polling until status isn't running.
Response
200 OK
json
{
"generation": {
"id": "b7e2c9a4-1f6d-4e3b-a8c5-0d2f7b9e4a13",
"module": "podcast",
"status": "running",
"input": { "minutes": 3, "model": "speech", "music": true, "…": "…" },
"output": {
"phase": "review",
"stageLabel": "Ready to review",
"script": {
"title": "What Cinara does with a document",
"description": "Maya and Jake walk through how a document becomes an episode.",
"lines": [
{ "speaker": "A", "text": "So you hand Cinara a document, and it hands you back a conversation?" },
{ "speaker": "B", "text": "That's the idea. It writes the script first, and you get to change it." }
]
},
"estimate": { "characters": 3980, "minutes": 5.5, "speechCredits": 3980, "musicCredits": 600 },
"progress": { "linesDone": 0, "linesTotal": 44 }
},
"credits": 100,
"error": null,
"created_at": "2026-09-15T22:10:04.118320+00:00",
"completed_at": null
},
"files": []
}| Field | Description |
|---|---|
output.phase | writing, review, recording or done. |
output.script | The episode's title, a one-sentence description, and the lines, each with a speaker (A or B) and its text. |
output.estimate | What recording the current script would cost: its characters, the episode's estimated minutes, speechCredits and musicCredits. |
output.progress | linesDone of linesTotal while recording. |
output.segments | Once the lines are joined: each line's speaker, text, start and end in seconds. |
output.seconds | The finished episode's length. |
files | episode, voices (Hosts only), intro, outro and transcript, as each is made. Empty if the episode failed. |
Without music, the hosts' track is the episode, so episode and voices are the same audio.
Failures: The script couldn't be written., Podcast took too long and was stopped., Podcast failed. Credits are refunded.
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Not found |
Credits
Free to read.
Polling example
bash
while :; do
RES=$(curl -s "https://api.cinara.ai/v1/studio/podcasts/$ID" -H "Authorization: Bearer $CINARA_API_KEY")
PHASE=$(echo "$RES" | jq -r .generation.output.phase)
[ "$(echo "$RES" | jq -r .generation.status)" != "running" ] && break
[ "$PHASE" = "review" ] && echo "Script ready to review" && break
echo "$(echo "$RES" | jq -r .generation.output.stageLabel)…"
sleep 5
doneEdit the script
POST /v1/studio/podcasts/{id}/script
Replaces the script's lines while the episode waits in review. Free, and you can do it as often as you like.
Needs a verified phone.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
lines | object[] | yes | The whole script, in order. Up to 400 lines. |
lines[].speaker | string | yes | A or B. Both hosts need at least one line. |
lines[].text | string | yes | What they say. 1–800 characters. |
title | string | no | A new episode title, up to 120 characters. |
Response
200 OK with the updated generation. output.estimate and output.progress.linesTotal follow the new script.
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Not found |
| 409 | invalid_request | The script is still being written |
| 409 | invalid_request | This script can only be edited while it waits for review |
| 400 | invalid_request | Add some lines |
| 400 | invalid_request | An episode can have up to 400 lines |
| 400 | invalid_request | Line 3 needs a host: A or B |
| 400 | invalid_request | Line 3 is empty |
| 400 | invalid_request | Keep each line under 800 characters; line 3 is longer |
| 400 | invalid_request | Both hosts need something to say |
| 403 | phone_unverified | Verify your phone number to start creating. |
Credits
Free.
Example
bash
curl -X POST "https://api.cinara.ai/v1/studio/podcasts/$ID/script" \
-H "Authorization: Bearer $CINARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"lines": [{"speaker": "A", "text": "Let me start with the part that surprised me."}, {"speaker": "B", "text": "Go on."}]}'Record a podcast
POST /v1/studio/podcasts/{id}/record
Reserves the speech and music credits and starts recording the script as it stands.
Needs a verified phone.
The workspace's pronunciation rules are applied to every line.
Response
200 OK
json
{
"generation": {
"id": "b7e2c9a4-1f6d-4e3b-a8c5-0d2f7b9e4a13",
"module": "podcast",
"status": "running",
"output": {
"phase": "recording",
"stage": "processing",
"stageLabel": "Recording line 1 of 44",
"estimate": { "characters": 3980, "minutes": 5.5, "speechCredits": 3980, "musicCredits": 600 },
"progress": { "linesDone": 0, "linesTotal": 44 },
"…": "…"
},
"credits": 4680,
"error": null,
"created_at": "2026-09-15T22:10:04.118320+00:00",
"completed_at": null
},
"credits": 35320
}generation.credits is now the script's credits plus the recording.
Then keep reading GET /v1/studio/podcasts/{id}: each read records the next few lines, joins them, makes the music and puts the episode together.
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | Not found |
| 409 | invalid_request | The script is still being written; read it and edit it before recording |
| 409 | invalid_request | This episode has already been recorded |
| 402 | insufficient_credits | Recording this episode needs 4,580 credits; the workspace has 1,200. |
| 403 | phone_unverified | Verify your phone number to start creating. |
Credits
Speech credits for the script, plus the music:
| Part | Credits |
|---|---|
speech | 1 per character of the script |
speech-hd | 2 per character |
| Music | 300 per track, 2 tracks, so 600 with music on |
output.estimate gives the same numbers before you commit. Refunded if the episode fails.
Example
bash
curl -X POST "https://api.cinara.ai/v1/studio/podcasts/$ID/record" \
-H "Authorization: Bearer $CINARA_API_KEY"Download a podcast file
GET /v1/studio/podcasts/{id}/files/{name}
Downloads one file as an attachment: episode, voices, intro, outro or transcript. Each is available as soon as it's made, while the rest of the episode is still being put together.
Response
200 OK with the file and Content-Disposition: attachment. The transcript is text/plain; the rest are MP3.
Errors
| Status | error | Message |
|---|---|---|
| 404 | not_found | File not ready |
Credits
Free.
Example
bash
curl -L "https://api.cinara.ai/v1/studio/podcasts/$ID/files/episode" \
-H "Authorization: Bearer $CINARA_API_KEY" -o episode.mp3