YouTube transcript in Zapier
Zapier's YouTube app will tell you a channel posted a video. It will not tell you what was said in it — there is no transcript action, because the YouTube Data API underneath only serves captions to the video's owner. Two Zapier steps fill the gap: Webhooks by Zapier for a no-code GET, or Code by Zapier for a single JavaScript step that fetches and joins the transcript. Both call https://api.transcriptapi.io/transcript.
Option 1: Webhooks by Zapier (GET)
Add an action step, choose Webhooks by Zapier, event GET. Webhooks is a premium app, so it needs a paid Zapier plan (as of September 2026).
| Field | Value |
|---|---|
| URL | https://api.transcriptapi.io/transcript |
| Query String Params | video_id → the trigger's video id field; language → en; optional translate_to → de |
| Send As JSON | No |
| Unflatten | Yes |
| Headers | Authorization → Bearer ta_your_key |
Test the step. The API returns JSON with a transcript array whose items carry start and duration in seconds and the text:
{
"video_id": "dQw4w9WgXcQ",
"transcript": [
{ "start": 0.0, "duration": 3.52, "text": "We're no strangers to love" },
{ "start": 3.52, "duration": 2.88, "text": "You know the rules and so do I" }
]
}Zapier shows the array as line items:
video_id dQw4w9WgXcQ transcript (line items) transcript[]start 0, 3.52, 6.4, ... transcript[]text We're no strangers to love, You know the rules and so do I, ...
To turn that into one paragraph without code, add Formatter by Zapier → Utilities → Line-item to Text, input Transcript Text, separator a single space. Its output is the plain transcript for the next step. The full parameter list for the endpoint is in the API docs.
Option 2: Code by Zapier (JavaScript)
One step that does all three jobs — extract the id, fetch the transcript, join the segments — and works on every plan. Add Code by Zapier → Run JavaScript, map two Input Data fields, paste the script:
// Code by Zapier · JavaScript
// Input Data: url ← 1. Video URL (from the trigger)
// key ← ta_your_key (typed in once, stored by Zapier)
const m = String(inputData.url).match(
/(?:v=|youtu\.be\/|\/shorts\/|\/embed\/|\/live\/)([A-Za-z0-9_-]{11})/
);
if (!m) throw new Error('No YouTube video id in: ' + inputData.url);
const videoId = m[1];
const res = await fetch(
'https://api.transcriptapi.io/transcript?video_id=' + videoId + '&language=en',
{ headers: { Authorization: 'Bearer ' + inputData.key } }
);
const body = await res.json();
if (!res.ok) {
// 404: captions off / private / deleted / age-restricted. Refunded.
// retryable:false means the video is the problem, so end the Zap quietly.
if (body.retryable === false) return { videoId, text: '', skipped: body.detail };
// 402 (no credits) or 502/503 (YouTube refused, refunded): let Zapier
// mark the run as errored and replay it later.
throw new Error(res.status + ': ' + body.detail);
}
const text = body.transcript
.map((s) => s.text.trim())
.filter(Boolean)
.join(' ');
return { videoId, segments: body.transcript.length, text };Output fields text, videoId and segments become mappable in later steps. A one-hour talk is around 9,000 words of text; the fetch takes a second or two on a cache miss and well under that on a hit.
Because Zapier passes line items into a Code step as one comma-separated string, and captions contain commas, so the segments cannot be told apart again. Either join with Formatter (option 1) or fetch inside Code (option 2). A second request for the same video is a cache hit and costs nothing, so fetching again in Code is not wasteful.
Getting the 11-character id
The YouTube trigger gives you a Video Id field directly; map that and skip extraction. Anything else — a form, a Slack message, a Notion property, an RSS item — gives a URL, and video_id wants only the id. The regular expression in the Code step handles watch?v=, youtu.be/, Shorts, embed and live links. Without Code, use Formatter → Text → Extract Pattern with (?:v=|youtu\.be/|shorts/)([A-Za-z0-9_-]{11}).
Send a full URL, or a field that never resolved, and the API stops you before it spends anything:
HTTP 422
{
"detail": "video_id still contains an unsubstituted template placeholder ('{{123456__id}}'). Your automation sent the variable rather than its value — check the expression that builds this request."
}That second case happens when a Zap is tested before the trigger has a sample record: Zapier sends the raw field token. Pull in a real sample, re-test, and the token becomes an id. 422 is never charged.
The Zap: new channel video → summary in Slack or Notion
1. YouTube · New Video in Channel (trigger, polls the channel) 2. Code by Zapier · Run JavaScript (id extraction + transcript + join) 3. Filter by Zapier · only continue if Text · (Text) Exists 4. ChatGPT · Conversation (or Anthropic · Send Message) "Summarise in five bullets with the timestamp of each point:" + Step 2 · Text 5. Slack · Send Channel Message (or Notion · Create Page)
Step 3 matters: a video with captions off comes out of the Code step with an empty text, and the filter stops the Zap instead of sending an empty summary to the channel. Notion's page body is capped at 2,000 characters per block, which is fine for a summary and wrong for a whole transcript — post the summary and a watch link, not the text.
The YouTube trigger polls on Zapier's schedule for your plan. If you would rather be told, POST /channel/subscribe with a Webhooks by Zapier → Catch Hook URL delivers {channel_id, video_id} to the Zap within 15 minutes of an upload, free. Details and a FastAPI receiver are in the cookbook.
Failures, refunds and replays
Captions off, private, deleted or age-restricted videos have nothing to return. They come back as 404, the credit is refunded, and retryable is false:
HTTP 404
{
"detail": "This video has captions turned off, so there is no transcript to fetch. Your credits have been refunded.",
"error": "TranscriptsDisabled",
"hint": "Retrying will not help — the uploader has disabled captions for this video.",
"retryable": false,
"credits_refunded": true
}A Webhooks step treats any 4xx or 5xx as an error and halts the Zap, which is the right behaviour for 402 (balance empty) and 502/503 (YouTube refused, refunded, replay it from the Zap history in a few minutes). It is noisy for 404, which no replay will fix. The Code step above handles that: it returns an empty text on retryable: false and throws on everything else, so only failures worth replaying show up as errors.
Brand-new uploads are the special case: auto-generated captions can lag the video by minutes to hours. A Delay by Zapier step of 20 to 30 minutes after the trigger avoids most of those first-attempt NoTranscriptFound responses.
Cost per run
Zapier: one task per action step, so the outline above is four tasks per video (Filter counts, the trigger does not). TranscriptAPI: one credit per transcript. A cached repeat of the same video is free, a refunded failure is free, the channel webhook is free. Translation adds one credit per 40 segments, minimum two.
A channel that uploads daily uses about 30 credits a month. Accounts start with 20 free credits and no card; 2,500 credits are a $10 one-time top-up that never expires; the Production plan is $49 a month for 25,000. Full table on the pricing page.
What we do not do: download the video or audio, return comments or view counts, or manage a channel you own. If there is no caption track, there is no transcript, and you are not charged for finding that out.
Frequently asked questions
Does Zapier have a built-in YouTube transcript action?
No. Zapier's YouTube app offers triggers (new video in channel, in playlist, by search) and an upload action, all through the YouTube Data API — which does not return captions for videos you do not own. A Webhooks GET or a Code step calling TranscriptAPI is how you get the transcript.
Webhooks or Code — which one should I use?
Code, unless you already pay for a plan with Webhooks by Zapier and want a no-code Zap. The Code step extracts the id, fetches the transcript and joins the segments in one task, and the array of segments never has to survive Zapier's line-item flattening. Webhooks GET plus Formatter's Line-item to Text also works and needs no JavaScript.
What does one run cost?
Zapier counts each action step as a task, so the Zap above is four tasks per video (trigger steps are free). On our side it is one credit per transcript; a cached repeat is free and a failed video is refunded. Every account starts with 20 free credits; 2,500 more are $10, one-time, never expiring.
Why did I get 422 'unsubstituted template placeholder'?
The literal text of a mapped field reached the API instead of its value — typically a Zap tested before the trigger had sample data, or a field pasted as text. TranscriptAPI rejects anything containing {{ or ${ before contacting YouTube, so nothing is charged. Re-test the trigger so the field resolves.
The trigger fired but the transcript is missing. Why?
Auto-generated captions can trail an upload by minutes to hours, so the first fetch for a brand-new video may return 404 NoTranscriptFound — refunded. Add Delay by Zapier for 20 to 30 minutes after the trigger, or let the errored run be replayed from the Zap history.
Can I get the transcript in another language?
Add &translate_to=de (or any code or language name) to the query string. Translation costs one extra credit per 40 segments, minimum two. For summaries in another language it is cheaper to ask the model in step 4 to answer in that language.
The same integration in n8n and Make.com; in code, see LangChain. If an AI assistant should read the video directly, the MCP server needs no Zap at all. Get a free key with 20 credits at /login.