---
name: kaithescribe-transcribe
description: >-
  Transcribe audio or video to text through the Kai The Scribe REST API at
  https://www.kaithescribe.com/api/v1/transcribe. Use whenever a task needs a
  transcript of a recording (podcast episode, interview, meeting, lecture,
  voice memo, video) and the human has a Kai The Scribe account. Handles file
  upload or public URL, optional speaker labels, language hints, and the
  free-tier minute allowance. Triggers: transcribe this, get a transcript,
  turn this recording into text, speaker-labelled transcript, podcast to text,
  meeting notes from audio, what was said in this file.
---

# Kai The Scribe Transcription API

One endpoint turns a recording into punctuated text, with an optional speaker-labelled version. It runs on the account of whoever created the API key, uses their minutes, and saves the transcript to their account.

Full reference: <https://www.kaithescribe.com/docs/api/>

## Endpoint

```
Base: https://www.kaithescribe.com/api/v1
Auth: Authorization: Bearer <kts_live_...>
```

## Getting a key from the human

Keys are created by a signed-in person, never by the agent. Ask for one like this:

1. Open <https://www.kaithescribe.com/content/video/transcribe> and sign in with Google.
2. Scroll to the **API keys** card, type a name (for example the agent's name), click **Create key**.
3. Copy the key that starts with `kts_live_` right away. It is shown once.
4. Hand it to the agent as an environment variable, for example `KTS_KEY`.

Store it as a secret. Never print it, log it, or write it into a file the human did not ask for. If the key is lost, the human revokes it from the same card and creates another. Up to 5 keys can be active per account.

## Operations

### Check the plan first: `GET /api/v1/me/`

```bash
curl -s https://www.kaithescribe.com/api/v1/me/ \
  -H "Authorization: Bearer $KTS_KEY"
```

Returns:

```jsonc
{
  "email": "owner@example.com",
  "plan": "free",              // or "pro"
  "status": "none",            // Stripe subscription status, "none" on free
  "minutesUsedThisMonth": 12,
  "minutesAllowance": 60,      // null on pro (unlimited)
  "minutesRemaining": 48,      // null on pro (unlimited)
  "rateLimitPerHour": 30,
  "keyPrefix": "kts_live_Ab3dEf9h"
}
```

If `minutesRemaining` is smaller than the recording length in minutes, tell the human before uploading instead of burning the request.

### Transcribe: `POST /api/v1/transcribe/`

By public URL (preferred, no size cap):

```bash
curl -s -X POST https://www.kaithescribe.com/api/v1/transcribe/ \
  -H "Authorization: Bearer $KTS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/episode.mp3", "diarize": true, "language": "en"}'
```

By file upload (under 4.5 MB):

```bash
curl -s -X POST https://www.kaithescribe.com/api/v1/transcribe/ \
  -H "Authorization: Bearer $KTS_KEY" \
  -F "file=@interview.m4a" -F "diarize=true"
```

Fields:

| Field | Where | Notes |
|---|---|---|
| `url` | JSON body | Direct public http(s) link to the media file. Share pages (YouTube, Zoom, Drive) do not work; you need the file itself. |
| `file` | multipart field | MP3, WAV, M4A, MP4, MOV, WEBM. Direct uploads over 4.5 MB are rejected with 413; use `url` instead. |
| `language` | either | BCP-47 code (`en`, `es`, `de`). Omit or `auto` to detect. |
| `diarize` | either | `true` to label speakers. Turn it on for interviews, meetings, podcasts with more than one voice. |

Response:

```jsonc
{
  "transcript": "Hi, Sam. Hi, Alex. Ready? Yes.",
  "formattedTranscript": "Speaker 1: Hi, Sam.\n\nSpeaker 2: Hi, Alex.\n\nSpeaker 1: Ready?\n\nSpeaker 2: Yes.",
  "words": [{ "word": "Hi,", "start": 0.08, "end": 0.32, "confidence": 0.99, "speaker": 0 }],
  "durationSeconds": 3.4,
  "language": "en",
  "persisted": true
}
```

Use `formattedTranscript` when showing text to a person. It equals `transcript` when there are no speaker labels. Use `words` for timestamps and per-word speaker indexes (0-based).

## Errors

| Status | Meaning | What to do |
|---|---|---|
| 400 | Missing file, bad URL, invalid JSON | Read `error`, fix the request. |
| 401 | Missing, malformed, unknown, or revoked key | Ask the human for a new key. Do not retry with the same one. |
| 402 | Free minutes for the month are used up | Show the human `upgradeUrl` (Pro is $19/month, unlimited). Do not retry until they upgrade or the month rolls over. |
| 413 | Upload over 4.5 MB | Host the file somewhere public and resend with `{"url": ...}`. |
| 415 | Wrong content type | Send multipart/form-data or application/json. |
| 429 | Over 30 requests this hour for the key | Wait until `resetAt` (also in the Retry-After header). |
| 502 | Speech engine rejected the media | The URL was unreachable or the file is not decodable audio/video. Check the source. |
| 503 | Transcription not configured on this deployment | Report it to the human; nothing to retry. |

## Limits and pricing

- 30 requests per hour per key, both endpoints, reset on the hour UTC.
- 5 active keys per account.
- 4.5 MB per direct upload; any size by URL.
- 60 seconds of processing per request. Split recordings over about an hour.
- Minutes count the recording length, rounded up per request.
- Free: 60 minutes a month, shared between the website and the API. Pro: $19 a month, unlimited. The human upgrades at <https://www.kaithescribe.com/#pricing>; a key cannot buy Pro.

## Anti-patterns

| Don't | Why |
|---|---|
| Send a YouTube, Zoom, or Drive share link as `url` | The endpoint fetches the file itself; a share page returns 400 or 502. |
| Upload a 50 MB video directly | 413. Host it and send the URL, or extract the audio track first. |
| Retry a 401 or 402 in a loop | Neither clears on its own. Both need the human. |
| Print or store the key outside the secret the human chose | Anyone with the key transcribes on their account and minutes. |
| Re-transcribe the same file to "check" | Every request spends minutes and rate-limit budget. Reuse the first response. |

## Verification

1. `persisted: true` means the transcript is saved to the owner's account; `false` means keep the response, it is the only copy.
2. `durationSeconds` tells you how many minutes were spent; a follow-up `GET /api/v1/me/` shows the new balance.
