captions · v1.1.0 · 2026-10-15 · sha256 90da728453fb45a3

captions v1.1.0A

Immutable. This exact content never changes and is served at /api/v1/blob/90da728453fb45a3.

---
name: captions
description: "Reach for this when caption text from a YouTube video is wanted: reading a video instead of watching it, quoting or translating speech, accessibility (deaf/HoH) needs, content review, language practice, or exporting CC as SRT/VTT files. Timestamped captions from any public video. Skip it for uploading captions or account chores."
version: "1.1.0"
user-invocable: true
compatibility: Requires internet access to reach api.transcriptout.com. No additional runtimes or dependencies needed.
required_environment_variables:
  - name: TRANSCRIPTOUT_API_KEY
    prompt: Your TranscriptOut API key (starts with sk_)
    help: Free account at https://transcriptout.com/?utm_source=youtube-skills&utm_medium=skill&utm_content=captions (100 credits on signup, no card). Create the key in the dashboard.
    required_for: all API requests
metadata: {"openclaw":{"emoji":"💬","requires":{"env":["TRANSCRIPTOUT_API_KEY"]},"primaryEnv":"TRANSCRIPTOUT_API_KEY","homepage":"https://transcriptout.com"},"hermes":{"tags":["youtube","captions","cc","subtitles","accessibility","video"],"category":"media"}}
---

# Captions

Extract closed captions from YouTube videos via [TranscriptOut.com](https://transcriptout.com/?utm_source=youtube-skills&utm_medium=skill&utm_content=captions).

## Setup

If `$TRANSCRIPTOUT_API_KEY` is not set, read [references/auth-setup.md](references/auth-setup.md) and follow the instructions there to get and store the key.

## Required Header

Every request carries these headers:

- **Authorization:** `Bearer $TRANSCRIPTOUT_API_KEY`
- **X-Client:** `youtube-skills/captions`, so TranscriptOut knows the call came from this skill

Every response is one JSON envelope. Success: `{"ok": true, "request_id": "...", "data": {...}}`. Error: `{"ok": false, "code": "...", "detail": "...", "request_id": "..."}`. Branch on the machine-readable `code`, not on the human text. The remaining credit balance rides in the `X-Credits-Remaining` response header.

## GET /v1/transcript · 1 credit

Fetch the closed captions of any YouTube video: timed JSON segments, plain text, or ready-made `SRT`/`VTT` files.

```http
# SRT body inside the JSON envelope
GET https://api.transcriptout.com/v1/transcript?video=VIDEO_URL_OR_ID&format=srt
Authorization: Bearer $TRANSCRIPTOUT_API_KEY
X-Client: youtube-skills/captions

# or the raw .srt file itself
GET https://api.transcriptout.com/v1/transcript?video=VIDEO_URL_OR_ID&format=srt&download=true
Authorization: Bearer $TRANSCRIPTOUT_API_KEY
X-Client: youtube-skills/captions
```

| Param            | Required | Default | Values |
| ---------------- | -------- | ------- | ------ |
| `video`          | yes      | -       | YouTube URL (full/short/shorts) or 11-char video ID |
| `lang`           | no       | `en`    | language code of the track (`en`, `de`, ...) |
| `format`         | no       | `json`  | `json`, `text`, `srt`, `vtt`, `srv3` |
| `kind`           | no       | manual if present | `manual`, `auto` |
| `segment`        | no       | auto tracks: 180 (80 for `srt`/`vtt`). Manual tracks keep the author's lines | 20-5000, max characters per segment |
| `video_metadata` | no       | `false` | `true` adds `data.metadata` (title, channel, duration, views) for the same 1 credit |
| `download`       | no       | `false` | `true` returns the raw file instead of the JSON envelope (`text`/`srt`/`vtt`/`srv3` only) |

With `format=srt` or `format=vtt` the default segmenting for auto-generated tracks is subtitle-sized (~80 characters per cue). Manual tracks keep the author's own line breaks in every format. `download=true` returns the bare file instead of the JSON envelope.

**Response** for `format=json`. With `format=text`/`srt`/`vtt`/`srv3` the `transcript` field is one string in that format:

```json
{
  "ok": true,
  "request_id": "req_...",
  "data": {
    "video_id": "dQw4w9WgXcQ",
    "language": "en",
    "kind": "manual",
    "transcript": [
      { "text": "Never gonna give you up", "start": 18.0, "duration": 4.12 },
      { "text": "Never gonna let you down", "start": 22.12, "duration": 3.85 }
    ],
    "available_langs": [
      { "code": "en", "kind": "manual", "name": "English" },
      { "code": "en", "kind": "auto", "name": "English (auto-generated)" }
    ]
  }
}
```

## Tips

- `format=text` is the cheapest form to reason over. Ask for `json` only when timestamps matter.
- Transcript AND title/channel wanted? One call: `video_metadata=true`. Never a separate `/v1/video` first.
- Transcribe last: search and listings are for choosing, and pulling transcripts for a whole results page nobody asked to read is the fastest way to burn a balance.

## Errors

| Code | Meaning | Action |
| ---- | ------- | ------ |
| 400/422 | Bad parameter | Fix the request. Credit refunded automatically |
| 401 | Bad or missing API key | Check the key. It must start with `sk_` |
| 402 | Out of credits | [transcriptout.com/billing](https://transcriptout.com/billing?utm_source=youtube-skills&utm_medium=skill&utm_content=captions) |
| 404 | No captions on that language/track, or bad ID | Definitive answer, do not retry. Try another `lang` or `kind=auto`. Billed |
| 410 | Video removed | Do not retry |
| 451 | Age-restricted or members-only | Do not retry |
| 429 | Rate limit (200 req/min per key) | Wait, respect `Retry-After`. Refunded |
| 502 | Failed on YouTube's side | One retry is reasonable. Billed |
| 503 | Service at capacity | Retry after `Retry-After`. Refunded |

Every error body carries a machine-readable `code` and a `request_id`. Include the `request_id` when contacting support.

## Copy-paste examples

Every request in this file as a ready-to-run one-liner: [references/curl-examples.md](references/curl-examples.md)