---
title: "Correct words in the captions | GPT-Video API reference"
description: "Replaces words in the captions of a composition, such as a name the transcript got wrong."
url: "https://www.gpt-video.com/developers/api/replace-caption-words"
operation: "replace_caption_words"
method: "POST"
path: "/v1/compositions/{compositionId}/captions/replace"
scope: "clips:write"
---

Do not assume endpoints have parameters other than the ones explicitly specified.

# Correct words in the captions

Operation id `replace_caption_words`. Request `POST /v1/compositions/{compositionId}/captions/replace`.

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

```text
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 `write` call. See [rate limits](/developers/rate-limits.md).
- `Idempotency-Key` is 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](/developers/pagination-and-idempotency.md). |

### 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.

```bash title="curl"
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"
      }
    ]
  }'
```

```ts title="TypeScript"
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);
```

```python title="Python"
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)
```

```json title="MCP (Full)"
{
  "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](/developers/objects.md#composition) 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

```json title="HTTP 200"
{
  "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](/developers/objects.md#composition). |
| `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`

```json title="Request body"
{
  "expectedRevision": 0,
  "replacements": [
    {
      "from": "costumer",
      "to": "customer"
    }
  ]
}
```

```json title="HTTP 200"
{
  "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`

```json title="Request body"
{
  "expectedRevision": 0,
  "replacements": [
    {
      "from": "customer",
      "to": "client"
    }
  ]
}
```

```json title="HTTP 409"
{
  "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`

```json title="Request body"
{
  "expectedRevision": 0,
  "replacements": [
    {
      "from": "launch",
      "to": "Launch"
    }
  ]
}
```

```json title="HTTP 409"
{
  "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`

```json title="Request body"
{
  "expectedRevision": 0,
  "replacements": [
    {
      "from": "customer",
      "to": "client"
    }
  ]
}
```

```json title="HTTP 404"
{
  "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`](/developers/errors.md#NOT_FOUND) | 404 | Correct the captions of a composition that does not exist | Change the request, then send it again. |
| [`CONFLICT`](/developers/errors.md#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`](/developers/errors.md#VALIDATION_FAILED), [`UNKNOWN_PARAMETER`](/developers/errors.md#UNKNOWN_PARAMETER), [`UNAUTHORIZED`](/developers/errors.md#UNAUTHORIZED), [`SCOPE_MISSING`](/developers/errors.md#SCOPE_MISSING), [`IDEMPOTENCY_IN_PROGRESS`](/developers/errors.md#IDEMPOTENCY_IN_PROGRESS), [`PAYLOAD_TOO_LARGE`](/developers/errors.md#PAYLOAD_TOO_LARGE), [`IDEMPOTENCY_KEY_REUSED`](/developers/errors.md#IDEMPOTENCY_KEY_REUSED), [`RATE_LIMITED`](/developers/errors.md#RATE_LIMITED), [`INTERNAL`](/developers/errors.md#INTERNAL), [`UNAVAILABLE`](/developers/errors.md#UNAVAILABLE).

## Cost

No credits are used.

## Related operations

- [`get_composition_captions`](/developers/api/get-composition-captions.md) (`GET /v1/compositions/{compositionId}/captions`) Get the captions of a composition
- [`edit_composition`](/developers/api/edit-composition.md) (`POST /v1/compositions/{compositionId}/edit`) Edit a composition
- [`get_composition`](/developers/api/get-composition.md) (`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.
