Skip to main content

Overview

The Audiobook API exposes the complete production pipeline end to end, so you can drop AI audiobook generation directly into your own platform or control panel: a file goes in, a finished, ACX-compliant package comes back, with no human in the loop. It is the same API surface our own Audiobook Studio runs on — nothing here is a private endpoint.
Every endpoint is API-key authenticated and asynchronous: submit a job, get an ID back immediately, and receive a signed webhook when it finishes — so your control panel never blocks on a long render.

Key features

  • Inputs: upload PDF, EPUB, DOCX, or TXT; paste raw text; or generate a book from a prompt. Chapters are detected automatically.
  • Voices: 100+ languages with standard narration voices; custom voice cloning across 20+ languages.
  • ACX-compliant export: per-chapter MP3 at 192 kbps CBR / 44.1 kHz, a retail sample, optional narrated opening/closing credits, a cover slot, and a metadata.json manifest — packaged as a single ZIP via a presigned URL.
  • Automatic compliance check: file format, audio levels, silence, and per-chapter length are validated on export; a non-compliant book is rejected with the exact reason rather than shipped silently.
  • Asynchronous + webhooks: job.completed / job.failed / job.cancelled callbacks for backlog-scale automation.
  • White-label output: delivered files carry the publisher’s own title/author/narrator metadata — nothing points back at AudioPod.
  • Prepaid usage pricing: 0.04perminuteofoutput( 0.04 per minute of output (~24 for a 10-hour book). See API Wallet.

Authentication

All endpoints require authentication. Use an API key (recommended for server-to-server integrations):
  • API Key (Recommended): X-API-Key: your_api_key header — create one in the dashboard.
  • JWT Token: Authorization: Bearer your_jwt_token (session-based auth).

Getting the IDs

Almost every call below is addressed by an ID, and how you obtain them is the first thing to get right.
Every audiobook identifier is a UUID string, and in every response body the field is called id — not project_id or chapter_id. Those names appear only as path parameters (/projects/{project_id}/chapters/{chapter_id}) and as back-references on child objects (a chapter carries project_id; a paragraph carries both chapter_id and project_id). So you read id from the object you just created, and you write it into the next request’s path.
GET /audiobook/projects/{project_id} does not return chapters by default — the project body carries total_chapters (a count) and chapters: null. To get the chapter objects (and therefore their ids) you must either pass ?include_chapters=true on the project read, or call GET /audiobook/projects/{project_id}/chapters separately.
The full call chain, with the ID each step produces: Chapters are the unit you organise by; paragraphs are the unit that is actually narrated, billed, and re-recorded. If you only ever need whole-book narration you can stop at step 5 — POST .../narration/batch with no chapter_ids or paragraph_ids narrates the entire project.

API Endpoints Quick Reference

POST /audiobook/projects/{project_id}/narration/start is removed and now returns 410 Gone. It predates per-paragraph billing and bypassed metering entirely. Use POST /audiobook/projects/{project_id}/narration/batch — with no chapter_ids and no paragraph_ids it narrates the whole project, which is exactly what narration/start used to do.

Quick Start (cURL)

1. Create a project

A project is the container for a single audiobook. Create it first, then attach a manuscript.
string
required
Audiobook title.
string
Author name (written into the package metadata).
string
Project description.
string
ISBN, if available.
string
Book genre.
integer
Default narration voice for the project (can also be set per narration request).
number
default:"1.0"
Speech speed multiplier.
string
default:"narrative"
Narration style.
string
default:"acx"
Export format. acx produces an Audible/ACX-ready package.
string
default:"audible"
Target distribution platform.
Response (AudiobookProjectResponse):
string
Project UUID — this is your project_id for every subsequent call.
string
Lifecycle state (see Status values).
integer
Number of chapters once parsed. This is a count — the chapter objects are not in this response unless you ask for them.
number
Estimated finished runtime.
string
Uploaded filename, once attached.
string
pdf · epub · docx · txt.
integer
Total words after parsing.
string
ISO timestamp set when a manuscript parse finished successfully; null until then.
string
ISO timestamp of the first narration job.
string
ISO timestamp when narration finished.
string
ISO timestamp when production/export finished.
string
Sanitized failure reason when a stage failed; null otherwise.
integer
Project default narration voice.
number
Speed multiplier (0.5–2.0).
string
Delivery style.
string
Export format (acx).
string
Target distribution platform.
string
Creation timestamp.
string
Last-modified timestamp.

Read one project

GET /audiobook/projects/{project_id} returns the same body, plus a chapters field:
boolean
default:"false"
When true, the response carries the project’s full chapters array inline, saving a second round-trip. When omitted or false, chapters is null — the chapter objects (and their ids) are simply not there.

List projects

GET /audiobook/projects is paginated with page and per_page (not skip/limit):
integer
default:"1"
1-based page number (minimum 1).
integer
default:"20"
Projects per page, 1100.
string
Return only projects in this status.
Case-insensitive substring match on title or author.
array
The page of projects (each an AudiobookProjectResponse).
integer
Total projects matching the filters, across all pages.
integer
Echo of the requested page.
integer
Echo of the requested page size.
boolean
Whether another page follows.
boolean
Whether a previous page exists.
Unknown query parameters are ignored, not rejected. Sending ?skip=0&limit=20 returns a 200 with the first page every time — so a paging loop built on skip/limit never advances and silently re-reads page 1 forever. Use page / per_page, and drive your loop off has_next.

2. Add the manuscript

Three ways to get text into a project — pick one.

Upload a file

POST /audiobook/projects/{project_id}/manuscript/upload — multipart form field file (PDF, EPUB, DOCX, or TXT). Returns a file_key to hand to the parser.
string
Storage key of the uploaded manuscript — pass this to the parser.
string
Reference URL for the stored file.
integer
Maximum accepted size in bytes.

Parse the uploaded file into chapters

POST /audiobook/projects/{project_id}/manuscript/parse — pass file_key (from the upload response) and filename. The endpoint accepts a JSON body or form fields (multipart/form-data or application/x-www-form-urlencoded) — use whichever your HTTP client makes easiest. Parsing is asynchronous and returns a job_id immediately — see Poll for parse completion for how to wait on it.

Poll for parse completion

There is no parse-status endpoint, and polling GET .../chapters “until chapters appear” is not a safe wait — a failed parse produces no chapters, so that loop never terminates. Poll the project instead: it is the object that carries the parse outcome.
Poll GET /audiobook/projects/{project_id}?include_chapters=true — one call returns both the parse state and, on success, the chapters — and branch on these three fields:
string
parsing while the job is running. On success it becomes ready_for_narration. On a failed first parse it becomes failed.
string | null
null until a parse has completed successfully; an ISO timestamp afterwards. This is the success signal.
string | null
The sanitized failure reason, set when the parse fails (e.g. an unreadable or over-length manuscript). Read it to report why to your user.
Decision table — evaluate in this order:
Check status before error_message. error_message is not cleared when you retry a parse, so a leftover value from an earlier failure can persist on a project that has since parsed fine. status (plus parsing_completed_at) is the authoritative state; error_message is the reason to display once status says failed.One nuance for manuscript/append: if an append-parse fails on a project that already has chapters, the project is returned to ready_for_narration rather than failed, so the existing book is not destroyed. In that case compare total_chapters before and after to detect that nothing was added.
Parses typically finish in under two minutes for EPUB/DOCX/TXT and in 2–10 minutes for large or scanned PDFs; the estimated_completion_time string in the parse response reflects the format you submitted. A 15-minute poll ceiling is a safe default.

Paste text directly

POST /audiobook/projects/{project_id}/manuscript/paste — JSON. Best for content you already have as text.
string
required
Manuscript or chapter text.
string
default:"paste"
Source label.

Generate a book from a prompt

POST /audiobook/projects/{project_id}/manuscript/generate — JSON. Drafts an original manuscript, then chapters it.
string
required
What the book should be about.
string
Optional working title.
string
Intended audience.
string
default:"warm"
Writing tone.
integer
default:"3"
Number of chapters.
integer
default:"600"
Target words per chapter.
The paste and generate endpoints return a ManuscriptIntakeResponse with total_chapters, total_words, and estimated_duration_minutes.
manuscript/parse, manuscript/paste, and manuscript/generate replace the project’s entire chapter/paragraph structure — any existing narration is discarded. Parse once per manuscript; re-parse only if the manuscript file itself changed.While narration, another parse, mixing, or an export is already running for the project, these endpoints return 409 Conflict — poll GET .../narration/progress and retry once it finishes. If you don’t want to wait for a narration run to finish, stop it first with POST .../narration/cancel; already-narrated paragraphs are kept.If the project already has completed narration, these endpoints instead return 409 with a structured body: detail.code = "completed_narration_exists" and detail.completed_paragraphs = N. To proceed anyway, pass force=true (accepted as a JSON field, form field, or query parameter). Do not hard-code force=true in your integration — treat this 409 as a stop-and-confirm; forcing permanently deletes the N narrated paragraphs.manuscript/append is the exception: it’s additive (appends chapters after the existing ones) and never deletes narration.

3. Chapters

List chapters with GET /audiobook/projects/{project_id}/chapters, or fetch one with GET .../chapters/{chapter_id}.
integer
default:"1"
1-based page number (minimum 1).
integer
default:"50"
Chapters per page, 1500.
The response is the same paginated envelope as the project list — chapters, total, page, per_page, has_next, has_prev — with chapters ordered by chapter_number. As with projects, skip/limit are not recognised and are silently ignored.
Chapter (AudiobookChapterResponse):
string
Chapter UUID — this is your chapter_id.
string
Back-reference to the owning project.
integer
Order in the book.
string
Chapter title.
string
Chapter text.
integer
Words in the chapter.
integer
Characters in the chapter.
number
Narrated length, once generated.
string
pending · queued · narrating · completed · failed.
string
Stored audio key, once narrated.
string
Reason a chapter failed (e.g. content too short for narration).
boolean
If true, the chapter is excluded from narration and export.
integer
Per-chapter voice override.
string
Free-text narration instructions stored on the chapter.
number
Estimated narrated length before rendering.
integer
Number of narration retries so far.
You can edit a chapter (title, content, skip_narration, per-chapter voice_id, narration notes) with PUT .../chapters/{chapter_id} before narrating.

3b. Paragraphs

Chapters are containers; paragraphs are the atomic unit of narration. Each paragraph is narrated, billed, re-recorded, and version-tracked (as “takes”) independently, so paragraph-level access is what you want for fine-grained control — fixing one mispronounced sentence without re-rendering a 40-minute chapter.

List a chapter’s paragraphs

GET /audiobook/projects/{project_id}/chapters/{chapter_id}/paragraphs — returned in position order.
integer
1-based page number. Optional.
integer
Paragraphs per page, 1200. Optional; defaults to 50 when only page is given.
Pagination here is opt-in: omit both page and per_page and you get every paragraph in the chapter in a single response. Supply either one and paging switches on. total is always the chapter’s full paragraph count, never the length of the page you received.
Response (PaginatedParagraphsResponse):
array
The paragraphs, ordered by position.
integer
Total paragraphs in the chapter.
integer | null
Echo of the requested page; null in unpaginated mode.
integer | null
Echo of the requested page size; null in unpaginated mode.
boolean
Whether another page follows (false in unpaginated mode).
boolean
Whether a previous page exists (false in unpaginated mode).
Paragraph (AudiobookParagraphResponse):
string
Paragraph UUID — this is your paragraph_id.
string
Back-reference to the owning chapter.
string
Back-reference to the owning project.
integer
Order within the chapter (the sort key).
string
The text that will be narrated.
integer
Words in the paragraph.
integer
Characters in the paragraph (narration is metered per character).
integer
Per-paragraph voice override, if set.
number
Per-paragraph speed override.
string
Per-paragraph delivery style.
string
Emotional direction for this paragraph.
string
Speaking character, when the book is cast.
string
Screenplay-style direction applied to this line.
integer
Silence appended after this paragraph, in milliseconds.
string
Stored audio key, once narrated.
number
Narrated length, once generated.
string
pending · queued · narrating · completed · failed.
boolean
A locked paragraph is protected: edits and regeneration are refused with 409 until it is unlocked.
boolean
If true, the paragraph is excluded from narration and export.
string
Sanitized reason this paragraph failed.
integer
Narration retries so far.
Read one paragraph with GET .../projects/{project_id}/paragraphs/{paragraph_id} (handy for status polling) and edit one with PUT .../projects/{project_id}/paragraphs/{paragraph_id}.

Narrate a single paragraph

POST /audiobook/projects/{project_id}/paragraphs/{paragraph_id}/narrate generates — or regenerates — audio for exactly one paragraph and records a new take. The body is optional; send {} to narrate with the paragraph’s existing settings.
integer
Voice for this take; falls back to the paragraph’s, then the project’s, voice.
string
Narrate this text instead of the stored paragraph text (the stored text is unchanged).
number
Speed multiplier for this take, 0.52.0.
string
Delivery style for this take.
string
Emotional direction for this take.
string
Speaking character (max 120 chars).
string
Screenplay-style direction (max 500 chars); takes precedence over narration_style for this take.
Response:
string
Background task identifier for this narration.
string
The paragraph being narrated.
string
queued — the paragraph’s status is set to queued immediately.
object
is_free_regen, estimated_credits, estimated_chars, estimated_seconds, take_number_after.
Poll GET .../projects/{project_id}/paragraphs/{paragraph_id} until status is completed (or failed, with error_message). The first take of a paragraph is billed, the first regeneration is free, and subsequent regenerations are billed again — billing.is_free_regen tells you which case you are in before the work runs. A 409 means the paragraph is locked; unlock it with PUT .../paragraphs/{paragraph_id} first.

4. Choose a voice

GET /audiobook/voices/available returns a VoiceSelectionResponse:
array
All standard voices you can narrate with (each has an integer id).
A curated shortlist.
array
Your own cloned voices (see Voice Management).
GET /audiobook/voices/languages lists every supported narration locale (100+). Before committing, GET /audiobook/projects/{project_id}/cost-estimate returns a per-paragraph breakdown with word_count, estimated_seconds, and credits_to_charge.

5. Narrate

Narrate the book — narration/batch

POST /audiobook/projects/{project_id}/narration/batch is the narration endpoint. It queues one job that narrates the selected paragraphs in order. Scope is chosen by what you send:
  • omit both chapter_ids and paragraph_ids → narrate the entire project;
  • chapter_ids → narrate only those chapters;
  • paragraph_ids → narrate only those paragraphs.
integer
Voice to narrate with (from voices/available). Optional — omit to use each paragraph’s/chapter’s assigned voice, falling back to the project’s selected_voice_id.
array
Optional — UUIDs of chapters to narrate.
array
Optional — UUIDs of individual paragraphs to narrate.
boolean
default:"false"
Re-narrate paragraphs that already have finished audio.
boolean
default:"false"
Include locked paragraphs.
boolean
default:"false"
Include paragraphs marked skip_narration.
object
default:"{}"
Free-form per-run narration settings.
Response (BatchNarrationResponse):
integer
The narration job.
integer
Paragraphs queued for this run.
integer
Paragraphs excluded by the include_* rules above (already completed, locked, or skipped).
string
Human-readable estimate.
Common non-200s: 402 insufficient balance (nothing is queued or charged), 409 another narration/parse/mix/export is already live for this project, 503 the job could not be queued (nothing charged — retry).

Track progress

GET /audiobook/projects/{project_id}/narration/progress returns a NarrationProgress:
integer
Total chapters.
integer
Finished narrations.
integer
Failed narrations.
integer
Currently rendering.
array
Per-chapter status detail.
Narration is complete when completed_chapters + failed_chapters >= total_chapters. (Or skip polling and wait for the completion webhook.)

Paragraph-level & regeneration

For a single line, use POST .../paragraphs/{paragraph_id}/narrate. To re-render a whole chapter after an edit, POST .../projects/{project_id}/chapters/{chapter_id}/regenerate (optional ?voice_id= query parameter). To re-queue only the paragraphs of a chapter whose status is failed, POST .../projects/{project_id}/chapters/{chapter_id}/retry-failed-paragraphs.

Cancel a narration run

POST /audiobook/projects/{project_id}/narration/cancel stops a narration run that is still in progress. No request body is required. Cancelling is resume-friendly, not destructive:
  • Paragraphs that already finished keep their audio — nothing you have paid for is discarded.
  • Paragraphs that had not produced audio yet are returned to a re-narratable state, and the charges held for them are released.
  • To carry on later, call POST .../narration/batch again: it skips the paragraphs that are already done and picks up only what is left.
If no narration is running, the endpoint returns 404 with detail.error_code = "no_narration_running". That is also what a repeat cancel returns — so a client that retries after a network timeout can treat this as confirmation that the earlier cancel succeeded, not as an error. If you have a webhook endpoint registered, cancelling emits a job.cancelled event, so an integration waiting on a narration job learns it has stopped instead of polling until it times out.
Existing webhook endpoints only receive the events they were registered with. If yours was created before job.cancelled existed, re-register it (or add the event) to receive cancellations.

Regenerate a completed chapter

By default, POST .../narration/batch skips paragraphs that already have completed narration — that’s the "No eligible paragraphs to narrate" response. To re-narrate them, pass "include_completed": true:
Alternatively, POST .../projects/{project_id}/chapters/{chapter_id}/regenerate re-renders a single chapter in one call.

6. Production extras (optional)

Add intro/outro music, narrated opening/closing credits, or a cover before export:
  • GET·PUT /audiobook/projects/{project_id}/production-settingsProductionMusicSettings (intro, outro, credits, cover, silence). This is where the spoken title/author announcement is configured. Set credits.narrator_name (plus include_opening / include_closing, both default true) and the export produces narrated Chapter_00_*_Credits.mp3 (title, subtitle, author, narrator) and Chapter_99_*_Credits.mp3 as separate files in the ACX package — the opening announcement Audible/ACX expect. No credits files are produced unless this is set.
    Previously named /music-settings. That path still works and will keep working, but it only ever described 2 of the 4 fields — use production-settings.
  • POST /audiobook/projects/{project_id}/media/upload — upload a cover image or custom music track to reference from the settings above.

Chapter silence (room tone)

Every exported chapter opens and closes with digital silence — this is why an exported MP3 has leading and trailing silence, and it is a retail requirement, not padding you should trim. The same values apply to the per-chapter download and to each chapter inside the ACX package, so an audition always matches what ships. Defaults are 750 ms head / 3000 ms tail (mid-window for ACX). Override per project with silence:
integer
default:"750"
Leading silence, in milliseconds. Accepted range 0–5000.
integer
default:"3000"
Trailing silence, in milliseconds. Accepted range 0–10000.
boolean
Read-only. true when head_ms is 500–1000 and tail_ms is 1000–5000 — the ACX opening/closing silence requirements.
silence is always returned, populated with the defaults when you have never set it, so you can read the effective values without knowing them. Values outside the ACX range are accepted, not rejected — plenty of deliveries are not Audible, and a house standard of 2 s/2 s is a legitimate choice. Each out-of-range field adds an entry to the response’s warnings array naming the field and the ACX range, and acx_compliant flips to false:
Values outside the accepted range (a negative head_ms, a tail_ms above 10000) are a 422. Send "silence": null to reset both sides to the defaults; omit the field entirely to leave them unchanged.
If you are delivering to ACX/Audible, keep acx_compliant at true. Out-of-range silence is one of the most common causes of a retail QA rejection, and the rejection arrives after upload — not at export time.

7. Export the ACX package

POST /audiobook/projects/{project_id}/export runs an automatic ACX compliance check, then assembles the package.
array
Optional — export only these chapters.
object
Optional — override export settings.
Response (ACXExportResponse):
integer
Export job ID.
object
An ACXComplianceCheck (see below).
integer
Any additional TTS cost incurred by the export (0 when audio already exists).
ACXComplianceCheck:
boolean
Whether the book passes ACX requirements.
array
Blocking problems (export is refused when non-empty).
array
Non-blocking advisories.
boolean
Container/bitrate compliant.
boolean
RMS/peak within ACX bounds.
boolean
Head/tail room present.
boolean
File names follow the ACX convention.
If a book is non-compliant, the call returns the specific issues instead of producing an invalid package:
Poll GET .../export/{job_id}/status until COMPLETED, then fetch the download: GET /audiobook/projects/{project_id}/export/{job_id}/download returns a JSON manifest with a 7-day presigned download_url to the ZIP:

The ACX package

Completion webhooks

Rather than poll, register an endpoint and receive a signed event the moment a job finishes — the recommended pattern for backlog-scale automation.
Every delivery carries these headers: Verify the signature — HMAC-SHA256 over "<timestamp>.<raw-body>" with your endpoint secret as the key:
Delivery is retried with exponential backoff and parked in a dead-letter queue after repeated failures; inspect and redeliver from the delivery log. Endpoints are SSRF-guarded (the resolved IP is re-checked at delivery time; https-only; private/internal ranges blocked).

Status values

string
draftparsingready_for_narrationnarratingready_for_productionmixingready_for_exportexportingcompleted. failed if a stage cannot complete (with the reason in error_message).Note that the state after a successful parse is ready_for_narration, not parsing or completed — pair it with parsing_completed_at when waiting on a parse.
string
pendingqueuednarratingcompleted, or failed with an error_message.
Jobs are prepaid: credits are reserved at job start, settled on success, and released on failure — a failed narration or export never leaves a stranded charge. Narration and export retry automatically; a job.failed webhook fires if a job ultimately can’t complete, so an automated integrator can react deterministically.

Error Handling

The export found blocking issues (e.g. a chapter under the 30-second minimum). The response lists each issue under detail.issues. Fix the flagged chapters (lengthen, merge, or skip_narration) and re-export.
Chapters under ~50 characters can’t be narrated (often a stray title-page fragment from PDF parsing). Mark it skip_narration: true via PUT .../chapters/{id}, or merge it into an adjacent chapter, then continue.
Narration/export draws from your prepaid balance. Top up the API Wallet and retry; reserved credits for a failed job are released automatically.
Parsing, narration, mixing, and export are mutually exclusive per project — each would rewrite structure the other is reading. Poll GET .../narration/progress (or the project’s status) and retry once the live job finishes. A 409 on a single paragraph means it is locked; unlock it via PUT .../paragraphs/{paragraph_id} first.
POST .../narration/start has been removed because it bypassed metering. Send POST .../narration/batch instead — with no chapter_ids and no paragraph_ids it narrates the whole project.
You are probably sending skip/limit. Unknown query parameters are ignored rather than rejected, so the request still returns 200 — with the first page, every time. The project and chapter list endpoints page with page and per_page; loop on has_next.
Send a valid X-API-Key header. Keys are created in the dashboard and can expire — check the key’s status if previously working calls start returning 401.

Pricing

Usage-based on a prepaid wallet — you pay per minute of finished output, with no per-seat fee or monthly minimum. A 10-hour audiobook (~600 minutes) costs roughly **24,versus24**, versus 2,000–$4,000 for a human ACX narrator. For committed monthly volume across many titles, contact us about partner pricing below list. See API Wallet for top-ups and balance.

Next Steps

Voice Management

Create custom narrator voices from a short sample, then narrate with them.

API Wallet

Top up, check your balance, and see per-minute pricing.

Authentication

API keys, scopes, and how to authenticate every request.

Quickstart

The shortest path from zero to a generated audiobook.