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

PDFs and other files

Fetch a PDF, image, office file, ebook, data file, or audio file, and get the same agent-ready Markdown back.

Not every URL an agent follows is a web page. Research trails end in PDFs, data links end in spreadsheets, and a citation chain will happily drop you on a scanned report.

Fetch converts those files on the endpoint you already call and returns the same Markdown. There is nothing to turn on. POST /v1/fetch decides for itself whether a URL is a page to render or a file to convert.

import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const markdown = await expand.fetch({ url: 'https://arxiv.org/pdf/1706.03762' })
expandai 
fetch
https://arxiv.org/pdf/1706.03762
// Arguments for the fetch tool
{
  "url": "https://arxiv.org/pdf/1706.03762"
}
curl https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://arxiv.org/pdf/1706.03762"}'

What Fetch converts

FamilyFormatsHow it convertsExtra credits
PDF.pdfFrom the text layer. Pages with no usable text go to a vision model.Per page, plus vision-model pages.
Images.png, .jpg, .jpeg, , , , , ,

Everything else, including HTML and SVG, is rendered in a browser and captured as a page. See Browser-rendered pages. A YouTube video is its own case and comes back as a transcript. See YouTube videos.

URLs without an extension

The extension is only a hint. Fetch looks at what the origin serves, so https://arxiv.org/pdf/1706.03762 converts as a PDF even though the URL has no extension, and a .pdf URL that serves a web page is captured as a page. A spreadsheet served as application/octet-stream still converts.

Note
A URL with no recognizable extension that serves text/plain is captured as a page. Give it a .txt or .md extension, or serve text/markdown, to get the text back as is.

What comes back

A file 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 converted file as Markdown text. It is the document itself, with no frontmatter added. Details such as the content type and the link to the original file are only in the JSON object.

import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const markdown = await expand.fetch({ url: 'https://arxiv.org/pdf/1706.03762' })
expandai fetch https://arxiv.org/pdf/1706.03762
curl https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json"
# Attention Is All You Need

## Abstract

The dominant sequence transduction models are based on complex recurrent or convolutional neural networks ...

JSON Mode

JSON Mode returns the converted file as a JSON object. It has the standard fields, plus data.document, a link to the original file, and data.usage, what the conversion cost.

import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const page = await expand.fetchJson({ url: 'https://arxiv.org/pdf/1706.03762' })
expandai fetch https://arxiv.org/pdf/1706.03762 --format json
// Arguments for the fetch tool
{
  "url": "https://arxiv.org/pdf/1706.03762"
{
  "meta": {
    "version": 1,
    "url": "https://arxiv.org/pdf/1706.03762",
    "capturedAt": "2026-09-01T00:00:00.000Z",
    "snapshotId": "0f9c3a1e-...",
    "contentType": 











FieldDefinition
markdownThe whole converted document.
meta.contentTypeThe detected content type. It can differ from what the origin's header said.
meta.snapshotIdThe ID of the stored conversion. There is no page to replay.
data.document.url
Note
A file 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. The Playground previews the converted document instead.

Additional credit usage

Fetching a file costs the usual 1 credit, plus credits for the work of converting it:

UnitCreditsWhen it applies
Converted page1 per pageEvery page a conversion delivers. An image counts as one page.
Vision-model page5 per page, on top of the pageA page read by the vision model instead of from a text layer.
Audio per minute

Office files, ebooks, data files, and text convert locally and add nothing. Those Fetches cost the flat 1 credit.

For example, a 12-page PDF with a text layer costs 13 credits. The same document scanned, with no text layer, costs 73 credits.

You are only charged for a delivered result, never when a file can't be converted. data.usage reports the units each Fetch spent, so you can check how it was charged. See Pricing & Usage.

Limiting what gets converted

documentConfig caps the work a conversion can do, and so what it can cost. Every field is optional.

FieldDefinitionDefaultRange
maxPagesRefuse the Fetch rather than convert a PDF with more pages than this.No limitAny positive integer
maxAudioSecondsRefuse the Fetch rather than transcribe audio longer than this.

The ocr modes:

  • auto keeps the readable text from each page and sends pages with missing or unreliable text to the vision model.
  • never keeps the vision model out. It is the cheapest and most predictable setting. It also returns an empty document for a scanned PDF, and nothing at all for an image, since an image has no text layer to fall back on.
  • always reads every page with the vision model, even where a text layer exists.
import { ExpandClient } from '@expandai/sdk'

const expand = new ExpandClient()

const markdown = await expand.fetch({
  url: 'https://www.irs.gov/pub/irs-pdf/fw4.pdf',
  documentConfig: { maxPages: 10, ocr: 'never' },
})
curl https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H



Warning
A file over one of these limits is refused with 413 FetchPageTooLarge, not cut short. A half-converted document is worse than a clear error, because nothing in the Markdown says where it stopped. A refused Fetch costs nothing.

A conversion that is within the limits can still take a while.

Note
Conversion 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 conversion to finish. Downloads also have a size cap, and image and audio conversion depend on their providers' limits.

Batched Fetch

Batched Fetch doesn't convert files yet. It renders every URL in a browser, so a file URL comes back as a failed item rather than as Markdown. The batched endpoint accepts documentConfig but ignores it. Send file URLs to POST /v1/fetch or POST /v1/fetch/json until this lands.

Document errors

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

_tagCodeRetryable
FetchUnsupportedContentType415No
FetchOriginHttpError404, 410, or

FetchUnsupportedContentType

The file can't be converted. Its format is unknown, or it is too damaged to read.

HTTP 415Not retryable
{
  "_tag": "FetchUnsupportedContentType",
  "url": "https://example.com/archive.7z",
  "contentType": "application/x-7z-compressed"
}

contentType is present when Fetch could detect one.

FetchOriginHttpError

The origin refused to serve the file. statusCode is the status it answered with.

HTTP 404, 410, or 502Sometimes retryable
{
  "_tag": "FetchOriginHttpError",
  "url": "https://example.com/report.pdf",
  "statusCode": 404
}

A missing or removed file comes back as 404 or 410, and any other refusal as 502. Only the 502 is worth retrying, and the SDK retries it for you.

A page Fetch works differently. When a page's origin answers with an error, Fetch renders that error page and reports the status beside it, because an error page is often the answer you needed. A file has no page to render, so the origin's status comes back as the error.

Note
An upcoming change reports these refusals as 200 FetchOriginError with contentAvailable: false, originStatusCode, and a retryable verdict. Support both shapes during the transition.

FetchDocumentProcessingUnavailable

The vision model or the speech model couldn't finish.

HTTP 503Retryable
{
  "_tag": "FetchDocumentProcessingUnavailable",
  "url": "https://example.com/scan.pdf",
  "operation": "ocr",
  "message": "OCR provider unavailable"
}

The operation field is set to ocr when the vision model couldn't finish, and transcription when the speech model couldn't finish. The SDK retries this error automatically, with backoff.

FetchPageTooLarge

The file is over one of your documentConfig limits.

HTTP 413Not retryable
{
  "_tag": "FetchPageTooLarge",
  "url": "https://example.com/book.pdf",
  "limitKind": "document_pages",
  "limit": 50,
  "measured": 312
}

limitKind says which limit: document_pages for maxPages, or audio_seconds for maxAudioSeconds. limit and measured are in that unit. To convert the file, raise the limit. See Limiting what gets converted.

Next steps

  • YouTube videos: video transcripts on the same endpoint.
  • Fetching a URL: choose what a Fetch returns.
  • Playground & replay: inspect a converted document next to its original.
  • Pricing & Usage: what converted pages and audio cost in credits.
  • Errors & retries: which errors to retry, and what you are charged for.
PreviousYouTube videos
NextIris

On This Page

What Fetch convertsURLs without an extensionWhat comes backMarkdown ModeJSON ModeAdditional credit usageLimiting what gets convertedBatched FetchDocument errorsFetchUnsupportedContentTypeFetchOriginHttpErrorFetchDocumentProcessingUnavailableFetchPageTooLargeNext steps
.gif
.webp
.avif
.bmp
.tif
.tiff
A vision model reads the text and describes the image.
One vision-model page.
Office.doc, .docx, .docm, .ppt, .pptx, .pptm, .pps, .ppsx, .ppsm, .pot, .xls, .xlsx, .xlsm, .xlsb, .odt, .ods, .odp, .rtfLocally. Spreadsheets become Markdown tables with their number formats intact.None
Ebooks.epubLocally, chapter by chapter.None
Data.csv, .tsv, .json, .xml, .rdfLocally. CSV and TSV become tables. JSON and XML keep their structure.None
Text.txt, .text, .md, .markdownMarkdown comes back as is. Plain text is fenced so its whitespace survives.None
Audio.mp3, .wav, .m4a, .aac, .ogg, .oga, .opus, .flacTranscribed with a speech model.Per minute.
\
-d '{"url":"https://arxiv.org/pdf/1706.03762"}'
}
curl https://api.expand.ai/v1/fetch/json \
  -H "x-expand-api-key: $EXPAND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://arxiv.org/pdf/1706.03762"}'
"application/pdf"
},
"markdown": "# Attention Is All You Need\n\n...",
"json": [],
"data": {
"document": {
"url": "https://s3.amazonaws.com/.../document/original.pdf?X-Amz-Signature=...",
"contentType": "application/pdf",
"sizeBytes": 2215244
},
"usage": { "documentPages": 15, "ocrPages": 0, "audioSeconds": 0, "videos": 0 }
}
}
A presigned link to the original file, for sharing or downloading it.
data.document.sizeBytesThe size of the original, as downloaded.
data.usageThe pages converted, pages read by the vision model, and seconds of audio transcribed. See Additional credit usage.
10
Audio files. Rounded up, one-minute minimum.
3600
1 to 14400
ocrWhether pages may go to the vision model.autoauto, never, always
"Content-Type: application/json"
\
-d '{
"url": "https://www.irs.gov/pub/irs-pdf/fw4.pdf",
"documentConfig": { "maxPages": 10, "ocr": "never" }
}'
502
Sometimes
FetchDocumentProcessingUnavailable503Yes
FetchPageTooLarge413No