Create a voiceover
create_voiceoverPOST/v1/voiceoversBeta
Starts reading a script aloud with a synthetic voice, and returns the tool run that makes the audio file.
Beta. This endpoint is supported and can gain new fields. If a change would break your code, we tell you before it takes effect and list it in the changelog.
About
Call this to turn a script into an MP3 file read by a synthetic voice. Choose the voice with list_voices (GET /v1/reference/voices): it must speak the language you send. The call answers 202 at once with the run, queued, and a Location to follow; read it with get_tool_run (GET /v1/tool-runs/{toolRunId}) (with ?wait= to hold the request) until it is done, then download the file from its downloadUrl. The file is also an asset of the account, with origin voice. Always send an Idempotency-Key, the same one on every retry: a retry of the same request returns the same run and never uses a second tool run.
Example use
A team that makes explainer videos sends a script and a voice chosen with list_voices (GET /v1/reference/voices). It follows the run with get_tool_run (GET /v1/tool-runs/{toolRunId}) and uses the MP3 as the sound of its next video.
Endpoint
POST https://api.gpt-video.com/v1/voiceovers- Scope
spend. - Rate limit 10 requests a minute for each key and 20 for each account, shared by every
spendcall. See rate limits. Idempotency-Keyis required.- It starts work that runs on its own. The answer comes back before the work is done.
Parameters
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | string | Required | This operation spends, so the key is required. Create it once, before the request, and send the same key on every retry. From 1 to 255 visible characters, such as a UUID. See idempotency. |
Body
| Name | Type | Required | Description |
|---|---|---|---|
script | string | Required | The text to read aloud, up to 5000 characters. From 1 to 5000 characters. |
voice | string | Required | The voice that reads the script, as list_voices (GET /v1/reference/voices) names it, like en-aria. It must speak the language of the script. One of en-aria, en-jenny, en-guy, en-christopher, it-elsa, it-isabella, it-diego, it-giuseppe. |
language | string | Required | The language of the script: en for English, it for Italian. One of en, it. |
title | string | Optional | A name for the run and its file. Leave it out to use the start of the script. From 1 to 120 characters. |
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Read a script with an English voice.
# Create the key once. Send this same key again if you retry this request.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST "https://api.gpt-video.com/v1/voiceovers" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en",
"title": "Launch voiceover"
}'// Create the key once. Send this same key again if you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api.gpt-video.com/v1/voiceovers", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en",
"title": "Launch voiceover"
}),
});
const data = await res.json();
console.log(data);import os
import uuid
import requests
# Create the key once. Send this same key again if you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post(
"https://api.gpt-video.com/v1/voiceovers",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en",
"title": "Launch voiceover"
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "create_voiceover",
"arguments": {
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en",
"title": "Launch voiceover",
"confirm": true,
"idempotencyKey": "REPLACE-WITH-A-NEW-UUID-FOR-THIS-CALL"
}
}
}Response
A successful call answers HTTP 202. Returns a Tool run object in toolRun.
Fields to check
| Name | Type | Description |
|---|---|---|
toolRun.id | string | The tool run id. It starts with run_. |
toolRun.status | string | queued and running are still working; done and failed are final. |
toolRun.file.downloadUrl | string or null | A link to download the file, on a read of a finished run; null otherwise. |
toolRun.file.downloadUrlExpiresAt | string or null | An ISO 8601 time in UTC. |
toolRun.jobId | string or null | The job that carries out the run, to follow with get_job (GET /v1/jobs/{jobId}); null once it is gone. |
paidWith | string or null | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. |
Response example
Location: https://api.gpt-video.com/v1/tool-runs/run_5e8a1c3f7d2b4096a1e4
{
"toolRun": {
"id": "run_5e8a1c3f7d2b4096a1e4",
"tool": "voiceover",
"status": "queued",
"progress": 0,
"errorMessage": null,
"file": null,
"ideas": null,
"jobId": "job_2c7f9e1a4d6b48035f9e",
"createdAt": "2026-09-01T11:00:00.000Z",
"finishedAt": null,
"voiceover": {
"title": "Launch voiceover",
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en"
},
"separation": null,
"brainstorm": null
},
"paidWith": "plan"
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
toolRun | Tool run object | Required | Described in Tool run object. |
paidWith | string or null | Required | Who paid for this request: plan is the credits or the daily allowance of the account's plan, api_bonus is the API bonus. null when this request paid nothing, because the same work was already running. A retry with the same Idempotency-Key says who paid the first time. One of plan, api_bonus. |
More examples
A voice that does not speak the language of the script
POST /v1/voiceovers
{
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "it-elsa",
"language": "en"
}{
"error": {
"code": "VALIDATION_FAILED",
"message": "Voice it-elsa is not available in English. Call list_voices to see the voices you can use.",
"hint": "Fix the request as the message says, then send it again.",
"requestId": "req_3e9b1d7a5c2f4e8a0d6b9c13"
}
}The tool runs of the day are used up
POST /v1/voiceovers
{
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en"
}{
"error": {
"code": "DAILY_LIMIT_REACHED",
"message": "Daily limit of tool runs reached (100). It resets at 00:00 UTC. Upgrade your plan for a higher limit.",
"hint": "The limit resets at 00:00 UTC. A higher plan raises it: https://www.gpt-video.com/app/billing.",
"requestId": "req_6a2d9f1c4e8b3a7d0f5c2e94"
}
}Voices through the API are off
POST /v1/voiceovers
{
"script": "Meet the editor that turns one long video into ten short clips.",
"voice": "en-aria",
"language": "en"
}Retry-After: 60
{
"error": {
"code": "UNAVAILABLE",
"message": "Making voices through the API is not available right now.",
"hint": "Retry later.",
"requestId": "req_9a3d7f1c5e8b2a6d0c4f7e13"
}
}Errors
| Code | Status | When | What to do |
|---|---|---|---|
INSUFFICIENT_CREDITS | 402 | See the error reference. | Stop and tell the user. |
PLAN_REQUIRED | 402 | See the error reference. | Stop and tell the user. |
DAILY_LIMIT_REACHED | 429 | The tool runs of the day are used up | Stop until the limit resets at 00:00 UTC. |
Every call can also answer these errors. VALIDATION_FAILED, UNKNOWN_PARAMETER, IDEMPOTENCY_KEY_REQUIRED, UNAUTHORIZED, SCOPE_MISSING, IDEMPOTENCY_IN_PROGRESS, PAYLOAD_TOO_LARGE, IDEMPOTENCY_KEY_REUSED, RATE_LIMITED, INTERNAL, UNAVAILABLE.
Cost
Uses one tool run of the plan's daily allowance, which resets at 00:00 UTC. When the API bonus pays, it also uses one bonus credit. Nothing is returned if the run fails.
- Paid by the API bonus, it uses 1 bonus credit.
- When the API bonus pays, an account can run 10 tools a day.
- See API pricing for who pays first.
Related operations
get_tool_run(GET /v1/tool-runs/{toolRunId}) Get a tool runlist_tool_runs(GET /v1/tool-runs) List the tool runslist_voices(GET /v1/reference/voices) List the voicesget_asset(GET /v1/assets/{assetId}) Get an asset
Questions
What happens if my request times out and I send it again?
Send it again with the same Idempotency-Key. You get the same run back and no second tool run is used from the daily allowance. A new key starts a new run.
Which voices can I use?
The ones list_voices (GET /v1/reference/voices) returns for the language of the script. A voice of another language answers VALIDATION_FAILED, and nothing is used.
Where is the audio file?
Once the run is done, get_tool_run (GET /v1/tool-runs/{toolRunId}) gives its file with a downloadUrl. The file is also an asset of the account, with origin voice, so get_asset (GET /v1/assets/{assetId}) and list_assets (GET /v1/assets) show it too.
What does UNAVAILABLE mean here?
Making voices through the API is off for now. Nothing is used: wait the seconds in Retry-After and send the same request again later.