Fetch once, then go back to the Snapshot for more. Read the original HTML, the network requests, or the whole archive without fetching the page again.
Fetch gives your agent the useful part first: the Main Markdown and State JSON. The Snapshot keeps everything else from the capture, and each part is one public GET away, with no API key. That is progressive disclosure: start small, and read more only when you need it.
The trail runs from the answer to the bytes. The Fetch response names the Snapshot's catalog, meta.resources. The catalog lists collections, such as the network requests, and artifacts, such as the original HTML. A collection holds records, an artifact offers a bounded preview, and both end in the saved content.
The catalog serves two different jobs:
Every Fetch response names the Snapshot's catalog. Markdown text carries a more URL in its frontmatter. The JSON object carries the same URL in meta.resources. Follow it exactly as given.
{
"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, the public ID. It is not the meta.snapshotId that Highlights and the Playground use. See Two IDs.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 exchanges 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, or the HAR for every exchange |
| The original HTML, to re-parse with your own tools or diff against a later Fetch | artifacts/html.original/content |
| The files the page referenced: images, PDFs, scripts |
The catalog lists what was saved and where to read it:
import { ExpandClient } from '@expandai/sdk'
const expand = new ExpandClient()
const catalog = await expand.snapshots.get({ snapshotId: '<snapshot-link-id>' })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":
The links are paths on the API origin. The catalog has two kinds of entry:
{ items, next? }. A network or asset record carries the captured request and response headers, status, and timing, plus an artifact link to the HAR and a content link to the saved body. A record's url says where the bytes came from. It is never a download target.preview gives a bounded, base64-encoded look, and url gives the full content.content link, which returns the first match. For polling or repeated POSTs, read the HAR, which keeps every exchange. A preview answers 413 when the archive is over the server's budget. The content link still works, so fall through to the full download.With the TypeScript SDK, one fetchJson runs the browser and everything after reads from the Snapshot:
import { ExpandClient, isFetchOriginError } from '@expandai/sdk'
const expand = new ExpandClient()
// 1. The answer: metadata and Markdown. The only step that runs a browser.
const page = await expand.fetchJson({ url: 'https://example.com' })
if (isFetchOriginError(page) || !page.meta.resources) throw new Error('No Snapshot to read')
// 2. The catalog behind meta.resources: what else was saved. No API key needed.
const snapshotId = page.meta.resources.snapshotId
const catalog = await expand.snapshots.get({ snapshotId })
console.log(catalog.artifacts.map((artifact)
Main Markdown leaves out the clutter on purpose. Sometimes the thing the task needs went with it: an attribute value, a data- field, a number inside a script tag, a hidden element, a form default. The agent should not fetch the page again. The saved HTML is exact, already paid for, and one GET away.
# 1. The Markdown response names the catalog. 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 reading 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. Read 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.
html.original.Rules for this path:
GET the more URL exactly as written, with no auth header. Follow its links, and never guess routes.truncated and fall through to content when the preview is cut off or answers 413.url as where the bytes came from. Saved bytes come from the catalog's artifact links and a record's content link, never from the original URL.expandai skill install. It already teaches agents the more catalog step.snapshots methods.---
snapshotId: 019c25ee-1a2a-7d6d-9c1b-3f4e5a6b7c8d # the capture ID, for Highlights and the Playground
more: https://api.expand.ai/v1/public/snapshots/ng0EFjewTRF6?format=markdown # the public ID is in the URL
---collections.assets |
| The whole capture, as one archive | the wacz artifact |