Make the voices of a fake text story
create_fake_text_voicesPOST/v1/projects/{projectId}/story/voicesBeta
Starts making the voice of every line of the story that does not have it yet, and returns the job to follow.
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 it once the story is as you want it and voiceover is on. Only lines without their voice are voiced: new lines, lines whose words changed, and the lines of a side whose voice changed. The answer is 202 with the job: follow it with get_job (GET /v1/jobs/{jobId}), then export the video with create_export (POST /v1/compositions/{compositionId}/exports). If voices are already being made for the story, you get that job back and nothing is used (reused is true). A story whose lines all have their voice is refused before anything is used.
Example use
After the story is final, a script asks for the voice of every line that does not have one yet. It follows the job with get_job (GET /v1/jobs/{jobId}) and calls create_export (POST /v1/compositions/{compositionId}/exports) when it is done.
Endpoint
POST https://api.gpt-video.com/v1/projects/{projectId}/story/voices- 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
Path
| Name | Type | Required | Description |
|---|---|---|---|
projectId | string | Required | The project id. It starts with prj_. Matches ^prj_[0-9a-f]{20}$. |
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. |
Request
Make the voices of the story.
# 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/projects/prj_ba8b07a88bfb14585459/story/voices" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY"// 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/projects/prj_ba8b07a88bfb14585459/story/voices", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
},
});
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/projects/prj_ba8b07a88bfb14585459/story/voices",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "create_fake_text_voices",
"arguments": {
"projectId": "prj_ba8b07a88bfb14585459",
"confirm": true,
"idempotencyKey": "REPLACE-WITH-A-NEW-UUID-FOR-THIS-CALL"
}
}
}Response
A successful call answers HTTP 202. Returns a Job object in job.
Fields to check
| Name | Type | Description |
|---|---|---|
job.id | string | The job id. It starts with job_. |
job.status | string | queued and running are still working; done, failed and cancelled are final. |
reused | boolean | true when making the voices was already running for this project and this is its job, paid by nobody again. |
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/jobs/job_7d2c9a4e1f0b4386c5a2
{
"job": {
"status": "queued",
"progress": 0,
"errorMessage": null,
"projectId": "prj_ba8b07a88bfb14585459",
"compositionId": null,
"exportId": null,
"assetId": null,
"toolRunId": null,
"turnId": null,
"createdAt": "2026-09-01T11:10:00.000Z",
"startedAt": null,
"finishedAt": null,
"id": "job_7d2c9a4e1f0b4386c5a2",
"kind": "voices"
},
"reused": false,
"paidWith": "plan"
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
job | Job object | Required | Described in Job object. |
reused | boolean | Required | true when making the voices was already running for this project and this is its job, paid by nobody again. |
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
Voices that are already being made
POST /v1/projects/prj_ba8b07a88bfb14585459/story/voices
{
"job": {
"status": "running",
"progress": 0.5,
"errorMessage": null,
"projectId": "prj_ba8b07a88bfb14585459",
"compositionId": null,
"exportId": null,
"assetId": null,
"toolRunId": null,
"turnId": null,
"createdAt": "2026-08-31T22:12:00.000Z",
"startedAt": "2026-08-31T22:13:00.000Z",
"finishedAt": null,
"id": "job_8060ba8cd16645d280c1",
"kind": "voices"
},
"reused": true,
"paidWith": null
}Every line already has its voice
POST /v1/projects/prj_ba8b07a88bfb14585459/story/voices
{
"error": {
"code": "CONFLICT",
"message": "Every line of this story already has its voice.",
"requestId": "req_1e6b9d3a0c7f48252d8b4f0e"
}
}A project with no story yet
POST /v1/projects/prj_ba8b07a88bfb14585459/story/voices
{
"error": {
"code": "CONFLICT",
"message": "This project has no story yet. Write one with update_fake_text_story, or let GPT-Video write one with generate_fake_text_story.",
"requestId": "req_4a7c2e9f1b5d40636f0a3c8e"
}
}Voices are turned off
POST /v1/projects/prj_ba8b07a88bfb14585459/story/voices
{
"error": {
"code": "CONFLICT",
"message": "Voices are turned off for this project. Turn them on with update_fake_text_project.",
"requestId": "req_8d3f6b1a4e9c27050a5d8e2c"
}
}A voice that is not available through the API
POST /v1/projects/prj_ba8b07a88bfb14585459/story/voices
{
"error": {
"code": "CONFLICT",
"message": "This project uses a voice that is not available through the API. Choose one with update_fake_text_story.",
"requestId": "req_2b9e5c8d0a3f46171e4c9a6d"
}
}The fake text scripts of the day are used up
POST /v1/projects/prj_ba8b07a88bfb14585459/story/voices
{
"error": {
"code": "DAILY_LIMIT_REACHED",
"message": "Daily limit of fake text scripts reached (100). It resets at 00:00 UTC. Upgrade your plan for a higher limit.",
"requestId": "req_6f2a0d7c3e9b41588b1f6d3a"
}
}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. |
NOT_FOUND | 404 | See the error reference. | Change the request, then send it again. |
CONFLICT | 409 | Every line already has its voice | Read the resource again, then decide. Do not send the same request again. |
DAILY_LIMIT_REACHED | 429 | The fake text scripts 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 fake text script 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 work fails.
- Paid by the API bonus, it uses 1 bonus credit.
- When the API bonus pays, an account can make 10 fake text scripts a day.
- See API pricing for who pays first.
Related operations
get_job(GET /v1/jobs/{jobId}) Get a jobget_fake_text_story(GET /v1/projects/{projectId}/story) Get the story of a fake text projectupdate_fake_text_story(PUT /v1/projects/{projectId}/story) Write the story of a fake text projectcreate_export(POST /v1/compositions/{compositionId}/exports) Export a compositiongenerate_fake_text_story(POST /v1/projects/{projectId}/story/generate) Write a fake text story with GPT-Video
Questions
Which lines get a voice?
Lines that do not have their voice yet: new lines, lines whose words changed, and the lines of a side whose voice changed. Lines that already have their voice are not made again.
Is the script returned if making the voices fails?
No. Ask for the voices again: lines that got their voice before the failure keep it, and a retry with the same Idempotency-Key never uses a second script.