> For the documentation index, fetch https://sync.so/docs/llms.txt. Append .md to a page URL for Markdown. Documentation-search MCP: https://sync.so/docs/_mcp/server.

# Transcribe a video for dialogue editing

POST https://api.sync.so/v2/transcriptions
Content-Type: application/json

Starts a word-level transcription of a window of a video, the first step of a dialogue edit. Provide exactly one of `sourceVideoUrl` or `sourceAudioUrl`, both hosted in sync. storage (upload via `POST /v2/assets/upload`). Passing pre-extracted audio skips the server-side download and transcode. Poll `GET /v2/transcriptions/{id}` until `status` is `COMPLETED`, then pass the `transcript` and your edits to `POST /v2/dialogue-edits`. An identical source and window returns the existing job instead of starting a new one. Transcription is not billed.

Reference: https://sync.so/docs/api-reference/api/transcriptions-api/create

## Authentication

- `x-api-key` header (required) — API Key authentication via header

## Servers

- `https://api.sync.so` (Default, default)
- `https://dev-api.sync.so` (dev)
- `http://localhost:3001` (local)

## Request

### Body (application/json)

This endpoint expects a CreateTranscriptionDto.

- `sourceVideoUrl` (string, optional) — URL of the source video, hosted in sync. storage (upload it via POST /v2/assets/upload first). Provide exactly one of sourceVideoUrl or sourceAudioUrl.
- `sourceAudioUrl` (string, optional) — URL of audio already extracted from the source, hosted in sync. storage. Skips the server-side audio extraction. Provide exactly one of sourceVideoUrl or sourceAudioUrl.
- `startMs` (integer, optional) — Start of the window to transcribe, in milliseconds from the start of the source. Defaults to 0.
- `endMs` (integer, optional) — End of the window to transcribe, in milliseconds from the start of the source. Omit to transcribe to the end. The window cannot exceed 60 minutes.
- `maxSourceSeconds` (integer, optional) — Refuse the source if it is longer than this many seconds. Useful when a later step, such as a dialogue edit (10 minute window), cannot consume a longer transcript. Cannot exceed the endpoint ceiling of 3600 seconds.
- `projectId` (string, optional) — Optional id of a project in your organization to file the job under.

## Response

### 201

Transcription job created successfully

- `id` (string, required) — Job id. Poll GET /v2/transcriptions/\{id} with it.
- `status` (enum, required) — Job status. COMPLETED and FAILED are terminal.
  - Allowed values: `PENDING`, `PROCESSING`, `COMPLETED`, `FAILED`
- `sourceStartMs` (integer, required) — Start of the transcribed window, in source milliseconds.
- `createdAt` (datetime, required) — When the job was created.
- `sourceVideoUrl` (string, optional) — The source video the job was created from. Null for jobs created from sourceAudioUrl.
- `sourceEndMs` (integer, optional) — End of the transcribed window, in source milliseconds. Null when the job transcribes to the end of the source.
- `transcript` (Transcript, optional) — Word-level transcript. Word timings are in milliseconds from the start of the source video, not the window. Null until status is COMPLETED.
- `speakerCount` (integer, optional) — Number of distinct speakers detected. Dialogue edits currently support a single speaker; a count above 1 completes the job but a dialogue edit of that source is unsupported. Null until status is COMPLETED.
- `difficulty` (TranscriptionDifficulty, optional) — Informational estimate of how hard the source is to edit cleanly, with the reasons behind it. Null while the job is running and for some older jobs. Do not gate behaviour on it.
- `error` (TranscriptionError, optional) — Why the job failed. Null unless status is FAILED.
- `startedAt` (datetime, optional) — When the job started processing. Null until it starts.
- `finishedAt` (datetime, optional) — When the job reached a terminal status. Null until it finishes.

## Errors

### 401 Unauthorized Error

Unauthorized - Invalid or missing authentication

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

### 402 Payment Required Error

Payment Required - Feature requires higher plan

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

### 422 Unprocessable Entity Error

Unprocessable Entity - The requested generation is not downloadable (generation_not_downloadable), or submit-time validation failed (e.g. inaccessible media, invalid segments, a projectId/voiceId/assetId that does not resolve in your organization, or a file exceeding the plan size limit). The body carries a stable errorCode and, where applicable, the failing field.

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

### 429 Too Many Requests Error

Too Many Requests - Batch concurrency limit reached

- `statusCode` (double, required) — The HTTP status code (429).
- `message` (GenerationErrorMessage, required) — A human-readable description of the error.
- `errorCode` (string, optional) — Machine-readable error code (`concurrency_limit_reached`).
- `activeBatches` (double, optional) — The number of batches you currently have in progress.
- `concurrencyLimit` (double, optional) — The maximum number of concurrent batches allowed on your plan.
- `retryAfterSeconds` (double, optional) — Suggested number of seconds to wait before retrying. Mirrors the `Retry-After` header.

### 500 Internal Server Error

Internal Server Error - An unexpected failure on our side. The body carries errorCode internal_error and a requestId; include the requestId when contacting support.

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

## Types

### Transcript

A word-level transcript. Word timings are in milliseconds from the start of the source video.

- `segments` (list of TranscriptSegment, required) — The transcript segments, in order.
- `speakerCount` (integer, required) — The number of distinct speakers detected.

### TranscriptionDifficulty

Informational estimate of how hard the source is to edit cleanly, with the reasons behind it. Do not gate behaviour on it.

- `tier` (enum, required) — The overall difficulty tier.
  - Allowed values: `green`, `yellow`, `red`
- `reasons` (list of enum, required) — The reasons behind the tier.
  - Allowed values: `MULTI_SPEAKER`, `LOW_ASR_CONFIDENCE`, `LOW_SPLICE_OPPORTUNITY`, `FAST_VARIABLE_RATE`, `LOW_AUDIO_QUALITY`, `VERY_SHORT_SOURCE`, `VERY_LONG_SOURCE`, `LOW_AUDIO_BANDWIDTH`

### TranscriptionError

Why a transcription job failed. Present only when status is FAILED.

- `code` (enum, required) — Machine-readable failure code. NO_AUDIO, NO_AUDIO_TRACK and SOURCE_TOO_LONG describe the source and will not succeed on retry.
  - Allowed values: `NO_AUDIO`, `NO_AUDIO_TRACK`, `SOURCE_TOO_LONG`, `EXTRACTION_FAILED`, `PROVIDER_FAILED`, `TIMEOUT`, `UNKNOWN`
- `message` (string, required) — Human-readable summary of the failure.

### GenerationErrorMessage

A message describing the error.

### DialogueEditRetimeFailureSection

The dialogue-edit section that removes too much speech to stay in sync.

- `slotIndex` (integer, required) — Zero-based index of the section in the preview's `resultSlots`.
- `sourceStartMs` (integer, optional) — Start of the section in the source video, in absolute milliseconds.
- `sourceDurationMs` (integer, optional) — Length of the section in the source video, in milliseconds.

### TranscriptSegment

A run of words attributed to one speaker turn.

- `id` (string, required) — The segment id.
- `words` (list of TranscriptWord, required) — The words in the segment, in order.

### TranscriptWord

A single transcribed word with its timing in the source.

- `id` (string, required) — The word id. Dialogue edits name words by this id.
- `text` (string, required) — The transcribed text of the word.
- `startMs` (integer, required) — Start of the word, in milliseconds from the start of the source.
- `endMs` (integer, required) — End of the word, in milliseconds from the start of the source.

## Examples

**Request**

```json
{
  "sourceVideoUrl": "https://assets.sync.so/docs/example-video.mp4",
  "endMs": 30000
}
```

**Response**

```json
{
  "id": "6533643b-aceb-4c40-967e-d9ba9baac39e",
  "status": "PENDING",
  "sourceStartMs": 0,
  "createdAt": "2024-01-15T09:30:00Z",
  "sourceVideoUrl": "https://assets.sync.so/docs/example-video.mp4",
  "sourceEndMs": 30000
}
```

**SDK Code**

```python
import requests

url = "https://api.sync.so/v2/transcriptions"

payload = {
    "sourceVideoUrl": "https://assets.sync.so/docs/example-video.mp4",
    "endMs": 30000
}
headers = {
    "x-api-key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.sync.so/v2/transcriptions';
const options = {
  method: 'POST',
  headers: {'x-api-key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"sourceVideoUrl":"https://assets.sync.so/docs/example-video.mp4","endMs":30000}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.sync.so/v2/transcriptions"

	payload := strings.NewReader("{\n  \"sourceVideoUrl\": \"https://assets.sync.so/docs/example-video.mp4\",\n  \"endMs\": 30000\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("x-api-key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.sync.so/v2/transcriptions")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"sourceVideoUrl\": \"https://assets.sync.so/docs/example-video.mp4\",\n  \"endMs\": 30000\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.sync.so/v2/transcriptions")
  .header("x-api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"sourceVideoUrl\": \"https://assets.sync.so/docs/example-video.mp4\",\n  \"endMs\": 30000\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.sync.so/v2/transcriptions', [
  'body' => '{
  "sourceVideoUrl": "https://assets.sync.so/docs/example-video.mp4",
  "endMs": 30000
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.sync.so/v2/transcriptions");
var request = new RestRequest(Method.POST);
request.AddHeader("x-api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"sourceVideoUrl\": \"https://assets.sync.so/docs/example-video.mp4\",\n  \"endMs\": 30000\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "x-api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "sourceVideoUrl": "https://assets.sync.so/docs/example-video.mp4",
  "endMs": 30000
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.sync.so/v2/transcriptions")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```