Two ways to get YouTube transcripts: pull on your schedule, or receive them instantly via webhook the moment a new video drops.
The Pull API uses API key authentication. Find your key in the dashboard settings. Pass it using any of these methods:
Authorization: Bearer tr_live_xxxxxxxxxxxx
X-Api-Key: tr_live_xxxxxxxxxxxx
GET /api/videos?api_key=tr_live_xxxxxxxxxxxx
Webhooks don't require authentication — you configure the endpoint URL when creating a subscription. VidProxy will POST to that URL whenever a new video is detected.
Fetch all transcripts detected for your subscriptions within a time window. Good for batch processing, scheduled jobs, or polling-based integrations.
| Parameter | Type | Default | Description |
|---|---|---|---|
since |
string | 24h |
Time window. Accepts Nh (hours) or Nd (days). Examples: 6h, 7d. |
tag |
string | — | Filter by subscription label. If omitted, returns videos from all subscriptions. |
curl https://vidproxy.pro/api/videos?since=24h&tag=my-channel \ -H "Authorization: Bearer tr_live_xxxxxxxxxxxx"
{
"videos": [
{
"id": 1234,
"subscription_id": 42,
"channel_id": "UCxxxxxxxxxxxxxxxxxxxxxx",
"channel_name": "HomeBrewers",
"tag": "my-channel",
"video_id": "dQw4w9WgXcQ",
"video_title": "How to Brew a West Coast IPA",
"published_at": "2026-04-30T10:00:00.000Z",
"transcript_available": true,
"transcript_text": "Welcome to today's brew session...",
"transcript_source": "proxy",
"detected_at": "2026-04-30T10:05:12.000Z",
"ai_summary": null,
"ai_key_takeaways": null,
"ai_topics": null
}
],
"query": {
"tag": "my-channel",
"since": "24h",
"cutoff": "2026-04-29T10:05:12.000Z"
}
}
| Field | Type | Description |
|---|---|---|
video_id |
string | YouTube video ID. Construct the full URL as https://www.youtube.com/watch?v={video_id}. Use this for deduplication. |
channel_id |
string | YouTube channel ID (e.g. UCxxxxxx). |
channel_name |
string | Channel display name, as shown on YouTube. |
tag |
string | null | The label you set on the subscription. Useful for routing videos to different pipelines. |
video_title |
string | Video title at time of detection. |
published_at |
ISO 8601 | When the video was published on YouTube. |
transcript_available |
boolean | false only when YouTube explicitly reports transcripts are disabled for this video. |
transcript_text |
string | null | Full transcript as plain text. null if unavailable. |
transcript_source |
string | proxy, direct, or scraperapi — which fetch method succeeded. |
detected_at |
ISO 8601 | When VidProxy first detected this video. |
ai_summary |
string | null | AI-generated 2–3 sentence summary of the video. Pro and Agency plans only. |
ai_key_takeaways |
string[] | null | Array of key takeaways extracted from the transcript. Pro and Agency plans only. |
ai_topics |
string[] | null | Array of topic tags for the video. Pro and Agency plans only. |
subscription_id |
integer | Internal subscription ID. Stable identifier for the channel subscription that triggered this. |
Fetch the transcript for any YouTube video by URL or video ID — no channel subscription required. Each successful fetch counts against your monthly lookup quota. Failed fetches (transcript unavailable) are free.
| Parameter | Type | Description |
|---|---|---|
url |
string | Full YouTube URL (e.g. https://youtube.com/watch?v=abc123 or https://youtu.be/abc123). Use either url or video_id, not both. |
video_id |
string | YouTube video ID (11-character string, e.g. dQw4w9WgXcQ). |
curl "https://vidproxy.pro/api/transcript?video_id=dQw4w9WgXcQ" \ -H "Authorization: Bearer tr_live_xxxxxxxxxxxx"
{
"video_id": "dQw4w9WgXcQ",
"available": true,
"text": "Full transcript as plain text...",
"segments": [
{ "start": 0, "duration": 4, "text": "Never gonna give you up" },
...
],
"credits_remaining": 2499
}
| Header | Description |
|---|---|
X-Credits-Remaining |
Lookups remaining this month. unlimited for Agency. |
| Status | Error | Meaning |
|---|---|---|
400 |
url or video_id is required |
Neither parameter was provided, or the URL couldn't be parsed. |
402 |
credits_exhausted |
Monthly lookup quota reached. Resets on the 1st of each month. |
| Plan | Lookups / month |
|---|---|
| Free | 10 |
| Starter | 500 |
| Pro | 2,500 |
| Agency | 10,000 |
VidProxy ships a Model Context Protocol server at https://vidproxy.pro/mcp. Add it to Claude Desktop, Cursor, VS Code, or any MCP-compatible client and your AI assistant can read your channel data, pull transcripts, check what's left on your plan, and manage which channels you monitor — no copy-pasting, no API calls to write.
Add the following to your MCP client config. For Claude Desktop that's ~/.config/claude/claude_desktop_config.json; for Cursor it's in Settings → MCP.
{
"mcpServers": {
"vidproxy": {
"url": "https://vidproxy.pro/mcp",
"headers": {
"Authorization": "Bearer tr_live_xxxxxxxxxxxx"
}
}
}
}
Your API key is in Dashboard → Settings. The MCP server uses the same key as the REST API.
| Tool | Description | Credits |
|---|---|---|
get_transcript |
Fetch the full transcript for any YouTube video by ID or URL | 1 per successful fetch |
get_recent_videos |
Get videos from your monitored channels within a time window (e.g. since: "24h"), including full transcripts |
Free |
search_videos |
Search your subscription history by keyword — matches video titles and transcript text | Free |
list_channels |
List your monitored channels with latest video info. Each row includes a subscription_id that the tools below accept |
Free |
get_account_status |
Your plan and everything metered against it — channels used vs. allowed, webhooks, remaining transcript credits and their reset date, rate limit, poll interval. Unlimited values come back as null |
Free |
add_channel |
Start monitoring a channel by @handle, URL or UC… ID. Already monitoring it? You get the existing subscription back rather than a duplicate. Counts against your plan's channel limit |
Free |
set_channel_active |
Pause or resume a channel. Paused channels are skipped by the poller but keep every stored video and transcript. A paused channel still occupies a slot against your channel limit | Free |
remove_channel |
Permanently delete a channel and all stored videos, transcripts and delivery records for it. Requires an explicit confirm: true, and is disabled unless you enable destructive tools for the key — see below |
Free |
Three of these tools write to your account — they add, pause and delete subscriptions using the same plan limits as the dashboard. Your API key is what authorises them, so treat it like a password: anyone holding it can change what you monitor. Rotate it any time in Dashboard → Settings. Webhook URLs are deliberately not settable over MCP — configure those in the dashboard.
remove_channel is disabled by default. Until you enable destructive tools for a key in Dashboard → Settings, the tool is not advertised to your assistant at all and is refused if called — every other tool works normally. Rotating a key switches it back off.
Why it works this way: the confirm: true flag is a speed bump for a well-behaved assistant, not a security boundary, because the model decides whether to set it. Transcripts are written by the channel owner, not by you, so a video whose captions contain instructions aimed at your assistant is a real possibility once that assistant can also delete things. The dashboard toggle is the part of the gate that no text your assistant reads can flip. Deletion is irreversible and we keep no per-account backup of transcripts.
Configure a webhook URL on any subscription and VidProxy will POST a JSON payload to it within minutes of a new video being published. No polling required.
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
VidProxy/1.0 |
X-VidProxy-Event |
new_video |
{
"event": "new_video",
"timestamp": "2026-04-30T10:05:12.000Z",
"subscription": {
"id": 42,
"label": "my-channel",
"channel_id": "UCxxxxxxxxxxxxxxxxxxxxxx",
"channel_name": "HomeBrewers"
},
"video": {
"id": "dQw4w9WgXcQ",
"title": "How to Brew a West Coast IPA",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"published": "2026-04-30T10:00:00.000Z"
},
"transcript": {
"available": true,
"text": "Welcome to today's brew session...",
"segments": [
{ "text": "Welcome to today's brew session", "start": 0.5, "duration": 3.2 }
]
},
"enrichment": {
"summary": "A step-by-step guide to brewing a West Coast IPA...",
"key_takeaways": ["Use high-alpha hops for bittering", "Dry hop at 68°F for best aroma", "Cold crash before packaging"],
"topics": ["IPA", "hops", "dry hopping", "homebrewing"]
},
"matched_keywords": ["west coast"],
"keyword_matches": [
{
"keyword": "west coast",
"in_title": true,
"occurrences": 4,
"snippets": [
{
"text": "… the classic West Coast style leans on Cascade and Centennial …",
"start_seconds": 412,
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=412s"
}
]
}
]
}
The enrichment field is only present on Pro and Agency plans with AI enrichment enabled. transcript.segments contains per-segment timing data when available.
matched_keywords is always present: [] when the subscription has no keyword filter, otherwise the keywords that matched this video, spelled as you typed them. keyword_matches appears only alongside a match and says where each keyword hit (in_title, occurrences) with up to three short snippets you can quote. start_seconds and the timestamped url are null when the transcript has no timing data.
A test send from the dashboard uses this same shape, but with "event": "test" and "test": true at the top level and placeholder video data — branch on event if you need to distinguish or ignore it.
If your endpoint returns a non-2xx status or times out (10s), VidProxy retries up to 3 times:
After 3 failed retries the delivery is marked failed and logged in your dashboard. Make your endpoint idempotent — use video.id for deduplication.
Return any 2xx status code within 10 seconds. If your processing takes longer, acknowledge immediately and handle async.
You don't need to wait for a real video to find out if your endpoint is set up correctly. Open the subscription's Edit modal in the dashboard and click Send test to fire a synthetic payload at your webhook URL immediately, including one you haven't saved yet, and see a pass/fail result right away. Test sends go through the same SSRF-guarded delivery path as a real webhook, but are a single attempt with no retry and are not written to the Delivery Log. Once your subscription is live, a failed delivery also shows up as a badge directly on the subscription card in the dashboard, so you no longer have to open the Delivery Log to notice a broken endpoint.
Keyword Alerts let you save keywords and receive an email whenever any new video across all your subscriptions mentions them — matched against the video title and transcript. This is separate from per-subscription keyword filters, which control webhook delivery.
Alerts are managed from the Keyword Alerts tab in your dashboard. Add a keyword, and VidProxy will email you each time a new video matches it. Up to 50 keywords per account.
gpt matches "GPT-4" and "GPT is…" but not "ChatGPT", and fed matches "the Fed's decision" but not "federal". Phrases work (federal reserve). There is no stemming: tariff does not match "tariffs", so add both.Subscriptions also support a per-subscription Keyword Filter (set in the subscription edit modal, comma-separated, up to 25 keywords). This controls webhook delivery only — the webhook fires only when the video title or full transcript matches one of the keywords, using the same whole-word rules as above, and the payload says which ones (matched_keywords). It does not send emails and is independent of Keyword Alerts.
filtered · no keyword match.skipped · no transcript to match.paused · filter not in your plan) until you clear it; clearing always works.| Method | Endpoint | Description |
|---|---|---|
GET | /api/keyword-alerts | List all keyword alerts |
POST | /api/keyword-alerts | Create a keyword alert — body: { "keyword": "string" } |
DELETE | /api/keyword-alerts/:id | Delete a keyword alert |
The Pull API is rate-limited per API key against two windows checked together: a burst limit (sliding 60 seconds) and a sustained limit (sliding 24 hours). A request has to fit under both. Response headers tell you your current usage:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Max burst requests per minute for your plan. |
X-RateLimit-Remaining |
Requests remaining in the current minute window. |
X-RateLimit-Limit-Day |
Max requests per day for your plan. |
X-RateLimit-Remaining-Day |
Requests remaining in the current day window. |
Retry-After |
Seconds until the window that refused the request resets (only on 429 responses). |
When you exceed either limit you receive a 429 Too Many Requests with a Retry-After header and a limit_type field (burst or daily) in the JSON body naming which one you hit. Back off and retry after that delay.
| Plan | Channels | Webhooks | API req / min | API req / day | Transcript lookups / mo |
|---|---|---|---|---|---|
| Free | 1 | 1 | 15 | 100 | 10 |
| Starter | 15 | 3 | 20 | 300 | 500 |
| Pro | 100 | Unlimited | 60 | 3,000 | 2,500 |
| Agency | 1,000 | Unlimited | 100 | 8,000 | 10,000 |
Authorization: Bearer header, an X-Api-Key header, or an api_key query parameter. The key lives in Dashboard → Settings. Keys are stored hashed, so the plaintext is shown only at the moment you create or rotate one — if you lose it, rotate rather than go looking for it. Rotating retires the old key immediately.
GET /api/videos?since=24h returns everything detected in that window with transcripts attached. Running both is reasonable: webhooks for the live path, a scheduled pull to catch anything a failed delivery dropped.
X-Credits-Remaining header, and the quota resets on the 1st of each month: 10 on Free, 500 on Starter, 2,500 on Pro, 10,000 on Agency. Past the quota the endpoint answers 402 credits_exhausted. The get_transcript MCP tool draws on the same pool; webhook deliveries and the pull API never touch it.
false means YouTube reports captions as disabled for that video, which no amount of retrying changes.
404 or 500 for channels that are plainly fine, then clears on its own. We currently see it between roughly 01:00 and 07:00 UTC, though the hours drift and other people using the same feed report a different window, so treat that range as an observation rather than a schedule. It's a long-standing YouTube issue, publicly reported since December 2025 and still open; it isn't specific to VidProxy and there's no setting on either side that avoids it.
429, with Retry-After giving the seconds until the window clears. Two budgets are checked per API key: a burst per-minute window and a sustained per-day window — 15/min (100/day) on Free, 20/min (300/day) on Starter, 60/min (3,000/day) on Pro, 100/min (8,000/day) on Agency. A request has to fit under both. X-RateLimit-Remaining and X-RateLimit-Remaining-Day on every response let you back off before you get there. Calls to /mcp draw from that same per-key budget rather than a separate one, so splitting traffic across the two transports doesn't buy you extra headroom.
get_transcript spends a credit, add_channel counts against your channel limit. The exception is remove_channel, which stays hidden from the tool list and is refused until you enable destructive tools for that key in Dashboard → Settings. Rotating the key switches that back off.