---
title: Transcript API  Documentation
description: Submit media URLs, track asynchronous transcription tasks, and retrieve structured transcripts, chapters, translations, SRT, and VTT files.
version: V1
base_url: https://videotranscriber.ai/openapi/v1
---

<!-- Generated from content/api-docs/index.md. Edit the source document instead. -->

# Transcript API 

Submit a public media URL, track the asynchronous task, and retrieve structured transcripts, chapters, translations, SRT, and VTT files through one predictable API.

`REST + JSON` · `UTF-8` · `UTC / ISO 8601` · `200+ languages`

**Base URL**

`https://videotranscriber.ai/openapi/v1`


**Using an AI agent?**

Connect your AI client through the [VideoTranscriber MCP guide](#mcp-overview), then install the [transcribe-media Agent Skill](#agent-skill-overview) for a guided transcription workflow.


## Quickstart

A transcription request follows three steps. The API returns immediately while processing continues asynchronously.

1. Create a task with `POST /transcriptions`.
2. Poll the returned `poll_url` and honor `retry_after`.
3. Retrieve structured results and short-lived subtitle download URLs.

**Asynchronous processing**

Creating a transcription does not wait for the media to finish processing. Store the returned request ID and poll until the task reaches a terminal state.


## Authentication

Sign in to the website and create an API Key in the API console before calling OpenAPI. Every OpenAPI request uses a Bearer API Key. Website cookies and login tokens are not accepted.

```http [HTTP header]
Authorization: Bearer $VT_API_KEY
```

| Header | Required | Description |
| --- | --- | --- |
| `Authorization` | Yes | Bearer API Key. |
| `Content-Type` | For POST | Must be `application/json`. |
| `Idempotency-Key` | For POST | Stable identifier for one logical create operation. |
| `X-Request-ID` | No | Client trace identifier echoed or generated by the server. |

**Keep API keys private**

Never expose an API Key in browser-side code, public repositories, logs, screenshots, or support messages.


**Trial quota**

Your first successful API Key creation grants 1,500 API Quota, limited to once per user and once per IP. The IP limit only affects the gift; it does not prevent key creation. API Quota does not expire, and gifted quota is consumed before purchased quota. Website credits and API Quota are separate balances.


## Idempotency

Use a stable `Idempotency-Key` for every logical create operation. This prevents network retries from creating duplicate billable tasks.

Keys are scoped to the API account and must contain 1–128 visible ASCII characters without spaces. Use a new key when changing the model, options, or requested features. Omitting the model and explicitly selecting `basic` remain compatible.

| Request | Result |
| --- | --- |
| Same key and same body | Returns the original task with HTTP 200. |
| Same key and different body | Returns HTTP 409 `idempotency_conflict`. |

## Sources and formats

Provide a public HTTP or HTTPS URL. File upload is not supported by OpenAPI. Vimeo platform URLs are not supported.

| Source | Supported examples |
| --- | --- |
| Platforms | YouTube, TikTok, Instagram, Facebook, X, Bilibili |
| Cloud links | Google Drive, Dropbox |
| Direct files | MP3, MP4, M4A, WAV, WebM, MOV, AVI, OGG, AAC |

**The URL must be reachable**

The media URL must be accessible without an interactive login, expiring browser session, or private network connection.


## Features and billing

Usage is calculated from rounded-up media duration, with a minimum of one billable minute:

`billable_minutes = max(1, ceil(duration_seconds / 60))`

| Features | Description | Quota Usage |
| --- | --- | --- |
| Transcribe (Basic Model) | Full text, detected language, timed segments, and optional speakers. | 1 API Quota / 1 Minute |
| Transcribe (Advanced Model) | Full text, detected language, word-level timestamps, and optional speakers. | 3 API Quota / 1 Minute |
| YouTube,<br>TikTok,<br>Instagram,<br>Facebook,<br>X,<br>Bilibili | Transcribe media directly from supported platform URLs. | Free (Limited Time Only) |
| Speaker Diarization | Identify and label different speakers in the transcript. | Free (Limited Time Only) |
| Language Detection | Automatically detect the source language. | Free (Limited Time Only) |
| Timestamps | Include timed segments for navigation and subtitle synchronization. | Free (Limited Time Only) |
| Subtitle (SRT/VTT) | Generate signed download URLs for SRT and VTT subtitle files. | Free (Limited Time Only) |
| Dynamic Audio Tags (Advanced Model) | Auto-detected non-speech cues, sound events and inline audio markers. | Free (Limited Time Only) |
| Smart Cleanup of Filler & Repeats (Advanced Model) | Auto-removed filler words, redundant repeats and verbal disfluencies. | Free (Limited Time Only) |
| Chapters | Generate ordered chapter ranges with titles and summaries. | 1 API Quota / 1 Minute |
| Translation | Generate translated full text and aligned segments. | 1 API Quota / 1 Minute |
| Keyterm Prompting (Advanced Model) | Custom keyterms for guided transcription and improved accuracy. | 1 API Quota / 1 Minute |

For example, a 10-minute recording with advanced transcription, non-empty Keyterms, chapters, and translation costs `10 × (3 + 1 + 1 + 1) = 60 API Quota` when all features succeed. Dynamic Audio Tags and Smart Cleanup do not change the cost.

Quota is reserved from the resolved media duration when processing starts. Successful features are charged, and reservations for features that finally fail or are cancelled are released. Partial success charges only successful features. Submission checks only the minimum required balance; a task can still fail if the balance cannot cover its resolved duration.

## Task lifecycle

- `queued`
- `processing`
- `succeeded`
- `partial_succeeded`
- `failed`
- `cancelled`

**Polling**

Use `retry_after` from the response or `Retry-After` from response headers. Do not poll in a tight loop.


## Create transcription

`POST /transcriptions`

Create an asynchronous transcription task from a public media URL.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source_url` | string | Yes | Public HTTP/HTTPS media or supported platform URL; up to 4,096 characters. |
| `language` | string | No | Defaults to `auto`; may be a source language code such as `en` or `zh`. |
| `speaker_diarization` | boolean | No | Defaults to `false`. Enable speaker detection. |
| `transcription_model` | string | No | `basic` or `advanced`; defaults to `basic`. |
| `include_audio_events` | boolean | No | Defaults to `false`. Enable Dynamic Audio Tags. Advanced model only. |
| `cleanup_speech` | boolean | No | Defaults to `false`. Enable Smart Cleanup. Advanced model only. |
| `keyterms` | string[] | No | Defaults to `[]`. Improve terminology recognition. Advanced model only; a non-empty list adds a charge. |
| `features.chapters` | boolean | No | Defaults to `false`. Generate chapters. |
| `features.translation` | object | No | Enable translation when present. |
| `features.translation.target_language` | string | For translation | Target language code such as `es` or `zh-CN`; up to 32 characters. |

The basic model cannot enable `include_audio_events`, `cleanup_speech`, or non-empty `keyterms`; these combinations return HTTP 400 `invalid_request`. The request body and nested `features` and `translation` objects reject unknown fields.

Keyterms accepts up to 1,000 non-empty strings. Each term may contain at most 50 characters and five whitespace-separated words. The characters `<`, `>`, `{`, `}`, `[`, `]`, `\`, `,`, and `，` are not allowed. The server trims and collapses whitespace and removes duplicates without regard to case.

All language examples below use the advanced model with sound events, speech cleanup, Keyterms, chapters, and Spanish translation. For basic transcription, submit only `source_url` and use the other fields' defaults.

**cURL**

```bash [cURL]
curl --request POST "https://videotranscriber.ai/openapi/v1/transcriptions" \
  --header "Authorization: Bearer $VT_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: transcribe-order-0001" \
  --data '{
    "source_url": "https://example.com/media/demo.mp4",
    "language": "auto",
    "speaker_diarization": true,
    "transcription_model": "advanced",
    "include_audio_events": true,
    "cleanup_speech": true,
    "keyterms": ["VideoTranscriber", "API"],
    "features": {
      "chapters": true,
      "translation": {"target_language": "es"}
    }
  }'
```

**Python**

```python [Python]
import os
import requests

response = requests.post(
    "https://videotranscriber.ai/openapi/v1/transcriptions",
    headers={
        "Authorization": f"Bearer {os.environ['VT_API_KEY']}",
        "Idempotency-Key": "transcribe-order-0001",
    },
    json={
        "source_url": "https://example.com/media/demo.mp4",
        "language": "auto",
        "speaker_diarization": True,
        "transcription_model": "advanced",
        "include_audio_events": True,
        "cleanup_speech": True,
        "keyterms": ["VideoTranscriber", "API"],
        "features": {"chapters": True, "translation": {"target_language": "es"}},
    },
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

**Go**

```go [Go]
package main

import (
  "bytes"
  "fmt"
  "io"
  "net/http"
  "os"
)

func main() {
  body := []byte(`
    {
      "source_url": "https://example.com/media/demo.mp4",
      "language": "auto",
      "speaker_diarization": true,
      "transcription_model": "advanced",
      "include_audio_events": true,
      "cleanup_speech": true,
      "keyterms": [
        "VideoTranscriber",
        "API"
      ],
      "features": {
        "chapters": true,
        "translation": {
          "target_language": "es"
        }
      }
    }
  `)
  req, _ := http.NewRequest("POST", "https://videotranscriber.ai/openapi/v1/transcriptions", bytes.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("VT_API_KEY"))
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("Idempotency-Key", "transcribe-order-0001")
  res, err := http.DefaultClient.Do(req)
  if err != nil { panic(err) }
  defer res.Body.Close()
  data, _ := io.ReadAll(res.Body)
  fmt.Println(string(data))
}
```

**Ruby**

```ruby [Ruby]
require "json"
require "net/http"
require "uri"

uri = URI("https://videotranscriber.ai/openapi/v1/transcriptions")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('VT_API_KEY')}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = "transcribe-order-0001"
request.body = {
  source_url: "https://example.com/media/demo.mp4",
  language: "auto",
  speaker_diarization: true,
  transcription_model: "advanced",
  include_audio_events: true,
  cleanup_speech: true,
  keyterms: ["VideoTranscriber", "API"],
  features: {chapters: true, translation: {target_language: "es"}}
}.to_json

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
puts response.body
```

**PHP**

```php [PHP]
<?php
$payload = [
  "source_url" => "https://example.com/media/demo.mp4",
  "language" => "auto",
  "speaker_diarization" => true,
  "transcription_model" => "advanced",
  "include_audio_events" => true,
  "cleanup_speech" => true,
  "keyterms" => ["VideoTranscriber", "API"],
  "features" => [
    "chapters" => true,
    "translation" => ["target_language" => "es"]
  ]
];

$ch = curl_init("https://videotranscriber.ai/openapi/v1/transcriptions");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("VT_API_KEY"),
    "Content-Type: application/json",
    "Idempotency-Key: transcribe-order-0001"
  ],
  CURLOPT_POSTFIELDS => json_encode($payload)
]);
echo curl_exec($ch);
curl_close($ch);
```

**Java**

```java [Java]
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class CreateTranscription {
  public static void main(String[] args) throws Exception {
    String body = """
      {
        "source_url": "https://example.com/media/demo.mp4",
        "language": "auto",
        "speaker_diarization": true,
        "transcription_model": "advanced",
        "include_audio_events": true,
        "cleanup_speech": true,
        "keyterms": [
          "VideoTranscriber",
          "API"
        ],
        "features": {
          "chapters": true,
          "translation": {
            "target_language": "es"
          }
        }
      }
      """;
    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://videotranscriber.ai/openapi/v1/transcriptions"))
      .header("Authorization", "Bearer " + System.getenv("VT_API_KEY"))
      .header("Content-Type", "application/json")
      .header("Idempotency-Key", "transcribe-order-0001")
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();
    var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
    System.out.println(response.body());
  }
}
```


```json [Response]
{
  "id": "req_example_001",
  "object": "transcription",
  "status": "queued",
  "created_at": "2026-10-10T00:00:00Z",
  "transcription_model": "advanced",
  "poll_url": "/openapi/v1/transcriptions/req_example_001",
  "retry_after": 3
}
```

A new task returns HTTP 202. Reusing the same idempotency key and body returns the original task with HTTP 200. Store the response `id` for subsequent calls. Resolve `poll_url` and `result_url` against `https://videotranscriber.ai`; they already include `/openapi/v1` and must not be prefixed with the base path again.

## Get transcription

`GET /transcriptions/{request_id}`

Get task status, feature-level status, usage, errors, and result availability.

**cURL**

```bash [cURL]
curl --request GET \
  "https://videotranscriber.ai/openapi/v1/transcriptions/$REQUEST_ID" \
  --header "Authorization: Bearer $VT_API_KEY"
```

**Python**

```python [Python]
import os
import requests

request_id = os.environ["REQUEST_ID"]
response = requests.get(
    f"https://videotranscriber.ai/openapi/v1/transcriptions/{request_id}",
    headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

**Go**

```go [Go]
req, _ := http.NewRequest("GET", "https://videotranscriber.ai/openapi/v1/transcriptions/"+os.Getenv("REQUEST_ID"), nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("VT_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
```

**Ruby**

```ruby [Ruby]
uri = URI("https://videotranscriber.ai/openapi/v1/transcriptions/#{ENV.fetch('REQUEST_ID')}")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('VT_API_KEY')}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
puts response.body
```

**PHP**

```php [PHP]
<?php
$url = "https://videotranscriber.ai/openapi/v1/transcriptions/" . getenv("REQUEST_ID");
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("VT_API_KEY")]
]);
echo curl_exec($ch);
curl_close($ch);
```

**Java**

```java [Java]
String requestId = System.getenv("REQUEST_ID");
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://videotranscriber.ai/openapi/v1/transcriptions/" + requestId))
  .header("Authorization", "Bearer " + System.getenv("VT_API_KEY"))
  .GET()
  .build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```


`transcription_model` identifies the model used for this task. `usage.reserved_quotas` is the current unsettled reservation, normally `0` after settlement; `usage.used_quotas` is the amount actually charged. Each feature also reports these fields.

Non-terminal responses include `retry_after`. A response with `result_available=true` includes a `result_url` that can be fetched. `partial_succeeded` means some features succeeded. Stop polling at a terminal state and inspect the task and feature `error` values for failures.

This example shows a 45-second recording with the advanced model and all options above. All features succeeded, for a total of 6 Quota.

```json [Response]
{
  "id": "req_example_001",
  "object": "transcription",
  "status": "succeeded",
  "created_at": "2026-10-10T00:00:00Z",
  "transcription_model": "advanced",
  "started_at": "2026-10-10T00:00:03Z",
  "finished_at": "2026-10-10T00:00:10Z",
  "duration_seconds": 45.0,
  "billable_minutes": 1,
  "usage": {
    "reserved_quotas": 0,
    "used_quotas": 6
  },
  "features": [
    {
      "type": "transcript",
      "status": "succeeded",
      "target_language": null,
      "reserved_quotas": 0,
      "used_quotas": 4,
      "error": null
    },
    {
      "type": "chapters",
      "status": "succeeded",
      "target_language": null,
      "reserved_quotas": 0,
      "used_quotas": 1,
      "error": null
    },
    {
      "type": "translation",
      "status": "succeeded",
      "target_language": "es",
      "reserved_quotas": 0,
      "used_quotas": 1,
      "error": null
    }
  ],
  "result_available": true,
  "result_expires_at": "2026-11-09T00:00:10Z",
  "error": null,
  "result_url": "/openapi/v1/transcriptions/req_example_001/result"
}
```

## Get result

`GET /transcriptions/{request_id}/result`

Retrieve structured results for successful features and signed download URLs for SRT and VTT files.

**cURL**

```bash [cURL]
curl --request GET \
  "https://videotranscriber.ai/openapi/v1/transcriptions/$REQUEST_ID/result" \
  --header "Authorization: Bearer $VT_API_KEY"
```

**Python**

```python [Python]
import os
import requests

request_id = os.environ["REQUEST_ID"]
response = requests.get(
    f"https://videotranscriber.ai/openapi/v1/transcriptions/{request_id}/result",
    headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

**Go**

```go [Go]
req, _ := http.NewRequest("GET", "https://videotranscriber.ai/openapi/v1/transcriptions/"+os.Getenv("REQUEST_ID")+"/result", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("VT_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
```

**Ruby**

```ruby [Ruby]
uri = URI("https://videotranscriber.ai/openapi/v1/transcriptions/#{ENV.fetch('REQUEST_ID')}/result")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('VT_API_KEY')}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
puts response.body
```

**PHP**

```php [PHP]
<?php
$url = "https://videotranscriber.ai/openapi/v1/transcriptions/" . getenv("REQUEST_ID") . "/result";
$ch = curl_init($url);
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("VT_API_KEY")]
]);
echo curl_exec($ch);
curl_close($ch);
```

**Java**

```java [Java]
String requestId = System.getenv("REQUEST_ID");
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://videotranscriber.ai/openapi/v1/transcriptions/" + requestId + "/result"))
  .header("Authorization", "Bearer " + System.getenv("VT_API_KEY"))
  .GET()
  .build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```


**Partial success**

Only results from successful features are returned. Features that were not requested or failed are `null`. Inspect the task status endpoint for feature errors.


An unavailable result returns HTTP 409 `result_not_ready`; an expired result returns HTTP 410 `result_expired`. If a download URL expires, call this endpoint again during the result retention period for a new URL. The download URLs below are illustrative.

```json [Response]
{
  "id": "req_example_001",
  "status": "succeeded",
  "result_expires_at": "2026-11-09T00:00:10Z",
  "transcript": {
    "language": "en",
    "duration_seconds": 45.0,
    "text": "Welcome to VideoTranscriber.",
    "segments": [
      {
        "id": 0,
        "start": 0.0,
        "end": 3.2,
        "text": "Welcome to VideoTranscriber.",
        "speaker": "speaker_0"
      }
    ],
    "audio_events": [
      {
        "start": 3.2,
        "end": 3.8,
        "text": "(laughter)"
      }
    ]
  },
  "chapters": [
    {
      "id": 0,
      "start": 0.0,
      "end": 45.0,
      "title": "Introduction",
      "summary": "An introduction to VideoTranscriber."
    }
  ],
  "translation": {
    "source_language": "en",
    "target_language": "es",
    "text": "Bienvenido a VideoTranscriber.",
    "segments": [
      {
        "source_segment_id": 0,
        "start": 0.0,
        "end": 3.2,
        "text": "Bienvenido a VideoTranscriber.",
        "speaker": "speaker_0"
      }
    ]
  },
  "files": {
    "subtitles": {
      "srt": {
        "url": "https://example.com/signed/subtitles.srt",
        "url_expires_at": "2026-10-10T01:00:10Z"
      },
      "vtt": {
        "url": "https://example.com/signed/subtitles.vtt",
        "url_expires_at": "2026-10-10T01:00:10Z"
      }
    },
    "translated_subtitles": {
      "srt": {
        "url": "https://example.com/signed/translation_es.srt",
        "url_expires_at": "2026-10-10T01:00:10Z"
      },
      "vtt": {
        "url": "https://example.com/signed/translation_es.vtt",
        "url_expires_at": "2026-10-10T01:00:10Z"
      },
      "target_language": "es"
    }
  }
}
```

## Response schemas

JSON is UTF-8. Timeline values use seconds and may contain decimals. Clients must ignore unknown response fields.

| Schema | Contents |
| --- | --- |
| Transcript | Detected language, full text, duration, timed segments, optional speakers, and `audio_events`. |
| Chapters | Ordered ranges with start, end, title, and summary. |
| Translation | Source and target language, translated text, and aligned segments. |

The `speaker` field is `null` when diarization is disabled or the provider does not return a reliable speaker label.

`transcript.audio_events` is an array of sound events, each with `start`, `end` in seconds and a `text` description such as laughter or applause. Events are separate from `transcript.text` and speech `segments`. It is `[]` for basic transcription, when events are disabled, or when none are detected. Treat a missing field in older results as `[]`.

`chapters` is an array. A translated segment's `source_segment_id` refers to the original transcript segment's `id`. SRT/VTT entries in `files.subtitles` and `files.translated_subtitles` contain `url` and `url_expires_at`; translated subtitles also include `target_language`. File groups are omitted when no corresponding files exist.

## Errors

Request-level failures use HTTP status codes and a stable English `error.code`. Do not branch on `error.message`.

```json [Error response]
{
  "error": {
    "code": "invalid_request",
    "message": "The request parameters are invalid.",
    "request_id": "vt_example_trace_001",
    "retryable": false,
    "details": {
      "cleanup_speech": [
        "This feature requires the advanced model."
      ]
    }
  }
}
```

| HTTP status | error.code | Action |
| --- | --- | --- |
| 400 | `invalid_request` | Check field errors in `details`, including advanced options used with the basic model. |
| 400 | `invalid_idempotency_key` | Supply a correctly formatted `Idempotency-Key`. |
| 400 | `unsupported_source_url` | Use a supported public HTTP/HTTPS source. |
| 401 | `invalid_api_key` / `api_key_revoked` | Check or replace the API Key. |
| 402 | `insufficient_quota` | Add API Quota to cover the request. |
| 403 | `api_account_disabled` | The API account is unavailable. |
| 404 | `request_not_found` | The task does not exist, belongs to another account, or has been removed. |
| 409 | `idempotency_conflict` | Use a new idempotency key when changing the body. |
| 409 | `result_not_ready` | Poll task status and fetch the result once available. |
| 410 | `result_expired` | The result is past its retention period. |
| 429 | `rate_limit_exceeded` / `concurrency_limit_exceeded` | Reduce request frequency or wait for active tasks to finish. |
| 500 | `internal_error` | Use `retryable` to decide whether to retry; retain the trace identifier. |

`error.request_id` is a trace identifier, separate from the transcription task `id`. `details` contains field errors or is `null` when no details are available.

Handle HTTP errors and task failures separately. A successful status query returns HTTP 200 even when the task is `failed` or `partial_succeeded`. Check task `error` and `features[].error`, which contain `code`, `message`, and `retryable`. Examples include `task_timeout`, `provider_unavailable`, `transcription_failed`, and `no_speech_detected`.

## Limits and retries

Honor rate-limit and retry headers returned by the API:

- `X-RateLimit-Limit`
- `X-RateLimit-Remaining`
- `X-RateLimit-Reset`
- `Retry-After`

**Retryable errors**

If submission times out or acceptance is uncertain, retry with the original `Idempotency-Key` and body to avoid duplicate tasks. Use a new key only to intentionally rerun a failed task; reusing the original key returns the original task. For HTTP 429, honor `Retry-After` before retrying.


## Retention and security

| Resource | Retention |
| --- | --- |
| Results | Available for 30 days from result generation. |
| Download URLs | Private, signed, and valid for up to one hour. |
| Task records | Kept for the creation month and the following calendar month in UTC. |

**Support requests**

Provide the transcription ID and `X-Request-ID`. Never send a complete API Key, Authorization header, source URL with credentials, or signed result URL.


<a id="mcp-overview"></a>

## VideoTranscriber MCP

Connect an AI client to VideoTranscriber to transcribe public media URLs, generate chapters or translations, and retrieve transcript and subtitle results.

`Streamable HTTP` · `Bearer API Key` · `Asynchronous transcription`

**MCP endpoint**

`https://videotranscriber.ai/mcp`


<a id="mcp-guide-before-connecting"></a>

### Before connecting

1. Sign in to VideoTranscriber and create an API Key in the API console.
2. Use an AI client that supports remote Streamable HTTP MCP and an Authorization header.
3. Check that your API account has enough API Quota for the requested model and features.

The MCP connection uses the same API Key and API Quota balance as OpenAPI. Website login tokens and cookies are not accepted. Authentication uses a Bearer API Key; the VS endpoint does not provide an OAuth login flow.

<a id="mcp-guide-authentication"></a>

### MCP authentication

Configure the credential in your client's MCP connection settings:

```http [Authorization header]
Authorization: Bearer YOUR_VS_API_KEY
```

Replace `YOUR_VS_API_KEY` with the Key created in the API console. Credentials belong in connection settings, never in tool arguments or chat messages.

<a id="mcp-guide-connect-with-codex"></a>

### Connect with Codex

Set the Key in the environment of the process that launches Codex:

```bash [Environment]
export VT_API_KEY="YOUR_VS_API_KEY"
```

Add the following entry to `~/.codex/config.toml`. The snippet is TOML configuration:

```text [config.toml]
[mcp_servers.videotranscriber]
url = "https://videotranscriber.ai/mcp"
bearer_token_env_var = "VT_API_KEY"
```

Run `codex mcp list`, then open `/mcp` in a Codex CLI session to check the connection and available tools.

The desktop app's process must also receive `VT_API_KEY` to use this configuration. Exporting a variable in a terminal applies to processes launched from that terminal. Clients with a header configuration UI can instead set `Authorization: Bearer YOUR_VS_API_KEY` there.

See [OpenAI's MCP documentation](https://developers.openai.com/codex/mcp) for client configuration options.

<a id="mcp-guide-connect-with-claude-code"></a>

### Connect with Claude Code

Set `VT_API_KEY` in your terminal as above, then register the remote service:

```bash [Claude Code]
claude mcp add --transport http --scope user videotranscriber \
  https://videotranscriber.ai/mcp \
  --header "Authorization: Bearer $VT_API_KEY"

claude mcp get videotranscriber
```

Open `/mcp` in Claude Code to check the connection and tool list.

For clients that use an `mcpServers` JSON configuration, the HTTP connection entry is:

```json [HTTP configuration]
{
  "mcpServers": {
    "videotranscriber": {
      "type": "http",
      "url": "https://videotranscriber.ai/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_VS_API_KEY"
      }
    }
  }
}
```

Configuration file locations vary by client. Use the client's documented HTTP configuration format. See [Claude Code's MCP documentation](https://code.claude.com/docs/en/mcp).

<a id="mcp-guide-available-tools"></a>

### Available tools

<a id="mcp-guide-create-transcription"></a>

#### MCP tool: create_transcription

Creates an asynchronous task. It returns a task ID immediately; the transcript is retrieved after processing.

```text [Arguments]
source_url: required string; public HTTP/HTTPS media URL, up to 4,096 characters.
idempotency_key: required string; 1–128 visible ASCII characters without spaces.
language: optional string; defaults to "auto".
transcription_model: "basic" or "advanced"; defaults to "basic".
speaker_diarization: optional boolean; defaults to false.
include_audio_events: optional boolean; defaults to false; advanced model only.
cleanup_speech: optional boolean; defaults to false; advanced model only.
keyterms: optional string array; defaults to an empty list; advanced model only.
chapters: optional boolean; defaults to false.
target_language: optional language code; enables translation when supplied.
```

MCP uses top-level `chapters` and `target_language` arguments. The server maps them to OpenAPI's `features.chapters` and `features.translation.target_language`.

```json [Advanced task arguments]
{
  "source_url": "https://example.com/media/demo.mp4",
  "idempotency_key": "vs-transcription-001",
  "language": "auto",
  "transcription_model": "advanced",
  "speaker_diarization": true,
  "include_audio_events": true,
  "cleanup_speech": true,
  "keyterms": ["VideoTranscriber", "API"],
  "chapters": true,
  "target_language": "es"
}
```

The JSON above is the tool's arguments, not an HTTP body to post directly to the MCP endpoint. Your MCP client handles the protocol.

Generate one `idempotency_key` for each intended task and retain it for retries. Changing the model, options, or features requires a new key. Keyterms accepts up to 1,000 non-empty strings, each up to 50 characters and five words; the same validation rules as OpenAPI apply.

<a id="mcp-guide-get-transcription"></a>

#### MCP tool: get_transcription

Queries the task status, per-feature status, quota usage, errors, and result availability.

```json [Arguments]
{
  "request_id": "req_0123456789abcdef0123456789abcdef"
}
```

Use the `id` returned by `create_transcription` as `request_id`. Task IDs contain `req_` followed by 32 lowercase hexadecimal characters.

<a id="mcp-guide-get-transcription-result"></a>

#### MCP tool: get_transcription_result

Retrieves available transcript, chapters, translation, and subtitle download links.

```json [Arguments]
{
  "request_id": "req_0123456789abcdef0123456789abcdef"
}
```

Call this tool when `get_transcription` returns `result_available=true`. The data fields follow the [OpenAPI result schema](#get-result), including `transcript.audio_events` for sound events.

<a id="mcp-guide-task-workflow"></a>

### Task workflow

1. Call `create_transcription` once and save the response `id`.
2. Poll `get_transcription` using `retry_after`; avoid polling continuously.
3. Stop polling at `succeeded`, `partial_succeeded`, `failed`, or `cancelled`.
4. If `result_available=true`, call `get_transcription_result`.
5. For partial success, retrieve successful results and inspect `features[].error` for failures.

Tool results expose OpenAPI data through `structuredContent` and JSON text content. They also carry `_meta.http_status` and, when present, `_meta.retry_after`. A failed tool call has `isError=true`. Client UIs may display only the JSON content.

A submission timeout does not establish that creation failed. Retry with the same `idempotency_key` and arguments. A deliberate new attempt for a failed task uses a new key and creates a separate billable task. OpenAPI does not expose a task cancellation operation.

<a id="mcp-guide-usage-examples"></a>

### Usage examples

```text [Basic transcription]
Transcribe https://example.com/media/demo.mp4 using the basic model.
Create the task once, wait for it to finish, then return the transcript and SRT download link.
```

```text [Advanced transcription]
Transcribe https://example.com/media/demo.mp4 using the advanced model.
Enable speaker diarization, Dynamic Audio Tags, and Smart Cleanup.
Use VideoTranscriber and API as keyterms, generate chapters, and translate to Spanish.
Wait for the task to finish and report both the results and actual Quota usage.
```

<a id="mcp-guide-billing-and-result-retention"></a>

### Billing and result retention

Successful features use the same rates as OpenAPI: basic transcription costs 1 Quota per minute, advanced costs 3, non-empty Keyterms adds 1, and chapters and translation each add 1. Dynamic Audio Tags and Smart Cleanup do not add a charge. Duration rounds up to whole minutes, with a one-minute minimum.

Failed features release their reservations; partial success charges only successful features. In status responses, `reserved_quotas` is the current unsettled reservation and `used_quotas` is the amount actually charged.

Results are retained for 30 days and signed download URLs last up to one hour. Call the result tool again during retention to refresh expired download URLs. See [features and billing](#features-and-billing).

<a id="mcp-guide-troubleshooting"></a>

### Troubleshooting

- `invalid_api_key` or `api_key_revoked`: check the configured Key and whether the client can read its environment variable.
- `api_account_disabled`: the API account is unavailable.
- `authentication_unavailable`: retry connecting after the service becomes available.
- `insufficient_quota`: add API Quota before submitting another task.
- `rate_limit_exceeded` or `concurrency_limit_exceeded`: reduce request frequency or wait for active tasks to finish.
- `idempotency_conflict`: the key was reused with different arguments.
- `result_not_ready`: query task status and wait for `result_available=true`.
- `result_expired`: the result is past its retention period.
- A missing tool list: check the URL, HTTP transport, client permissions, and authentication settings.

Request failures and asynchronous task failures are separate. A status query can succeed while the task is `failed`. Inspect task `error` and `features[].error`. Never paste a complete API Key or signed download URL into a support request.

To use the same workflow through an installed Skill, see the [Agent Skill guide](#agent-skill-overview).

<a id="agent-skill-overview"></a>

## Transcribe Media Agent Skill

Install the official `transcribe-media` Skill to guide an AI client through creating a Video Transcriber AI task, waiting for completion, and retrieving transcripts and subtitle links.

`Agent Skill` · `Video Transcriber AI MCP` · `Public media URLs`

**Skill and MCP are configured separately**

The Skill provides the workflow instructions. The MCP connection provides the tools and API credential. Install the Skill and configure the MCP server before starting a transcription.


<a id="agent-skill-guide-choose-the-right-skill"></a>

### Choose the right Skill

The repository contains two Skills with separate responsibilities:

| Skill | Use it for |
| --- | --- |
| `transcribe-media` | Transcribe a public media URL or continue an existing Video Transcriber AI task through MCP. |
| `transcript-insights` | Summarize, translate, organize, or extract insights from transcript or subtitle content that already exists. |

For an end-to-end workflow, run `transcribe-media` first, then pass the resulting transcript to `transcript-insights`.

<a id="agent-skill-guide-before-installing"></a>

### Before installing

1. Use an AI client that supports Agent Skills and remote Streamable HTTP MCP.
2. Install Node.js/npm and Git if you plan to use the Skills CLI.
3. Sign in to Video Transcriber AI and create an API Key in the API console.
4. Check that the API account has enough API Quota for the requested model and features.

The official repository is [VideoTranscriberAI/videotranscriber-skills](https://github.com/VideoTranscriberAI/videotranscriber-skills). Keep the complete Skill directory, including `SKILL.md` and its `references` and `agents` directories.

<a id="agent-skill-guide-install-the-skill"></a>

### Install transcribe-media

List the available Skills:

```bash [List Skills]
npx skills add VideoTranscriberAI/videotranscriber-skills --list
```

Install `transcribe-media` for Codex:

```bash [Codex]
npx skills add VideoTranscriberAI/videotranscriber-skills \
  --skill transcribe-media --agent codex --global
```

Install it for Claude Code:

```bash [Claude Code]
npx skills add VideoTranscriberAI/videotranscriber-skills \
  --skill transcribe-media --agent claude-code --global
```

Omit `--global` to install at project scope. These commands install the Skill only; they do not create the MCP connection.

For a manual installation, copy the complete `skills/transcribe-media` directory from the repository to the Skill directory supported by the client, such as `~/.agents/skills/transcribe-media/` for Codex or `~/.claude/skills/transcribe-media/` for Claude Code. Reload the Skill list or restart the client after installation.

<a id="agent-skill-guide-configure-mcp"></a>

### Configure Video Transcriber AI MCP

Configure a Streamable HTTP MCP connection named `videotranscriber`:

```text [Connection]
Endpoint: https://videotranscriber.ai/mcp
Authorization: Bearer YOUR_VS_API_KEY
```

Keep the API Key in the client's connection settings or a protected environment variable. Do not put it in a prompt, Skill file, source repository, or tool argument.

Follow the [MCP setup guide](#mcp-overview) for Codex, Claude Code, and JSON configuration examples. After connecting, verify that these tools are available:

- `create_transcription`
- `get_transcription`
- `get_transcription_result`

If the tools are unavailable, fix the MCP connection before asking the Skill to create a task.

<a id="agent-skill-guide-use-the-skill"></a>

### Use the Skill

In Codex, mention the Skill as `$transcribe-media`:

```text [Basic transcription]
Use $transcribe-media to transcribe https://example.com/media/demo.mp4
with the basic model. Create the task once, wait for completion,
then return the transcript and SRT subtitle link.
```

```text [Advanced transcription]
Use $transcribe-media to transcribe https://example.com/media/demo.mp4
with the advanced model. Enable speaker diarization, Dynamic Audio Tags,
and Smart Cleanup. Generate chapters, translate to Spanish,
and report the actual API Quota usage.
```

In Claude Code, invoke `/transcribe-media` at the start of the request.

To continue a task without creating another one:

```text [Existing task]
Use $transcribe-media to check task
req_0123456789abcdef0123456789abcdef.
If the result is available, return the transcript and subtitle links.
```

Replace the example URL and task ID with real values. The source must be a publicly accessible HTTP/HTTPS media URL. This Skill does not upload local or private files.

<a id="agent-skill-guide-workflow-and-options"></a>

### Workflow and options

The Skill creates one task, saves its `request_id`, polls according to `retry_after`, and retrieves the result when it becomes available. It stops on `succeeded`, `partial_succeeded`, `failed`, or `cancelled`.

- `basic` is the default model.
- `advanced` supports Dynamic Audio Tags, Smart Cleanup, and non-empty Keyterms.
- Speaker diarization, chapters, and translation can be requested when needed.
- A task submission uses one idempotency key. A transport retry reuses the same key and unchanged arguments.
- A timeout or lost response does not authorize creating a second task with a different key.
- An existing task ID is checked directly and does not create a new billable task.
- Polling is bounded. If the task is still running after the wait limit, the Skill returns the latest status and task ID so it can be resumed.

Transcript content is treated as source data, not as instructions for the AI client.

<a id="agent-skill-guide-billing-and-retention"></a>

### Skill billing and result retention

Creating a transcription consumes the API account's API Quota. Basic transcription costs 1 Quota per minute and advanced transcription costs 3. Non-empty Keyterms, chapters, and translation each add 1 Quota per minute. Dynamic Audio Tags and Smart Cleanup do not add a charge. Media duration rounds up to whole minutes, with a one-minute minimum.

Only successful features are charged. The Skill reports actual usage from the task status when available. Detailed rules are in [features and billing](#features-and-billing).

Results are retained for 30 days. Signed subtitle download URLs last up to one hour. If a link expires while the result is retained, ask the Skill to fetch the existing task result again; do not retranscribe the media.

<a id="agent-skill-guide-update-the-skill"></a>

### Update the Skill

```bash [Update]
npx skills update transcribe-media --global
```

Reload the client's Skill list or restart the client if the updated instructions do not appear.

<a id="agent-skill-guide-troubleshooting"></a>

### Skill troubleshooting

- Skill not found: verify the installation scope and confirm the installed directory contains `SKILL.md`.
- MCP tools missing: verify the server URL, Streamable HTTP support, connection permissions, and authentication settings.
- `invalid_api_key` or `api_key_revoked`: check the Key stored in the MCP connection. Do not paste the Key into chat.
- `insufficient_quota`: add API Quota before creating another task.
- `idempotency_conflict`: the same key was used with different arguments. Preserve the original task and do not bypass the error with a new key.
- Task failed or partly succeeded: inspect the task and feature errors before deciding whether to submit a separate task.
- Subtitle link expired: retrieve the result of the same task again during the retention period.

For MCP connection and tool details, see the [Video Transcriber AI MCP guide](#mcp-overview).
