Expand AI logo
DocsDocs
Glow Active
API Reference
Login

Documentation

Get started

IntroductionWhy ExpandQuickstartAuthenticationConnect your agent (MCP)

Products

OverviewFetching a URLBatched Fetch
Browser-rendered pagesYouTube videosPDFs and other files
API reference

SDKs & tools

TypeScript SDK
MCP tools & resourcesDocs for agents

Reference

API referenceErrors & retriesPricing & UsageLimitsConcurrency tiersFAQ
Browse docs

Get started

IntroductionWhy ExpandQuickstartAuthenticationConnect your agent (MCP)

Products

OverviewFetching a URLBatched Fetch
Browser-rendered pagesYouTube videosPDFs and other files
API reference

SDKs & tools

TypeScript SDK
MCP tools & resourcesDocs for agents

Reference

API referenceErrors & retriesPricing & UsageLimitsConcurrency tiersFAQ

YouTube videos

Fetch a YouTube video and get its transcript back as Markdown, with title, description, chapters, and timestamps.

Agents get sent to YouTube too. A conference talk, a tutorial, a product demo. The watch page itself is a player, a sidebar of recommendations, and a comment thread. Your agent needs what was said.

Fetch recognizes YouTube video URLs automatically. Send the video URL and get the transcript back as Markdown, with title, channel, description, and chapters, instead of rendering the watch page.

import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const markdown = await expand.fetch({ url: 'https://www.youtube.com/watch?v=aBcDeFgHiJk' })
expandai fetch
https://www.youtube.com/watch?v=aBcDeFgHiJk
// Arguments for the fetch tool
{
  "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk"
}
curl https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.youtube.com/watch?v=aBcDeFgHiJk"}'

Which URLs are recognized

Any YouTube URL associated with one video is converted to transcript Markdown. Everything else is fetched as an ordinary page.

youtube.com, www.youtube.com, m.youtube.com, music.youtube.com, and youtube-nocookie.com are all acceptable. Share noise in the query string (si, t, feature, a playlist list next to a v) is ignored. The video ID is what matters.

Playlists, channels, @handle pages, and search results carry no single video ID, so they render in a browser and come back as a page. See Browser-rendered pages.

FormTranscriptExample
Watch pageYeshttps://www.youtube.com/watch?v=aBcDeFgHiJk
Short linkYeshttps://youtu.be/aBcDeFgHiJk
ShortsYes

What comes back

A video comes back in either of Fetch's two modes, as Markdown text or as a JSON object. See Two modes.

Markdown Mode

Markdown Mode returns the transcript as Markdown text. The frontmatter has the same keys as any other page fetched as Markdown text, so no additional parsers are required.

import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const markdown = await expand.fetch({ url: 'https://www.youtube.com/watch?v=aBcDeFgHiJk' })
expandai fetch https://www.youtube.com/watch?v=aBcDeFgHiJk
curl https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json"
---
url: "https://www.youtube.com/watch?v=aBcDeFgHiJk"
capturedAt: "2026-09-08T10:15:00.000Z"
pageType: "video"
title: "Keynote: What changed in the browser this year"
author: "Example Conf"
createdAt: "2026-05-14"
image: "https://i.ytimg.com/vi/aBcDeFgHiJk/maxresdefault.jpg"
---

# Keynote: What changed in the browser this year

Example Conf · 41:07 · Published 2026-05-14 · 128,304 views
Transcript: captions (manual, en)

## Description

Opening keynote from Example Conf 2026. Slides: https://example.com/slides

## Transcript

### [0:00]







Reading it top to bottom:

  • Summary line. Channel, length, publish date, and view count. Anything YouTube doesn't report is left out.
  • Transcript: line. Where the text came from, such as captions (manual, en). See Transcription waterfall.
  • Description. The uploader's description, with links. Left out when there is none.
  • Chapters. ### [m:ss] Title headings, only when the uploader set chapters.
  • Paragraphs. Each starts with its timestamp, such as [0:42] or [1:02:03]. Sound-only captions like [Music] are dropped.

JSON Mode

JSON Mode returns the transcript as a JSON object. It has all the standard fields plus data.video, which holds the video's details as fields, so you don't have to pull them out of the Markdown.

import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const page = await expand.fetchJson({ url: 'https://www.youtube.com/watch?v=aBcDeFgHiJk' })
expandai fetch https://www.youtube.com/watch?v=aBcDeFgHiJk --format json
// Arguments for the fetch tool
{
  "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk"
{
  "meta": {
    "version": 1,
    "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
    "capturedAt": "2026-09-08T10:15:00.000Z",
    "snapshotId": "0f9c3a1e-...",
    "contentType": 























FieldDefinition
data.video.videoIdThe eleven-character ID, whichever URL form you sent.
data.video.title, channel, channelUrlAs YouTube reports them. channel and channelUrl are absent when unknown.
data.video.durationSecondsLength of the video, not of the transcript.
Note
A video isn't rendered in a browser, so it has no State JSON, Appendix, links, screenshot, or replay. Asking for them doesn't cause an error. json comes back as an empty list, and the other fields are left out.

To watch the video while you read the transcript, open the Fetch in the Playground. It shows the transcript next to an embedded player of the video.

Transcription waterfall

Fetch uses existing captions first and transcribes the audio only when there are none. It tries, in order:

Select one of the above steps to see how it is returned.

"data": {
  "video": {
    "videoId": "aBcDeFgHiJk",
    "language": "de",
    "transcript": {
      "source": "captions",
      "kind": "manual",
      "language": "de"
    }
  },
  "usage": { "videos": 1, "audioSeconds": 0 }
}
# Keynote: What changed in the browser this year

Example Conf · 41:07 · Published 2026-05-14 · 128,304 views
Transcript: captions (manual, de)

Both modes say where the transcript came from: the Transcript: line in Markdown text, and data.video.transcript in the JSON object.

For a German video, the first two steps return source: "captions", language: "de", and kind: "manual" or "auto". English captions use language: "en"; another uploaded track uses its own language. If Fetch transcribes the audio, it returns source: "transcription" with no kind, and data.usage.audioSeconds records the transcribed duration instead of 0.

FieldValuesDefinition
sourcecaptions or transcriptionWhether Fetch used a caption track from YouTube or transcribed the audio with a speech model.
kindmanual or autoWhen is , says whether the track is the creator's own () or YouTube's speech recognition ().
Note
You can't request a specific language yet. Fetch prefers the video's own language over a translation, so a German video with German captions comes back in German.

Additional credit usage

Fetching a YouTube video costs 6 credits ($0.006), whatever its length. That's 1 credit for the Fetch request plus 5 credits for the video, which cover the transcript, timestamps, chapters, and preview frames.

When a video has no caption track, Fetch transcribes the audio with a speech model, which charges an additional 10 credits per minute:

UnitCreditsWhen it applies
Video5 per videoEvery delivered transcript. One per Fetch, whatever its length.
Audio10 per minuteOnly when no caption track exists. Rounded up, one-minute minimum.

For example, a 25-minute video with captions costs 6 credits. The same video with no captions costs 256 credits.

With captions

Fetch
1
Video
5
Audio, 25 min × 10
250
Total
6 credits · $0.006

No captions

Fetch
1
Video
5
Audio, 25 min × 10
250
Total
256 credits · $0.256

You are only charged for a delivered result, never when a video can't be fetched, such as a private video or one longer than maxAudioSeconds. See Video errors.

data.usage reports videos and audioSeconds, so you can check how each Fetch was charged. See Pricing & Usage.

Limiting long transcriptions

When a video has no captions, Fetch transcribes the audio with a speech model. That's the only cost that grows with the video's length. You can cap it with the optional documentConfig.maxAudioSeconds field.

FieldDefinitionTypeDefaultRange
maxAudioSecondsRefuse the Fetch rather than transcribe a video longer than this.integer36001 to 14400
import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const markdown = await expand.fetch({
  url: 'https://youtu.be/aBcDeFgHiJk',
  documentConfig: { maxAudioSeconds: 900 },
})
curl https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json" \



  • When captions are available, Fetch ignores maxAudioSeconds. A video of any length comes back in full for the flat 6 credits, even a six-hour livestream replay.
  • When captions aren't available, Fetch checks the video's length against maxAudioSeconds before it downloads any audio. If the video is longer than the limit, the request is refused and you aren't charged.
Recommendation
Raise the limit only as far as the videos you expect. The ceiling is four hours (14400 seconds).

A transcription that is within the limit can still take a while.

Note
Transcription can run for up to 55 minutes once it starts. Only the wait for a worker uses the 150-second budget. Set your HTTP client timeout to allow a long transcription to finish.

Batched Fetch

Batched Fetch doesn't return YouTube transcripts yet. It renders the watch page in a browser and returns it as a standard page, with no transcript. Send video URLs to POST /v1/fetch or POST /v1/fetch/json until this lands.

Video errors

On top of the standard Fetch errors, a video Fetch can return three more errors. Their full payloads are in the API reference.

_tagCodeRetryable
FetchVideoUnavailable422Sometimes
FetchDocumentProcessingUnavailable503Yes

FetchVideoUnavailable

YouTube won't serve this video, so there's no transcript to return.

HTTP 422Sometimes retryable
{
  "_tag": "FetchVideoUnavailable",
  "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
  "availability": "live",
  "message": "This live event will begin in 2 hours"
}

The availability field explains why the video is unavailable, and whether it's retryable:

ValueDefinitionRetryable
privatePrivate.No
removedDeleted, or its account was terminated.No
geoRestrictedNot available from Expand's region.

This error is not retryable, except when availability is live or upcoming. Fetch those again after the stream ends, once YouTube has processed the replay.

FetchDocumentProcessingUnavailable

YouTube refused the request, or the speech model couldn't finish.

HTTP 503Retryable
{
  "_tag": "FetchDocumentProcessingUnavailable",
  "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
  "operation": "video",
  "message": "YouTube refused the request"
}

The operation field is set to video when YouTube refused the request, such as a bot check or a rate limit, and transcription when the speech model couldn't finish.

The TypeScript SDK retries this error automatically, with backoff.

FetchPageTooLarge

The video has no captions and is longer than maxAudioSeconds.

HTTP 413Not retryable
{
  "_tag": "FetchPageTooLarge",
  "url": "https://www.youtube.com/watch?v=aBcDeFgHiJk",
  "limitKind": "audio_seconds",
  "limit": 3600,
  "measured": 5400
}

For a video, the limitKind field is set to audio_seconds, and limit and measured are in seconds.

To fetch the video, increase the maxAudioSeconds limit, up to the ceiling of four hours (14400 seconds). A video longer than that can't be transcribed. See Limiting long transcriptions.

Next steps

  • PDFs and other files: PDFs, images, office files, ebooks, and audio on the same endpoint.
  • Fetching a URL: choose what a Fetch returns.
  • Pricing & Usage: see what transcribed minutes cost in credits.
  • Errors & retries: which errors to retry, and what you are charged for.
PreviousBrowser-rendered pages
NextPDFs and other files

On This Page

Which URLs are recognizedWhat comes backMarkdown ModeJSON ModeTranscription waterfallAdditional credit usageLimiting long transcriptionsBatched FetchVideo errorsFetchVideoUnavailableFetchDocumentProcessingUnavailableFetchPageTooLargeNext steps
https://www.youtube.com/shorts/aBcDeFgHiJk
Live replayYeshttps://www.youtube.com/live/aBcDeFgHiJk
EmbedYeshttps://www.youtube.com/embed/aBcDeFgHiJk
Legacy playerYeshttps://www.youtube.com/v/aBcDeFgHiJk
PlaylistNohttps://www.youtube.com/playlist?list=PLaBcDeFgHiJk
ChannelNohttps://www.youtube.com/@exampleconf
Search resultsNohttps://www.youtube.com/results?search_query=keynote
\
-d '{"url":"https://www.youtube.com/watch?v=aBcDeFgHiJk"}'
Welcome
[0:04] Good morning, everyone. Thanks for being here so early on a Thursday.
[0:21] I want to start with a number. Last year, 38 percent of the pages we measured shipped a table the crawler never saw.
### [2:15] The rendering gap
[2:15] Here is what that looks like in practice. ...
}
curl https://api.expand.ai/v1/fetch/json \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.youtube.com/watch?v=aBcDeFgHiJk"}'
"text/html"
},
"markdown": "---\nurl: \"https://www.youtube.com/watch?v=aBcDeFgHiJk\"\n...",
"json": [],
"data": {
"video": {
"provider": "youtube",
"videoId": "aBcDeFgHiJk",
"title": "Keynote: What changed in the browser this year",
"channel": "Example Conf",
"channelUrl": "https://www.youtube.com/@exampleconf",
"durationSeconds": 2467,
"publishedAt": "2026-05-14",
"viewCount": 128304,
"language": "en",
"transcript": { "source": "captions", "kind": "manual", "language": "en" }
},
"document": {
"url": "https://s3.amazonaws.com/.../document/original.json?X-Amz-Signature=...",
"contentType": "application/json",
"sizeBytes": 48211
},
"usage": { "documentPages": 0, "ocrPages": 0, "audioSeconds": 0, "videos": 1 }
}
}
data.video.publishedAtUpload date as an ISO calendar date.
data.video.viewCountView count at capture time.
data.video.languageThe video's primary spoken language, when YouTube or the transcript reports one.
data.video.transcriptsource, kind, and language of the transcript. See Transcription waterfall.
data.video.storyboardPreview frames sampled through the video, as sprite sheet images. Absent when YouTube doesn't publish them.
data.document.urlA presigned link to what the transcript was made from: the caption track as JSON, or the audio that was transcribed.
data.usage.audioSecondsSeconds of audio transcribed. 0 on the captions path.
meta.urlAlways the canonical watch?v= URL.
meta.contentTypetext/html, because that is what YouTube served. The transcript is Expand's rendering of it, the same way a PDF's Markdown is.
source
captions
manual
auto
languageA BCP-47 language tag (en, de, pt-BR)The transcript's language, when known.
-d '{
"url": "https://youtu.be/aBcDeFgHiJk",
"documentConfig": { "maxAudioSeconds": 900 }
}'
FetchPageTooLarge413No
No
ageRestrictedBehind an age gate that needs a signed-in account.No
membersOnlyReserved for channel members or Premium subscribers.No
liveStreaming right now. There is no transcript until it ends.Yes
upcomingA scheduled premiere or stream that has not started.Yes
unknownWithheld for a reason YouTube did not name.No