Fetch once, then go back to the saved snapshot for more context, or let an agent verify against the original HTML, without refetching the page.
Fetch returns the useful working context first. One capture also saves the original HTML, a HAR,
a WACZ archive, evidence, network records, and assets, and none of that is in the first response.
Every layer is one plain GET away, keyed by the snapshot, and public snapshot reads need no API key.
answer → catalog → collection → record → preview → contentThat serves two different jobs, and they work differently:
Markdown frontmatter carries one more URL. JSON Mode carries the same landing page in
meta.resources. Follow the URL exactly as supplied.
---
snapshotId: 01JPUBLIC01
more: https://api.expand.ai/v1/public/snapshots/01JPUBLIC01?format=markdown
---{
"meta": {
"resources": {
"version": 1,
"snapshotId": "01JPUBLIC01",
"url": "https://api.expand.ai/v1/public/snapshots/01JPUBLIC01",
"markdownUrl": "https://api.expand.ai/v1/public/snapshots/01JPUBLIC01?format=markdown"
meta.resources.snapshotId is the public snapshot-link ID used by every URL below. meta.snapshotId
is the capture ID used by Highlights and the Playground; they are not interchangeable.
Store meta.resources.snapshotId next to each fetch result. Hours or days later, everything the
capture saved is still readable by that ID, with no API key and no new browser session.
| You need | Read |
|---|---|
| The HTTP exchange behind the page, to debug a bad capture or replay it elsewhere | artifacts/har/content |
| The JSON API a page loads, so you can call it directly next time | collections.network for the request; the HAR for every exchange |
| The source HTML, to re-parse with your own tooling or diff against a later fetch | artifacts/html.original/content |
| Files the page referenced: images, PDFs, scripts |
The catalog lists what was saved and where to read it. Links are paths on the API origin.
curl 'https://api.expand.ai/v1/public/snapshots/<snapshot-link-id>'{
"version": 1,
"snapshotId": "01JPUBLIC01",
"collections": {
"evidence": "/v1/public/snapshots/01JPUBLIC01/evidence",
"network": "/v1/public/snapshots/01JPUBLIC01/network",
"assets":
{ items, next? }. Network and asset records carry the captured request
and response headers, status, and timing, plus an artifact link to the HAR and a content link
that resolves the saved body by URL. Requests to the same URL share one content link and it
returns the first match, so for polling or repeated POSTs read the HAR, which keeps every
exchange. A record's url is provenance, never a download target.preview for a bounded, base64-encoded look; use url for the
full content. Previews answer HTTP 413 when the capture archive is over the server budget; the
content link still works, so fall through to the full download in that case.With the TypeScript SDK, one fetchJson runs the browser and the rest reads back from the snapshot:
import { ExpandClient, isFetchOriginError } from "@expandai/sdk"
const client = new ExpandClient() // reads EXPAND_API_KEY
// 1. The answer: metadata + Markdown. The only step that runs a browser.
const page = await client.fetchJson({ url: "https://example.com" })
if (isFetchOriginError(page) || !page.meta.resources) throw new Error("no snapshot to disclose")
// 2. The public catalog behind meta.resources: what else was saved. No API key needed.
const snapshotId = page.meta.resources.snapshotId
const catalog = await client.snapshots.get({ snapshotId })
The same walkthrough ships as a runnable script in the SDK package at
packages/sdk-node/examples/progressive-disclosure.ts.
Main Markdown is pruned on purpose. Sometimes the thing the task needs was pruned with it: an
attribute value, a data- field, a number inside a script tag, a hidden element, a form default.
The agent should not refetch the page. The saved HTML is exact, already paid for, and one GET away.
# 1. The Markdown response names the catalog (the value is quoted). No API key for anything below.
MORE=$(sed -n 's/^more: //p' page.md | tr -d '"')
curl -sS "$MORE"
# 2. Look at the first bytes of the original HTML before committing to all of it.
curl -sS "https://api.expand.ai/v1/public/snapshots/<snapshot-link-id>/artifacts/html.original/preview?maxBytes=4096" \
| jq -r .body | base64 -d
# 3. Pull the full source and search it.
curl -sS "https://api.expand.ai/v1/public/snapshots/<snapshot-link-id>/artifacts/html.original/content"
If the value was loaded by an XHR rather than rendered into the HTML, list collections.network
to find the request, then read its content link for the saved body. If the page hit that URL more
than once, read the HAR instead; content resolves by URL and returns the first match.
This is a different tool from Highlights. expandai search <snapshotId> <query> and the fetch_search
MCP tool find passages that are in the Markdown and State JSON but were too long to read. The HTML is
not in that corpus. When the Markdown does not contain it at all, go to html.original.
Rules for this path. The Expand skill installed by
expandai skill install already teaches the more catalog step:
GET the more URL exactly as written, with no auth header. Follow its links; never guess routes.truncated and fall through to content when the preview is cut off
or answers 413.url fields as provenance. Saved bytes come from the catalog's artifact links and a
record's content link, never from the original URL.snapshots methods.collections.assets |
| A self-contained archive of the whole capture | the wacz artifact |