Correct words in the captions
replace_caption_wordsPOST/v1/compositions/{compositionId}/captions/replaceBeta
Replaces words in the captions of a composition, such as a name the transcript got wrong.
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
Each replacement finds its word without minding capitals or punctuation, and keeps the punctuation of the word it replaces. With occurrence, only that occurrence of the word changes, counting from 1; without it, every one does. A word that is not in the captions is listed in notFound with up to three similar words, and nothing is written when no word was found. Send the revision you read; up to 30 replacements a request. Works on Auto Clip, streamer, split screen and Reddit story compositions; the captions of a Vibe Editing project are corrected with apply_timeline_operations (POST /v1/projects/{projectId}/timeline/operations).
Example use
A podcast team sees that the captions spell the name of a guest wrongly in every clip. A script sends the same replacement to each composition of the episode.
Endpoint
POST https://api.gpt-video.com/v1/compositions/{compositionId}/captions/replace- Scope
clips:write. - Rate limit 60 requests a minute for each key and 120 for each account, shared by every
writecall. See rate limits. Idempotency-Keyis optional.
Parameters
Path
| Name | Type | Required | Description |
|---|---|---|---|
compositionId | string | Required | The id of the composition: accept_clip (POST /v1/clips/{clipId}/accept) returns it for a clip, and list_compositions (GET /v1/projects/{projectId}/compositions) or the project's compositionId give it for the other kinds of project. Matches ^cmp_[0-9a-f]{20}$. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
Idempotency-Key | string | Optional | Send the same key on every retry of this request. From 1 to 255 visible characters, such as a UUID. See idempotency. |
Body
| Name | Type | Required | Description |
|---|---|---|---|
expectedRevision | integer | Required | The revision of the composition you read with get_composition (GET /v1/compositions/{compositionId}). If the composition changed since, the request answers CONFLICT and changes nothing. At least 0. |
replacements | array of objects | Required | The words to replace, from 1 to 30. When two replacements find the same word, the first one wins it. From 1 to 30 items. |
replacements[].from | string | Required | The word to find, compared without capitals or punctuation. From 1 to 200 characters. |
replacements[].to | string | Required | The text to write in its place. From 1 to 200 characters. |
replacements[].occurrence | integer | Optional | Which occurrence of the word to replace, counting from 1. Leave it out to replace every one. At least 1. |
Send only the fields listed here. Any other field is refused with UNKNOWN_PARAMETER.
Request
Correct a word everywhere.
curl -X POST "https://api.gpt-video.com/v1/compositions/cmp_4e8c2a6f09d14b73a1e5/captions/replace" \
-H "Authorization: Bearer $GPT_VIDEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expectedRevision": 0,
"replacements": [
{
"from": "customer",
"to": "client"
}
]
}'const res = await fetch("https://api.gpt-video.com/v1/compositions/cmp_4e8c2a6f09d14b73a1e5/captions/replace", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.GPT_VIDEO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"expectedRevision": 0,
"replacements": [
{
"from": "customer",
"to": "client"
}
]
}),
});
const data = await res.json();
console.log(data);import os
import requests
r = requests.post(
"https://api.gpt-video.com/v1/compositions/cmp_4e8c2a6f09d14b73a1e5/captions/replace",
headers={
"Authorization": f"Bearer {os.environ['GPT_VIDEO_API_KEY']}",
},
json={
"expectedRevision": 0,
"replacements": [
{
"from": "customer",
"to": "client"
}
]
},
)
data = r.json()
print(data){
"method": "tools/call",
"params": {
"name": "replace_caption_words",
"arguments": {
"compositionId": "cmp_4e8c2a6f09d14b73a1e5",
"expectedRevision": 0,
"replacements": [
{
"from": "customer",
"to": "client"
}
]
}
}
}Response
A successful call answers HTTP 200. Returns a Composition object in composition.
Fields to check
| Name | Type | Description |
|---|---|---|
composition.id | string | The composition id. It starts with cmp_. |
composition.framing.status | string | |
composition.framing.jobId | string or null |
Response example
{
"composition": {
"id": "cmp_4e8c2a6f09d14b73a1e5",
"projectId": "prj_7c2e9a4f1b3d4e8096a5",
"clipId": "cnd_9e04a6c3d12b4f78a5b9",
"kind": "autoclip",
"title": "Paid before the product",
"revision": 1,
"durationSec": 30,
"width": 1080,
"height": 1920,
"fps": 30,
"source": {
"assetId": "ast_3f9a1c7e5b2d4086a1c4",
"startSec": 120,
"endSec": 150
},
"hook": "They paid us before we had a product",
"captions": {
"style": "word-pop",
"wordCount": 75
},
"framing": {
"status": "centered",
"jobId": null
},
"createdAt": "2026-09-01T10:00:00.000Z",
"updatedAt": "2026-09-01T11:10:00.000Z"
},
"replaced": [
{
"from": "customer",
"count": 4
}
],
"notFound": []
}All response fields
| Name | Type | Required | Description |
|---|---|---|---|
composition | Composition object | Required | Described in Composition object. |
replaced | array of objects | Required | Every replacement that found its word, with how many words it changed. |
replaced[].from | string | Required | |
replaced[].count | integer | Required | |
notFound | array of objects | Required | Every replacement that found no word, with up to three similar words of the captions. |
notFound[].from | string | Required | |
notFound[].similar | array of strings | Required |
More examples
A word that is not in the captions
POST /v1/compositions/cmp_4e8c2a6f09d14b73a1e5/captions/replace
{
"expectedRevision": 0,
"replacements": [
{
"from": "costumer",
"to": "customer"
}
]
}{
"composition": {
"id": "cmp_4e8c2a6f09d14b73a1e5",
"projectId": "prj_7c2e9a4f1b3d4e8096a5",
"clipId": "cnd_9e04a6c3d12b4f78a5b9",
"kind": "autoclip",
"title": "Paid before the product",
"revision": 0,
"durationSec": 30,
"width": 1080,
"height": 1920,
"fps": 30,
"source": {
"assetId": "ast_3f9a1c7e5b2d4086a1c4",
"startSec": 120,
"endSec": 150
},
"hook": "They paid us before we had a product",
"captions": {
"style": "word-pop",
"wordCount": 75
},
"framing": {
"status": "centered",
"jobId": null
},
"createdAt": "2026-09-01T10:00:00.000Z",
"updatedAt": "2026-09-01T10:00:00.000Z"
},
"replaced": [],
"notFound": [
{
"from": "costumer",
"similar": [
"customer"
]
}
]
}A revision that is not the current one
POST /v1/compositions/cmp_4e8c2a6f09d14b73a1e5/captions/replace
{
"expectedRevision": 0,
"replacements": [
{
"from": "customer",
"to": "client"
}
]
}{
"error": {
"code": "CONFLICT",
"message": "This composition changed since revision 0; its revision is now 1.",
"hint": "Read the composition again with get_composition, then send the change with its current revision.",
"requestId": "req_5a9c2e7f1b3d48a6c0e4f2b8"
}
}A composition of a Vibe Editing project
POST /v1/compositions/cmp_cfbdd85eaaff301fa997/captions/replace
{
"expectedRevision": 0,
"replacements": [
{
"from": "launch",
"to": "Launch"
}
]
}{
"error": {
"code": "CONFLICT",
"message": "This composition belongs to a Vibe Editing project. Correct its captions with the replace_caption_word operation of apply_timeline_operations.",
"requestId": "req_9e4a1c7b3f5d42a8e6c0b2d4"
}
}Correct the captions of a composition that does not exist
POST /v1/compositions/cmp_7a1f5d3c9e0b42d8f6a2/captions/replace
{
"expectedRevision": 0,
"replacements": [
{
"from": "customer",
"to": "client"
}
]
}{
"error": {
"code": "NOT_FOUND",
"message": "Composition cmp_7a1f5d3c9e0b42d8f6a2 not found",
"hint": "Check the id. An id that belongs to another account is not found either.",
"requestId": "req_6b2d9f0e4a7c41b3d8e5a2f9"
}
}Errors
| Code | Status | When | What to do |
|---|---|---|---|
NOT_FOUND | 404 | Correct the captions of a composition that does not exist | Change the request, then send it again. |
CONFLICT | 409 | A revision that is not the current one | Read 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, IDEMPOTENCY_IN_PROGRESS, PAYLOAD_TOO_LARGE, IDEMPOTENCY_KEY_REUSED, RATE_LIMITED, INTERNAL, UNAVAILABLE.
Cost
No credits are used.
Related operations
get_composition_captions(GET /v1/compositions/{compositionId}/captions) Get the captions of a compositionedit_composition(POST /v1/compositions/{compositionId}/edit) Edit a compositionget_composition(GET /v1/compositions/{compositionId}) Get a composition
Questions
Can I fix a name that is split into two words?
Each replacement finds one word. Replace the first word with the whole name and the second with a word of its own, or change the video in the GPT-Video app.
What happens to the punctuation?
A word keeps its punctuation: replacing customer with client in «customer,» writes «client,». If the new text ends with punctuation of its own, that one is kept instead.