> ## Documentation Index
> Fetch the complete documentation index at: https://docs.overlap.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Edit Transcript

> Read a clip's word-level transcript and replace words or phrases (e.g. spelling and name fixes)

Correct words in a clip's transcript — typically misspellings or names the
speech-to-text got wrong (for example `Kaz Schwarz` → `Cozz`). Corrections flow
into the clip's subtitles. Read the transcript first to find what you want to
change, then send a patch.

<Info>
  Transcript edits do not re-export the video. After patching, call
  [`POST /render`](/api-reference/render) to produce a clip with the corrected
  subtitles. The cached `renderedUrl` is cleared automatically on every patch.
</Info>

### Authentication

```http theme={null}
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

***

## Read the transcript

```http theme={null}
GET https://api.joinoverlap.com/clip-transcript?companyId={companyId}&clipId={clipId}
```

Returns the word-level transcript. Use it to locate the words to fix and to read
their `index` and timestamps for precise targeting.

### Query Parameters

| Parameter   | Required | Description                                                  |
| ----------- | -------- | ------------------------------------------------------------ |
| `companyId` | Yes      | Your Overlap company or organization identifier.             |
| `clipId`    | Yes      | The clip id returned by `GET /workflow-results/{triggerId}`. |

<Info>
  Also available at `GET /companies/{companyId}/clips/{clipId}/transcript`.
</Info>

### Response

```json theme={null}
{
  "clipId": "clip-id-from-results",
  "source": "wordTranscriptJSON",
  "wordCount": 312,
  "words": [
    { "index": 86, "word": "Kaz",     "start": 12.50, "end": 12.80, "speaker": 0, "isDeleted": false, "visible": true },
    { "index": 87, "word": "Schwarz", "start": 12.80, "end": 13.20, "speaker": 0, "isDeleted": false, "visible": true },
    { "index": 88, "word": "went",    "start": 13.45, "end": 13.70, "speaker": 0, "isDeleted": false, "visible": true }
  ]
}
```

| Field           | Type    | Description                                                                    |
| --------------- | ------- | ------------------------------------------------------------------------------ |
| `index`         | number  | Stable position of the word in the transcript. Use it for `index` targeting.   |
| `word`          | string  | The word text (includes punctuation).                                          |
| `start` / `end` | number  | Word timing in **source-video seconds**. Use these for time-range targeting.   |
| `speaker`       | number  | Diarized speaker index (0-based), when available.                              |
| `isDeleted`     | boolean | Whether the word has been removed from captions.                               |
| `visible`       | boolean | Whether the word falls inside the clip's currently visible region (see below). |

***

## Patch the transcript

```http theme={null}
PATCH https://api.joinoverlap.com/clip-transcript
```

Send one or more **operations**. Each operation targets a span of words and
replaces it with the `to` phrase. There are three ways to target a span — pick
whichever is most convenient:

| Target by      | Fields               | Use when                                                                        |
| -------------- | -------------------- | ------------------------------------------------------------------------------- |
| **Text**       | `from`, `to`         | Fixing a known word/phrase everywhere it appears (best for spelling and names). |
| **Time range** | `start`, `end`, `to` | You know the seconds of the span (from the `GET` response).                     |
| **Index**      | `index`, `to`        | You want one specific word, by its `index` from the `GET` response.             |

```json theme={null}
{
  "companyId": "your-company-id",
  "clipId": "clip-id-from-results",
  "operations": [
    { "from": "Kaz Schwarz", "to": "Cozz" },
    { "start": 12.5, "end": 13.2, "to": "Cozz" },
    { "index": 86, "to": "Cozz" }
  ]
}
```

### Operation fields

| Field           | Type    | Notes                                                                                                                                |
| --------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `to`            | string  | The replacement text. Required. (`replacement` is accepted as an alias.)                                                             |
| `from`          | string  | Text to match. Whole-word, case-insensitive by default. Replaces **every** occurrence. May be multiple words (e.g. `"Kaz Schwarz"`). |
| `matchCase`     | boolean | Match `from` case-sensitively. Defaults to `false`.                                                                                  |
| `start` / `end` | number  | Source-video seconds. Selects words **overlapping** the range. Use the timestamps from the `GET` response.                           |
| `index`         | number  | A single word's `index` from the `GET` response.                                                                                     |

A span is collapsed into the replacement: the first word in the span becomes the
new text spanning the original span's timing, and the rest of the span is removed
from captions. Timing stays aligned to the audio, so the corrected text shows at
the right moment.

### Response

```json theme={null}
{
  "clipId": "clip-id-from-results",
  "status": "success",
  "applied": 3,
  "replaced": 4,
  "appliedToHiddenWords": 0,
  "warnings": [],
  "words": [ "...updated transcript..." ]
}
```

| Field                  | Type      | Description                                                                                        |
| ---------------------- | --------- | -------------------------------------------------------------------------------------------------- |
| `applied`              | number    | Operations processed.                                                                              |
| `replaced`             | number    | Spans actually replaced (an operation that matched nothing counts as applied with 0 replacements). |
| `appliedToHiddenWords` | number    | Replacements that landed on words outside the visible region (see below).                          |
| `warnings`             | string\[] | Non-fatal issues with individual operations.                                                       |
| `words`                | object\[] | The full updated transcript, in the same shape as the `GET` response.                              |

***

## Visible region vs extended region

A clip is transcribed from the **full extended source**, so the transcript can
include words in the padding that aren't shown in the current cut. The `visible`
flag on each word tells you whether it currently renders:

* Editing a **visible** word changes the subtitles as expected.
* Editing a **hidden** word (outside the visible region) is saved, but it won't
  appear unless the clip is extended in the studio to include that word. These
  show up as `appliedToHiddenWords` in the patch response.

For most spelling/name fixes you don't need to think about this — **text
targeting (`from`/`to`) replaces every matching word regardless of region**, and
the timestamps returned by `GET` already account for it.

***

## Errors

| Code                 | Status | Meaning                                                        |
| -------------------- | ------ | -------------------------------------------------------------- |
| `INVALID_OPERATIONS` | 400    | `operations` is missing/empty, or every operation was invalid. |
| `NO_TRANSCRIPT`      | 409    | The clip has no transcript yet (it may still be generating).   |
| `CLIP_NOT_FOUND`     | 404    | No clip for that `companyId` / `clipId`.                       |
| `INVALID_API_KEY`    | 401    | Missing or invalid API key.                                    |

<RequestExample>
  ```bash cURL theme={null}
  # 1. Read the transcript
  curl "https://api.joinoverlap.com/clip-transcript?companyId=$OVERLAP_COMPANY_ID&clipId=clip-id-from-results" \
       -H "Authorization: Bearer $OVERLAP_API_KEY"

  # 2. Fix a name everywhere it appears
  curl -X PATCH "https://api.joinoverlap.com/clip-transcript" \
       -H "Authorization: Bearer $OVERLAP_API_KEY" \
       -H "Content-Type: application/json" \
       -d '{
             "companyId": "'"$OVERLAP_COMPANY_ID"'",
             "clipId": "clip-id-from-results",
             "operations": [ { "from": "Kaz Schwarz", "to": "Cozz" } ]
           }'

  # 3. Re-render to bake the corrected subtitles into the export
  curl -X POST "https://api.joinoverlap.com/render" \
       -H "Authorization: Bearer $OVERLAP_API_KEY" \
       -H "Content-Type: application/json" \
       -d '{ "companyId": "'"$OVERLAP_COMPANY_ID"'", "clipId": "clip-id-from-results" }'
  ```

  ```javascript javascript theme={null}
  const base = "https://api.joinoverlap.com";
  const headers = {
    "Authorization": `Bearer ${process.env.OVERLAP_API_KEY}`,
    "Content-Type": "application/json",
  };
  const companyId = process.env.OVERLAP_COMPANY_ID;
  const clipId = "clip-id-from-results";

  // Replace a misspelled name everywhere, then re-render.
  await fetch(`${base}/clip-transcript`, {
    method: "PATCH",
    headers,
    body: JSON.stringify({
      companyId,
      clipId,
      operations: [{ from: "Kaz Schwarz", to: "Cozz" }],
    }),
  });

  await fetch(`${base}/render`, {
    method: "POST",
    headers,
    body: JSON.stringify({ companyId, clipId }),
  });
  ```
</RequestExample>

<Card title="Render Clip" icon="play" color="#ff2700" href="/api-reference/render">
  Export the clip with the corrected subtitles
</Card>
