Write a fake text story with GPT-Video
generate_fake_text_storyPOST/v1/projects/{projectId}/story/generateBeta
Starts writing a new story for a fake text project from your brief, 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
Use it to let GPT-Video write the story from a short brief; an empty brief writes a sample story. The answer is 202 with the job: follow it with get_job (GET /v1/jobs/{jobId}), then read the story with get_fake_text_story (GET /v1/projects/{projectId}/story). The new story replaces the story of the project, and the video follows it; the old story is no longer shown. If a story is already being written for the project, you get that job back and nothing is used (reused is true). Make the voices afterwards with create_fake_text_voices (POST /v1/projects/{projectId}/story/voices).
Example use
A creator who posts a new chat story every morning sends a one-line brief, follows the job with get_job (GET /v1/jobs/{jobId}), and reads the result with get_fake_text_story (GET /v1/projects/{projectId}/story) before making the voices.
Endpoint
POST https://api.gpt-video.com/v1/projects/{projectId}/story/generate- 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. |
Body
| Name | Type | Required | Description |
|---|---|---|---|
brief | string | Required | What the story is about, up to 4000 characters. An empty brief writes a sample story. At most 4000 characters. |
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Write a story from a brief.
# 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/generate" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"brief": "Two roommates argue about who ate the last slice of pizza"
}'// 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/generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Idempotency-Key": idempotencyKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
"brief": "Two roommates argue about who ate the last slice of pizza"
}),
});
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/generate",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={
"brief": "Two roommates argue about who ate the last slice of pizza"
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "generate_fake_text_story",
"arguments": {
"projectId": "prj_ba8b07a88bfb14585459",
"brief": "Two roommates argue about who ate the last slice of pizza",
"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 a story 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": "story"
},
"reused": false,
"paidWith": "plan"
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
job | Job object | Required | Described in Job object. |
reused | boolean | Required | true when a story 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
A story that is already being written
POST /v1/projects/prj_ba8b07a88bfb14585459/story/generate
{
"brief": "A text from an old friend"
}{
"job": {
"status": "running",
"progress": 0.4,
"errorMessage": null,
"projectId": "prj_ba8b07a88bfb14585459",
"compositionId": null,
"exportId": null,
"assetId": null,
"toolRunId": null,
"turnId": null,
"createdAt": "2026-08-31T22:10:00.000Z",
"startedAt": "2026-08-31T22:11:00.000Z",
"finishedAt": null,
"id": "job_a84eefa1e7fd196089dd",
"kind": "story"
},
"reused": true,
"paidWith": null
}The fake text scripts of the day are used up
POST /v1/projects/prj_ba8b07a88bfb14585459/story/generate
{
"brief": "A wrong number"
}{
"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_3c8e0a5d7b2f49146e9c1a8d"
}
}A project that is not a fake text project
POST /v1/projects/prj_7c2e9a4f1b3d4e8096a5/story/generate
{
"brief": "A wrong number"
}{
"error": {
"code": "CONFLICT",
"message": "This project is not a fake text project. Stories exist only in fake text projects.",
"requestId": "req_7f1d4b8e2a6c40935b7e2d9a"
}
}Writing stories through the API is off
POST /v1/projects/prj_ba8b07a88bfb14585459/story/generate
{
"brief": "A wrong number"
}{
"error": {
"code": "UNAVAILABLE",
"message": "Writing a story through the API is not available yet.",
"requestId": "req_5d0a8c3e6f1b42977c4a0e5b"
}
}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 | A project that is not a fake text project | 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_fake_text_voices(POST /v1/projects/{projectId}/story/voices) Make the voices of a fake text storycreate_fake_text_project(POST /v1/projects/fake-text) Create a fake text project
Questions
What happens to the story the project had?
The new story replaces it: the project and its video show only the new one. Read it with get_fake_text_story (GET /v1/projects/{projectId}/story) once the job is done.
Is the script returned if writing the story fails?
No. The script of the day is used when the request is accepted. Ask for a new story again; a retry with the same Idempotency-Key never uses a second one.