# How does TubeTranscript secure API access and handle data?

TubeTranscript secures programmatic access with **Pro plan API keys** sent as a **Bearer Token**, stored only as **SHA-256 hashes**. Every caller is rate limited over a **sliding 24-hour window**: **10 transcripts** for guests (at most **5** via Whisper), **24** for free accounts, **1,000 requests** per Pro API key. Requests run within a **300-second** limit; AI features accept transcripts up to **1,200,000 characters**.

## How is the API authenticated?

Every production request to **`/api/v1/extract`** and the **MCP endpoint** must send **`Authorization: Bearer <API_KEY>`**. Keys use the format **`tt_live_` + 32 base64url characters** and are accepted only when the key's team is on the **Pro plan**. On the REST API a missing or unknown key returns **`401`** and a non-Pro team returns **`403 PLAN_UPGRADE_REQUIRED`**; the MCP servers answer the same cases with **`402 Payment Required`** and the original code in `reason`.

```http
Authorization: Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

| Key property  | Value                                                                  |
| ------------- | ---------------------------------------------------------------------- |
| Header        | `Authorization: Bearer <API_KEY>`                                      |
| Format        | `tt_live_` + 32 base64url characters                                   |
| Pattern       | `^tt_live_[A-Za-z0-9_-]{32}$`                                          |
| Accepted plan | Pro                                                                    |
| Required by   | `/api/v1/extract`, `/api/mcp`, `/api/mcp/tools`, `/api/mcp/transcript` |

| Condition                         | HTTP | `error.code`            |
| --------------------------------- | ---- | ----------------------- |
| No `Authorization` header         | 401  | `MISSING_API_KEY`       |
| Malformed, unknown or revoked key | 401  | `INVALID_API_KEY`       |
| Key's team not on Pro             | 403  | `PLAN_UPGRADE_REQUIRED` |

### How are API keys stored and revoked?

TubeTranscript generates each key from **24 random bytes**, shows the raw key **exactly once**, and persists only its **SHA-256 hash** plus a short hint. A database leak therefore exposes no usable keys. Revoking a key in the **dashboard** (`/dashboard/api`) sets a revocation timestamp, and every later request with that key returns **`401`** immediately.

| Stage            | What happens                                                  |
| ---------------- | ------------------------------------------------------------- |
| Generation       | 24 random bytes, base64url-encoded, prefixed with `tt_live_`. |
| Display          | Raw key shown once at creation.                               |
| Storage          | SHA-256 hash and a short hint only.                           |
| Revocation       | Dashboard `/dashboard/api` sets a revocation timestamp.       |
| After revocation | Every request returns `401 INVALID_API_KEY`.                  |

---

## What are the rate limits for unauthenticated and authenticated requests?

Unauthenticated **guests get 10 transcripts per 24 hours per client IP address**, of which at most **5** may fall back to **Whisper** (videos without captions); a Whisper job counts only once it completes. Signed-in **free accounts get 24 per 24 hours per team**. **Pro** allows **1,000 requests per 24 hours per API key**, or per team in the web app. All counters use an Upstash Redis **sliding window**.

| Tier               | Request Limit                                               | MCP Support                       | Transcript Storage                           |
| ------------------ | ----------------------------------------------------------- | --------------------------------- | -------------------------------------------- |
| Guest (no account) | 10 transcripts / 24 h per client IP (max 5 Whisper)         | No                                | Public page under `/c/`, no automatic expiry |
| Free account       | 24 transcripts / 24 h per team                              | No                                | Public page under `/c/`, no automatic expiry |
| Pro                | 1,000 requests / 24 h per API key (per team in the web app) | Yes: `/api/mcp`, `/api/mcp/tools` | Private to the team, no automatic expiry     |

| Pro surface            | What counts as one request                                         |
| ---------------------- | ------------------------------------------------------------------ |
| `POST /api/v1/extract` | Each call, regardless of the number of `features`.                 |
| MCP servers            | Each `tools/call`, including a `get_transcript` that returns `processing`; `initialize`, `tools/list` and `ping` are free. |
| Web app                | Each transcript job, counted per team.                             |

### What happens when a rate limit is exceeded?

An exceeded limit returns **`429 RATE_LIMIT_EXCEEDED`** with a **`Retry-After`** header in seconds. Successful responses include **`X-RateLimit-Limit`**, **`X-RateLimit-Remaining`** and **`X-RateLimit-Reset`** so agents can throttle before hitting the cap. If the rate-limit store is unreachable, authenticated routes return **`503 RATE_LIMIT_UNAVAILABLE`** with **`Retry-After: 60`** instead of allowing unmetered traffic.

| Situation               | Surface                | HTTP        | `error.code`             | `Retry-After`              |
| ----------------------- | ---------------------- | ----------- | ------------------------ | -------------------------- |
| Quota used up           | REST API, web app      | 429         | `RATE_LIMIT_EXCEEDED`    | Seconds until a slot frees |
| Quota used up           | MCP servers            | 402         | `M2M_INSUFFICIENT_FUNDS` | Seconds until a slot frees |
| Quota store unreachable | MCP servers            | 503         | `RATE_LIMIT_UNAVAILABLE` | 60                         |
| Quota store unreachable | `POST /api/v1/extract` | 503         | `RATE_LIMIT_UNAVAILABLE` | 60                         |
| Quota store unreachable | Guest requests         | Not limited | none                     | none                       |

| Response header         | Meaning                                      |
| ----------------------- | -------------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed per 24-hour sliding window. |
| `X-RateLimit-Remaining` | Requests left in the current window.         |
| `X-RateLimit-Reset`     | When the window resets.                      |
| `Retry-After`           | Seconds to wait before retrying.             |

---

## How does the system handle timeouts and payload caps?

The **`/api/v1/extract`** route runs for at most **300 seconds** and budgets **280 seconds** internally, so it finishes inside the **300-second** timeouts of Make and n8n. Unfinished Whisper jobs return **`202 EXTRACTION_IN_PROGRESS`** for a retry. AI features split transcripts above **120,000 characters** into segments to protect the LLM context window, and reject those above **1,200,000 characters** with **`TRANSCRIPT_TOO_LONG`**.

| Safeguard                             | Value                               | Behavior when reached                                                                        |
| ------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------- |
| Route execution limit                 | 300 s                               | Set client timeout to 300 s.                                                                 |
| Transcript wait budget, no features   | ~280 s                              | `202 EXTRACTION_IN_PROGRESS`, `Retry-After: 60`; resend the same request.                    |
| Transcript wait budget, with features | ~210 s                              | `202 EXTRACTION_IN_PROGRESS`, `Retry-After: 60`; resend the same request.                    |
| AI segment size                       | 120,000 characters (~2 h of speech) | Longer transcripts are processed in segments and merged.                                     |
| AI feature input cap                  | 1,200,000 characters                | Feature fails with `TRANSCRIPT_TOO_LONG`; transcript still returned (`502 FEATURES_FAILED`). |
| MCP AI tools execution limit          | 90 s                                | Request ends; retry later.                                                                   |
| `url` field length                    | 2,048 characters                    | `400 INVALID_INPUT`.                                                                         |

---

## What is the data retention and privacy policy?

TubeTranscript stores **video IDs and transcripts** to serve repeat requests: extraction jobs, including the job that every MCP **`get_transcript`** call stores for the team, keep their transcript in the database with **no automatic expiry**, and AI results of the MCP tools are stored with the job. **Free and guest jobs become public transcript pages**; **Pro jobs stay private**. TubeTranscript does not train its own models on transcripts.

| Data                               | Where       | Retention           | Visibility              |
| ---------------------------------- | ----------- | ------------------- | ----------------------- |
| Job transcript, free or guest      | Database    | No automatic expiry | Public page under `/c/` |
| Job transcript, Pro (web app, REST API or MCP `get_transcript`) | Database | No automatic expiry | Private to the team |
| MCP AI tool results (hooks, mindmap, blog) | Database, with the job | Until the job is deleted | Private to the team |
| API key                            | Database    | Until revoked       | SHA-256 hash only       |

### Which third parties process transcript data?

When a request includes **AI features** (`hooks`, `mindmap`, `action_items`), the transcript text is sent to **Google Gemini** to generate them; transcript-only requests are not sent to an LLM. Videos without captions are transcribed with **Whisper** on TubeTranscript's own worker. See the [API Reference](https://tubetranscript.io/docs/api.md) for request details.

| Processor                       | Receives          | When                                                       |
| ------------------------------- | ----------------- | ---------------------------------------------------------- |
| Google Gemini                   | Transcript text   | Request includes AI features, or an MCP AI tool is called. |
| Apify                           | YouTube video URL | Tier 1 caption fetch.                                      |
| Whisper (TubeTranscript worker) | Video audio       | Video has no captions.                                     |
