CrawlForge MCP
Structured Extraction2 credits

extract_embedded_state

Modern sites serialize the data their UI renders straight into the document. This reads it back: one fetch, exact values, and no model anywhere in the extraction path.

Use Cases

Exact prices from JavaScript-rendered pages

Prices, stock levels and IDs come from the site's own state rather than from a model reading rendered text, so there is nothing to fabricate.

Listings a plain scrape cannot see

Search results and product grids that render client-side are usually already present in __NEXT_DATA__ or an RSC payload on the first response.

Cheaper than an LLM extraction

2 credits against 3 for extract_with_llm or extract_structured, with no inference step to wait for.

Auditing what a site publishes about itself

found reports every state source on the page and its size, which is often more than the visible UI shows.

Endpoint

POST/api/v1/tools/extract_embedded_state
Auth Required
1 req/s on Free plan
2 credits

Parameters

NameTypeRequiredDefaultDescription
url
stringRequired-
Page to read embedded state from.
Example: https://example.com/products/widget
path
stringOptional-
Return one subtree instead of the whole payload. Dotted keys and array indexes only - this is not JSONPath, so there are no wildcards, filters, slices or recursive descent. A path that does not resolve comes back as a 400 naming the keys that were available where it stopped, and costs no credits.
Example: next_data.props.pageProps
user_agent
stringOptional-
Override the User-Agent sent to the target. The request stays signed as CrawlForge; the signature covers the authority, not this header.
Example: MyCompanyBot/1.0
respect_robots
booleanOptionaltrue
Respect the target site's robots.txt. Left at `true`, a path disallowed for `CrawlForge` is refused with 403 before anything is fetched and no credits are charged. Set it to `false` only for a target you have your own agreement with - the response then carries a `warnings` entry and the override is recorded against your API key.
Example: true
timeout
numberOptional20000
Fetch timeout in milliseconds, between 1000 and 60000. State payloads are often megabytes, so the default is higher than on the lighter extraction tools.
Example: 20000

Request Examples

cURL - Read the whole state

terminalBash
curl -X POST https://crawlforge.dev/api/v1/tools/extract_embedded_state \
  -H "X-API-Key: cf_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/products/widget"
  }'

TypeScript - Scope it with a path

extractEmbeddedState.tsTypescript
const response = await fetch('https://crawlforge.dev/api/v1/tools/extract_embedded_state', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.CRAWLFORGE_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example.com/products/widget',
    // Dotted keys and array indexes. Not JSONPath: no wildcards or filters.
    path: 'next_data.props.pageProps',
  }),
});

const payload = await response.json();
if (!response.ok) {
  // A path that does not resolve is a 400 naming the keys that were available.
  throw new Error(payload.error.code + ': ' + payload.error.message);
}

const result = payload.data;

// Every state source on the page, largest first, whether or not it was scoped.
for (const source of result.found) {
  console.log(source.name, source.variable, source.bytes);
}

// The values are the site's own, so they can be used as they are.
console.log(result.data.product.price);

Python - Discover, then scope

extract_embedded_state.pyPython
import os
import requests

URL = 'https://crawlforge.dev/api/v1/tools/extract_embedded_state'
HEADERS = {
    'X-API-Key': os.environ['CRAWLFORGE_API_KEY'],
    'Content-Type': 'application/json',
}

# 1. Discover what the page carries. Nothing is truncated, so this can be big.
discovery = requests.post(
    URL,
    headers=HEADERS,
    json={'url': 'https://example.com/products/widget'},
).json()

for source in discovery['data']['found']:
    print(source['name'], source['variable'], source['bytes'])

for warning in discovery['data']['warnings']:
    print('warning:', warning)

# 2. Ask again for just the branch you want.
scoped = requests.post(
    URL,
    headers=HEADERS,
    json={
        'url': 'https://example.com/products/widget',
        'path': 'next_data.props.pageProps.product',
    },
)

payload = scoped.json()
if not scoped.ok:
    error = payload['error']
    raise RuntimeError(f"{error['code']}: {error['message']}")

print(payload['data']['bytes'], 'bytes')
print(payload['data']['data'])

Response Example

200 OK980ms
{
"success": true,
"data": {
"url": "https://example.com/products/widget",
"found": [
{
"name": "next_data",
"variable": "__NEXT_DATA__",
"bytes": 412880
}
],
"path": null,
"bytes": 412893,
"data": {
"next_data": {
"buildId": "KfC_3GF1zuM",
"props": {
"pageProps": {
"product": {
"sku": "WID-9001",
"price": 149.99,
"currency": "USD",
"inStock": true
}
}
}
}
},
"warnings": [
"Result is 412893 bytes; \"next_data\" alone is 412880. Re-run with path to scope it, e.g. path:\"next_data.props\"."
]
},
"credits_used": 2,
"credits_remaining": 998,
"processing_time": 980
}
Field Descriptions
data.foundEvery state source on the page, with the raw thing it was read from and its serialized size
data.pathThe path that was applied, or null when the whole payload was returned
data.bytesSerialized size of what is being returned - after scoping, when a path was given
data.dataThe state itself, keyed by source name
data.warningsSources that were present but not parseable as JSON, and the size hint that suggests a path

Error Handling

Path did not resolve (400 Bad Request)

The path does not exist in the extracted state. The message names the point it stopped at and the keys that were available there, so a typo comes back fixable. No credits are charged. Run the call once without a path to see what the page actually carries.

Blocked by robots.txt (403 Forbidden)

The target site's robots.txt disallows this path for CrawlForge. Set respect_robots: false to override if you have your own agreement with the target - the override is recorded against your API key. The override does not reach a host on CrawlForge's permanent opt-out list, which is refused whatever respect_robots is set to.

Response too large (413 Payload Too Large)

The page's HTML exceeds the 25MB body limit. The limit is on the page as served, not on the state extracted from it. Nothing is charged.

Target did not respond (504 Gateway Timeout)

The site did not answer within timeout. State-heavy pages are large; raise timeout toward its 60000 ceiling before treating this as a failure. Nothing is charged.

Related Tools

extract_structured
Schema-driven extraction when the data is in the markup, not in a state blob (3 credits)
extract_with_llm
Natural-language extraction for pages that serialize nothing (3 credits)
extract_metadata
JSON-LD, OpenGraph and Twitter Card tags, which this tool deliberately leaves alone (1 credit)
Ready to read a page's own data? Sign up for free and get 1,000 credits.

Footer

CrawlForge MCP

Enterprise web scraping for AI Agents. 29 specialized MCP tools designed for modern developers building intelligent systems.

Product

  • Features
  • Playground
  • Pricing
  • Use Cases
  • Integrations
  • Alternatives
  • Changelog

Resources

  • Getting Started
  • API Reference
  • Templates
  • Guides
  • Blog
  • Glossary
  • FAQ
  • Sitemap

Developers

  • MCP Protocol
  • Claude Desktop
  • Cursor IDE
  • LangChain
  • LlamaIndex

Company

  • About
  • Contact
  • Privacy
  • Terms
  • Acceptable Use
  • Cookies

Stay updated

Get the latest updates on new tools and features.

Built with Next.js and MCP protocol

© 2025-2026 CrawlForge. All rights reserved.