Developer Docs

Build with PlayHT

A simple, powerful REST API for text-to-speech, voice cloning, dubbing, and every other PlayHT feature.

⌘K
quickstart.sh
# 1. Submit the job — returns immediately with a task id
curl -s -X POST https://yourapp.com/api/v1/tts \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world!", "voice_id": "..."}'

# 2. Poll until it's done (generation is async, not instant)
curl -s https://yourapp.com/api/v1/tts-job/<job_id> \
  -H "Authorization: Bearer pk_live_..."
# => {"status": "complete", "audio_url": "https://yourapp.com/app/audio/..."}

Get started in 3 steps

1

Get your API key

Sign up for a free account and grab your API key from the Dashboard → API Keys page.

Open API Keys →
2

Submit a job

POST to /api/v1/tts (or any other tool endpoint) with your text and voice ID. It returns a task id immediately — generation runs in the background.

API Reference →
3

Poll for the result

GET /api/v1/tts-job/{jobId} until status is complete. There is no push/webhook notification yet — polling is the only way to know when a job finishes.

Full endpoint list →
Attach an AI assistant

Let Claude (or any MCP client) call PlayHT directly

PlayHT runs an MCP (Model Context Protocol) server — add it as a custom connector in Claude.ai, Claude Desktop, or any other MCP-compatible client, and the assistant can generate speech, list voices, dub audio, and run every other tool below on your behalf, in the conversation, with no separate app to open.

Connector URL
https://playht.co/mcp
Authentication header
Authorization: Bearer pk_live_...
Same API key as the REST API above — one key, both surfaces.
1
Get an API key — From /app/api-keys, if you don't already have one.
2
Add a custom connector — In Claude.ai or Claude Desktop's connector settings, add the URL above and set the Authorization header to your key.
3
Just ask — e.g. "Turn this into speech with a calm narrator voice" — the assistant picks the right tool and calls it for you.
Open API Keys →

Available tools (11)

list_voices
generate_speech
generate_dialogue
change_voice
dub_audio
generate_sound_effect
generate_music
transcribe_audio
isolate_voice
get_task_status
get_tts_job_status

Each tool mirrors an endpoint from the REST API reference below — a job that doesn't finish within a few seconds returns a task id, and the assistant checks back with get_task_status / get_tts_job_status, the same async pattern the REST API itself uses.

API Reference

Every endpoint below is async: a POST returns a task_id or job_id immediately, and you poll a status endpoint until it's done. There's no push/webhook notification yet.

Download the full OpenAPI 3.0 spec (JSON)

Voice Library

List available voices — the public catalog, or your own cloned voices.

Parameters

Name In Type Required Description
source query string no signature (default) · natural · quick · compact · playht · clone. clone returns only your own ready cloned voices, read straight from the database — no vendor call.
provider query string no Deprecated alias for source: elevenlabs · minimax · edge · kokoro · playht · clone.
language query string no Filter by language code, e.g. en.
gender query string no Filter by voice gender.
accent query string no Filter by accent.
age query string no Filter by age category.
use_case query string no Filter by use case (e.g. narration, conversational).
q query string no Free-text search.
page query integer no Page number for pagination.
page_size query integer no Results per page.

Request

curl -s "https://playht.co/api/v1/voices?source=signature&language=en" \
  -H "Authorization: Bearer pk_live_..."

Response

{
  "status": 200,
  "body": {
    "voices": [
      {
        "canonical_id": "abc123",
        "voice_source": "signature",
        "provider_brand": "elevenlabs",
        "name": "Rachel",
        "gender": "female",
        "language": "en",
        "accent": "american",
        "age": "young",
        "use_case": "narration",
        "description": "Calm, professional narration voice.",
        "preview_url": "https://yourapp.com/app/media?sig=...",
        "is_clone": false
      }
    ],
    "pagination": { "page": 1, "page_size": 20, "total": 534 }
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.

Results are cached per filter combination for 1 hour. Preview URLs are always routed through the signed media proxy — a vendor domain never reaches the response. provider_brand is a deprecated alias for voice_source, kept for backward compatibility — new integrations should use voice_source.

Text to Speech

Convert text (or SSML) to speech. Long text is automatically chunked and merged server-side.

Parameters

Name In Type Required Description
text body string yes, unless ssml is given Plain text to speak.
ssml body string yes, unless text is given SSML markup to speak.
voice_id body string no Canonical voice ID from GET /voices.
voice_source body string no signature (default) · natural · quick · compact · playht · clone. Matches the voice_source field GET /voices returned for voice_id.
provider_brand body string no Deprecated alias for voice_source: elevenlabs · minimax · edge · kokoro · playht · clone.
output_format body string no mp3 (default) · wav · ogg · flac.
stability body number no 0–1.
similarity_boost body number no 0–1.
speed body number no 0.5–2.

Request

curl -s -X POST https://playht.co/api/v1/tts \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world!", "voice_id": "abc123", "output_format": "mp3"}'

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_9f8c2a..."
  }
}

// or, when the vendor is temporarily at capacity and queueing is enabled:
// HTTP 202
{
  "status": "queued",
  "job_id": "7f3e1a2b-...",
  "message": "The service is busy. Your job has been queued and will be processed automatically."
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
404 voice_unavailable This voice is temporarily unavailable. We're monitoring it and will restore it automatically.
422 — Laravel validation error — see the specific field rules for this endpoint below.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 vendor_daily_limit Service capacity for today has been reached. Please try again tomorrow.
503 — This tool is currently unavailable. Please try again later.

Text longer than the configured chunk size (default 800 chars) is split, submitted as separate vendor tasks, and merged with ffmpeg once every chunk completes — poll GET /tts-job/{job_id} for those. Short, single-chunk requests return a task_id instead — poll GET /tts-poll/{task_id}. Credits are reserved on submission and only charged once audio is confirmed stored.

Other AI Tools

Generate a multi-speaker spoken dialogue from a script.

Parameters

Name In Type Required Description
speakers body array yes Array of { name (required), voice_id (nullable) }, up to your plan's max speaker count.
inputs body array yes Array of { speaker (required, matches a name in speakers), text (required) } lines, in order.

Request

curl -s -X POST https://playht.co/api/v1/dialogue \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "speakers": [{"name": "Alex", "voice_id": "abc123"}, {"name": "Jordan", "voice_id": "def456"}],
    "inputs": [{"speaker": "Alex", "text": "Hey, have you tried the new API?"}, {"speaker": "Jordan", "text": "Not yet, how is it?"}]
  }'

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_4b7d1e..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.
422 — Laravel validation error — see the specific field rules for this endpoint below.

Poll the returned task_id with GET /task/{task_id}. Max speakers and max characters are admin-configured per vendor.

Re-voice an uploaded audio file as a different voice.

Parameters

Name In Type Required Description
file body (multipart) file yes Source audio file.
voice_id body string no Target canonical voice ID.
voice_source body string no signature (default) · natural · quick · compact · playht. Matches the voice_source field GET /voices returned for voice_id.
provider_brand body string no Deprecated alias for voice_source: elevenlabs · minimax · edge · kokoro · playht.
model_id body string no Vendor model override, when supported.
remove_background_noise body boolean no Strip background noise before conversion.

Request

curl -s -X POST https://playht.co/api/v1/voice-changer \
  -H "Authorization: Bearer pk_live_..." \
  -F "[email protected]" \
  -F "voice_id=abc123"

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_2c9a4f..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.
422 — Laravel validation error — see the specific field rules for this endpoint below.

multipart/form-data, not JSON. Max file size is admin-configured (default 25MB). Poll with GET /task/{task_id}.

Translate and dub an audio/video file into another language.

Parameters

Name In Type Required Description
target_lang body string yes Target language code.
file body (multipart) file no Source audio/video file.
source_lang body string no Source language code, auto-detected if omitted.
num_speakers body integer no 0–20.
disable_voice_cloning body boolean no Use a stock voice instead of cloning the original speaker.
start_time body number no Trim start (seconds).
end_time body number no Trim end (seconds).

Request

curl -s -X POST https://playht.co/api/v1/dubbing \
  -H "Authorization: Bearer pk_live_..." \
  -F "[email protected]" \
  -F "target_lang=es"

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "dubbing_id": "t1_7a1d3c..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.
422 — Laravel validation error — see the specific field rules for this endpoint below.

multipart/form-data. Max file size is admin-configured (default 100MB). Poll with GET /task/{dubbing_id}.

Generate a sound effect from a text prompt.

Parameters

Name In Type Required Description
text body string yes Description of the sound effect.
duration_seconds body number no 0.5 up to the admin-configured max (default 30).
prompt_influence body number no 0–1.

Request

curl -s -X POST https://playht.co/api/v1/sfx \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Glass shattering on a tile floor", "duration_seconds": 3}'

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_5e8b2d..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.
422 — Laravel validation error — see the specific field rules for this endpoint below.

Poll with GET /task/{task_id}.

Generate music from a text prompt.

Parameters

Name In Type Required Description
prompt body string yes Description of the music to generate.
duration_seconds body number no 5 up to the admin-configured max (default 300).

Request

curl -s -X POST https://playht.co/api/v1/music \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Upbeat lo-fi hip hop, 90 BPM", "duration_seconds": 30}'

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_1f4a9b..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.
422 — Laravel validation error — see the specific field rules for this endpoint below.

Poll with GET /task/{task_id}.

Transcribe an audio file to text.

Parameters

Name In Type Required Description
file body (multipart) file no Audio file to transcribe.
language body string no Language hint.
diarize body boolean no Label which speaker said what.
smart_format body boolean no Apply punctuation/casing formatting.

Request

curl -s -X POST https://playht.co/api/v1/stt \
  -H "Authorization: Bearer pk_live_..." \
  -F "[email protected]" \
  -F "diarize=true"

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_6c3e8a..."
  }
}

// once complete, GET /task/{task_id} body includes:
{
  "status": "done",
  "output": {
    "text": "Hey, have you tried the new API?",
    "transcript": [...],
    "words": [...],
    "language": "en",
    "duration": 4.2
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.

multipart/form-data. Max file size is admin-configured (default 25MB). All fields are optional, so this endpoint has no field-validation errors of its own.

Strip background noise from an audio file, isolating the voice.

Parameters

Name In Type Required Description
file body (multipart) file no Audio file to process.

Request

curl -s -X POST https://playht.co/api/v1/voice-isolate \
  -H "Authorization: Bearer pk_live_..." \
  -F "[email protected]"

Response

{
  "status": 200,
  "body": {
    "status": "processing",
    "task_id": "t1_3d7f2b..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
402 insufficient_credits You have no credits remaining this month. Please upgrade your plan.
402 team_limit_reached You've used your team spending allocation for this period. Ask your team owner to raise it, or wait for the next reset.
429 concurrency_limit_reached You have {n} job(s) already running, the max your plan allows at once. Wait for one to finish or upgrade your plan.
503 vendor_busy The service is currently at capacity. Please try again in a moment.
503 — This tool is currently unavailable. Please try again later.

multipart/form-data. Max file size is admin-configured (default 25MB). Poll with GET /task/{task_id}.

Task & Job Status

Poll the status of any task returned by dialogue, voice-changer, dubbing, sfx, music, stt, or voice-isolate.

Parameters

Name In Type Required Description
task_id path string yes The neutral-prefixed id (t1_.../t2_...) returned when the job was submitted.

Request

curl -s https://playht.co/api/v1/task/t1_9f8c2a... \
  -H "Authorization: Bearer pk_live_..."

Response

{
  "status": 200,
  "body": {
    "status": "done",
    "audio_url": "https://yourapp.com/app/media?sig=...",
    "url": "https://yourapp.com/app/media?sig=...",
    "output_url": "https://yourapp.com/app/media?sig=..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
400 — Invalid task ID format.
403 — Access denied — this task_id belongs to a different account.

Ownership is enforced: a task_id only resolves for the account that created it. All vendor-hosted media URLs are rewritten to the signed media proxy — a vendor domain never appears in the response.

Poll a single-chunk (short, unsplit) TTS submission. Finalizes the job — charges credits — on completion.

Parameters

Name In Type Required Description
task_id path string yes The task_id returned by POST /tts for a short (non-chunked) request.

Request

curl -s https://playht.co/api/v1/tts-poll/t1_9f8c2a... \
  -H "Authorization: Bearer pk_live_..."

Response

{
  "status": 200,
  "body": {
    "status": "done",
    "audio_url": "https://yourapp.com/app/audio/...",
    "url": "https://yourapp.com/app/audio/...",
    "output_url": "https://yourapp.com/app/audio/..."
  }
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
400 — Invalid task ID format.
403 — Access denied — this task_id belongs to a different account.

Only use this for short text that did NOT get chunked (see POST /tts notes). Credits are charged here, on confirmed completion — not at submission time. If audio download/storage fails transiently, the response reports { "status": "doing", "progress": 99 } so the client keeps polling instead of a false failure.

Poll a chunked TTS job (long text that was automatically split and is being merged).

Parameters

Name In Type Required Description
job_id path string yes The job_id (a UUID, not a t1_/t2_-prefixed task id) returned by POST /tts for long text.

Request

curl -s https://playht.co/api/v1/tts-job/7f3e1a2b-... \
  -H "Authorization: Bearer pk_live_..."

Response

{
  "status": "complete",
  "audio_url": "https://yourapp.com/app/audio/...",
  "total_chunks": 4,
  "completed_chunks": 4
}

// while still running:
{
  "status": "processing",
  "total_chunks": 4,
  "completed_chunks": 2
}

// on failure:
{
  "status": "failed",
  "error": "1 chunk(s) failed to generate."
}

Error responses

Status Error key Message
401 unauthenticated Missing, invalid, expired, or revoked Bearer token.
404 — Job not found.

Each call polls any still-processing chunks, and once every chunk is done, merges them with ffmpeg server-side and charges credits (capped at the amount reserved at submission). If a chunk permanently fails, remaining credits for that job are released back to your balance automatically.

Authentication

Every request needs a single header — no separate user/account id:

AUTHORIZATION: Bearer pk_live_...
Get your API key →

Rate Limits

One flat limit per API key, regardless of plan:

Every key 120 req / min
Compare plans →