Skip to content
Create a key

Paste a key to use it in the examples on these pages. The key stays in this browser tab. These pages never send it anywhere.

Write the story of a fake text project

update_fake_text_storyPUT/v1/projects/{projectId}/storyBeta

Replaces the story of a fake text project with the one you send, or writes its first story, and rebuilds its video.

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

Send the whole story as you want it: the title, the two people, the voices and every line in order. Send the line ids you read with get_fake_text_story (GET /v1/projects/{projectId}/story): a line sent with its id keeps its voice while its text does not change. Lines sent without an id get a new id each time you send the story, and a line with a new id has no voice until create_fake_text_voices (POST /v1/projects/{projectId}/story/voices) makes it. A project without a story gets this one as its first, and nothing is used. The language of the story is the language of the project. The answer is the story as it is saved.

Example use

A team that writes its own chat scripts sends the whole story as one object, with the two people and every line. It then calls create_fake_text_voices (POST /v1/projects/{projectId}/story/voices), which voices only the lines that are new or changed.

Endpoint

Code
PUT https://api.gpt-video.com/v1/projects/{projectId}/story
  • Scope clips:write.
  • Rate limit 60 requests a minute for each key and 120 for each account, shared by every write call. See rate limits.
  • No Idempotency-Key.

Parameters

Path

NameTypeRequiredDescription
projectIdstringRequiredThe project id. It starts with prj_. Matches ^prj_[0-9a-f]{20}$.

Body

NameTypeRequiredDescription
titlestringRequiredThe title of the story, also the title of its video. From 1 to 200 characters. Matches \S.
contactNamestringOptionalThe name shown at the top of the chat. Without it the chat shows left.name. From 1 to 60 characters. Matches \S.
leftobjectRequiredThe other person in the chat, whose bubbles are on the left.
left.namestringRequiredThe name of the other person in the chat. From 1 to 60 characters. Matches \S.
rightobjectRequiredThe person whose phone shows the chat, whose bubbles are on the right.
right.namestringRequiredThe name of the person whose phone shows the chat. From 1 to 60 characters. Matches \S.
voicesobjectOptionalThe voice of each side. A side you leave out keeps its voice.
voices.leftstring or nullOptionalA voice id as list_voices (GET /v1/reference/voices) names it, in the language of the project. null gives this side the default voice of the language; leaving it out keeps the voice this side has. From 1 to 40 characters.
voices.rightstring or nullOptionalA voice id as list_voices (GET /v1/reference/voices) names it, in the language of the project. null gives this side the default voice of the language; leaving it out keeps the voice this side has. From 1 to 40 characters.
messagesarray of objectsRequiredThe lines of the chat in the order they appear, from 1 to 60. From 1 to 60 items.
messages[].idstringOptionalThe id of a line you read with get_fake_text_story (GET /v1/projects/{projectId}/story). Send it to keep the line and its voice; leave it out for a new line. Matches ^[A-Za-z0-9_-]{1,64}$.
messages[].sidestringRequiredWho sends the line: left is the other person, right is the person whose phone shows the chat. One of left, right.
messages[].textstringRequiredThe words of the line, up to 300 characters. From 1 to 300 characters. Matches \S.

Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.

Request

Change a line and add one.

Request example
curl -X PUT "https://api.gpt-video.com/v1/projects/prj_ba8b07a88bfb14585459/story" \
  -H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "The surprise party",
    "left": {
      "name": "Sam"
    },
    "right": {
      "name": "Me"
    },
    "voices": {
      "left": "en-guy",
      "right": "en-aria"
    },
    "messages": [
      {
        "id": "msg_e09afef44e32faf0bcb7",
        "side": "left",
        "text": "Are you free on Saturday night?"
      },
      {
        "id": "msg_568316dbc0cca81d3b24",
        "side": "right",
        "text": "Maybe. Why do you ask?"
      },
      {
        "id": "msg_556466181cf09d6bf0a2",
        "side": "left",
        "text": "No reason. Wear something nice."
      },
      {
        "side": "right",
        "text": "Should I be worried?"
      }
    ]
  }'
const res = await fetch("https://api.gpt-video.com/v1/projects/prj_ba8b07a88bfb14585459/story", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "title": "The surprise party",
    "left": {
      "name": "Sam"
    },
    "right": {
      "name": "Me"
    },
    "voices": {
      "left": "en-guy",
      "right": "en-aria"
    },
    "messages": [
      {
        "id": "msg_e09afef44e32faf0bcb7",
        "side": "left",
        "text": "Are you free on Saturday night?"
      },
      {
        "id": "msg_568316dbc0cca81d3b24",
        "side": "right",
        "text": "Maybe. Why do you ask?"
      },
      {
        "id": "msg_556466181cf09d6bf0a2",
        "side": "left",
        "text": "No reason. Wear something nice."
      },
      {
        "side": "right",
        "text": "Should I be worried?"
      }
    ]
  }),
});
const data = await res.json();
console.log(data);
import os

import requests

r = requests.put(
    "https://api.gpt-video.com/v1/projects/prj_ba8b07a88bfb14585459/story",
    headers={
        "Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
    },
    json={
        "title": "The surprise party",
        "left": {
            "name": "Sam"
        },
        "right": {
            "name": "Me"
        },
        "voices": {
            "left": "en-guy",
            "right": "en-aria"
        },
        "messages": [
            {
                "id": "msg_e09afef44e32faf0bcb7",
                "side": "left",
                "text": "Are you free on Saturday night?"
            },
            {
                "id": "msg_568316dbc0cca81d3b24",
                "side": "right",
                "text": "Maybe. Why do you ask?"
            },
            {
                "id": "msg_556466181cf09d6bf0a2",
                "side": "left",
                "text": "No reason. Wear something nice."
            },
            {
                "side": "right",
                "text": "Should I be worried?"
            }
        ]
    },
)
data = r.json()
print(data)
{
  "method": "tools/call",
  "params": {
    "name": "update_fake_text_story",
    "arguments": {
      "projectId": "prj_ba8b07a88bfb14585459",
      "title": "The surprise party",
      "left": {
        "name": "Sam"
      },
      "right": {
        "name": "Me"
      },
      "voices": {
        "left": "en-guy",
        "right": "en-aria"
      },
      "messages": [
        {
          "id": "msg_e09afef44e32faf0bcb7",
          "side": "left",
          "text": "Are you free on Saturday night?"
        },
        {
          "id": "msg_568316dbc0cca81d3b24",
          "side": "right",
          "text": "Maybe. Why do you ask?"
        },
        {
          "id": "msg_556466181cf09d6bf0a2",
          "side": "left",
          "text": "No reason. Wear something nice."
        },
        {
          "side": "right",
          "text": "Should I be worried?"
        }
      ]
    }
  }
}

Response

A successful call answers HTTP 200.

Fields to check

NameTypeDescription
story.idstringThe story id. It starts with cnv_.
story.messages[].idstringThe line id. Send it back with update_fake_text_story (PUT /v1/projects/{projectId}/story) to keep the line and its voice.

Response example

HTTP 200
{
  "story": {
    "id": "cnv_a744d5ef6cc005e19285",
    "projectId": "prj_ba8b07a88bfb14585459",
    "title": "The surprise party",
    "contactName": "Sam",
    "language": "en",
    "left": {
      "name": "Sam"
    },
    "right": {
      "name": "Me"
    },
    "voices": {
      "left": "en-guy",
      "right": "en-aria"
    },
    "messages": [
      {
        "id": "msg_e09afef44e32faf0bcb7",
        "side": "left",
        "text": "Are you free on Saturday night?",
        "voiced": true,
        "voiceDurationSec": 1.9
      },
      {
        "id": "msg_568316dbc0cca81d3b24",
        "side": "right",
        "text": "Maybe. Why do you ask?",
        "voiced": false,
        "voiceDurationSec": null
      },
      {
        "id": "msg_556466181cf09d6bf0a2",
        "side": "left",
        "text": "No reason. Wear something nice.",
        "voiced": true,
        "voiceDurationSec": 1.9
      },
      {
        "id": "msg_9b3e1f7c2a5d4860b1c4",
        "side": "right",
        "text": "Should I be worried?",
        "voiced": false,
        "voiceDurationSec": null
      }
    ],
    "voicedCount": 2,
    "createdAt": "2026-08-31T22:12:00.000Z",
    "updatedAt": "2026-09-01T11:05:00.000Z"
  }
}
All response fields
NameTypeRequiredDescription
storyobjectRequired
story.idstringRequiredThe story id. It starts with cnv_.
story.projectIdstringRequired
story.titlestringRequired
story.contactNamestringRequiredThe name shown at the top of the chat.
story.languagestringRequiredOne of en, it.
story.leftobjectRequiredThe other person in the chat.
story.rightobjectRequiredThe person whose phone shows the chat.
story.voicesobjectRequiredThe voice of each side, as list_voices (GET /v1/reference/voices) names it; null for the default voice of the language, or for a voice that is not available through the API.
story.messagesarray of objectsRequired
story.voicedCountintegerRequiredHow many lines have their voice.
story.createdAtstringRequiredAn ISO 8601 time in UTC.
story.updatedAtstringRequiredAn ISO 8601 time in UTC.
Fields of story.left
NameTypeRequiredDescription
story.left.namestringRequired
Fields of story.right
NameTypeRequiredDescription
story.right.namestringRequired
Fields of story.voices
NameTypeRequiredDescription
story.voices.leftstring or nullRequired
story.voices.rightstring or nullRequired
Fields of story.messages[]
NameTypeRequiredDescription
story.messages[].idstringRequiredThe line id. Send it back with update_fake_text_story (PUT /v1/projects/{projectId}/story) to keep the line and its voice.
story.messages[].sidestringRequiredOne of left, right.
story.messages[].textstringRequired
story.messages[].voicedbooleanRequiredtrue when the line has its voice.
story.messages[].voiceDurationSecnumber or nullRequiredHow long the voice of the line lasts, in seconds; null without a voice.
More examples

Write the first story of a project

PUT /v1/projects/prj_ba8b07a88bfb14585459/story

Request body
{
  "title": "The last slice",
  "contactName": "Alex",
  "left": {
    "name": "Alex"
  },
  "right": {
    "name": "Me"
  },
  "messages": [
    {
      "side": "left",
      "text": "Who ate my pizza?"
    },
    {
      "side": "right",
      "text": "Not me."
    }
  ]
}
HTTP 200
{
  "story": {
    "id": "cnv_5f1e2d3c4b5a6978a8b9",
    "projectId": "prj_ba8b07a88bfb14585459",
    "title": "The last slice",
    "contactName": "Alex",
    "language": "en",
    "left": {
      "name": "Alex"
    },
    "right": {
      "name": "Me"
    },
    "voices": {
      "left": null,
      "right": null
    },
    "messages": [
      {
        "id": "msg_1a6c4e8f0b2d4975a3c8",
        "side": "left",
        "text": "Who ate my pizza?",
        "voiced": false,
        "voiceDurationSec": null
      },
      {
        "id": "msg_7e0b5d3a9c1f4268e4d0",
        "side": "right",
        "text": "Not me.",
        "voiced": false,
        "voiceDurationSec": null
      }
    ],
    "voicedCount": 0,
    "createdAt": "2026-09-01T11:05:00.000Z",
    "updatedAt": "2026-09-01T11:05:00.000Z"
  }
}

A voice of another language

PUT /v1/projects/prj_ba8b07a88bfb14585459/story

Request body
{
  "title": "The surprise party",
  "left": {
    "name": "Sam"
  },
  "right": {
    "name": "Me"
  },
  "voices": {
    "left": "it-diego"
  },
  "messages": [
    {
      "id": "msg_e09afef44e32faf0bcb7",
      "side": "left",
      "text": "Are you free on Saturday night?"
    }
  ]
}
HTTP 400
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Voice it-diego is not available in en. Call list_voices to see the voices you can use.",
    "requestId": "req_0d7b3f9a5e2c48614b8e3a7f"
  }
}

A project that is not a fake text project

PUT /v1/projects/prj_7c2e9a4f1b3d4e8096a5/story

Request body
{
  "title": "A story",
  "left": {
    "name": "Sam"
  },
  "right": {
    "name": "Me"
  },
  "messages": [
    {
      "side": "left",
      "text": "Hi"
    }
  ]
}
HTTP 409
{
  "error": {
    "code": "CONFLICT",
    "message": "This project is not a fake text project. Stories exist only in fake text projects.",
    "requestId": "req_9a4e6c1b3d7f45208c6d1e3b"
  }
}

Errors

CodeStatusWhenWhat to do
NOT_FOUND404See the error reference.Change the request, then send it again.
CONFLICT409A project that is not a fake text projectRead the resource again, then decide. Do not send the same request again.

Every call can also answer these errors. VALIDATION_FAILED, UNKNOWN_PARAMETER, UNAUTHORIZED, SCOPE_MISSING, PAYLOAD_TOO_LARGE, RATE_LIMITED, INTERNAL, UNAVAILABLE.

Cost

No credits are used.

Questions

What happens if I send the same story twice?

Lines with an id stay the same lines. Lines sent without an id get a new id each time, so send back the ids you read with get_fake_text_story (GET /v1/projects/{projectId}/story).

Which lines need a new voice after I change the story?

Lines whose words or side changed, new lines, and every line of a side whose voice you changed. create_fake_text_voices (POST /v1/projects/{projectId}/story/voices) makes only those.

How long can a story be?

Up to 60 lines of up to 300 characters each.

View as Markdown