---
title: "Return the JSON state a page already ships in its own HTML: __NEXT_DATA__, React Server Component flight chunks (self.__next_f, \"$<id>\" references between rows resolved, plus a data_rows index of the rows carrying data), __NUXT__ and Nuxt 3's __NUXT_DATA__ (decoded), SvelteKit kit.start data, __APOLLO_STATE__, __INITIAL_STATE__, __PRELOADED_STATE__, ytInitialData, ytInitialPlayerResponse, __remixContext and other window globals (JSON, or a JavaScript literal evaluated statically — no page code runs), Inertia data-page, Shopify meta and product JSON, JSON data-* attributes and <script type=\"application/json\"> blocks. One fetch, exact values, and no LLM in the extraction path — the numbers come from the site's own serialized state, so they cannot be fabricated. Sources are reported in \"found\" with their sizes; use \"path\" to return one subtree instead of a multi-megabyte blob, keys_only to see its shape first, or find to list the paths of one key. A bot-defence wall or an empty shell carrying no state returns success: false, unbilled; with escalate: true the same call renders the page once in a stealth browser and reads the state from that instead."
method: POST
path: "/tools/extract_embedded_state"
tags: ["tools"]
---

# Return the JSON state a page already ships in its own HTML: __NEXT_DATA__, React Server Component flight chunks (self.__next_f, "$<id>" references between rows resolved, plus a data_rows index of the rows carrying data), __NUXT__ and Nuxt 3's __NUXT_DATA__ (decoded), SvelteKit kit.start data, __APOLLO_STATE__, __INITIAL_STATE__, __PRELOADED_STATE__, ytInitialData, ytInitialPlayerResponse, __remixContext and other window globals (JSON, or a JavaScript literal evaluated statically — no page code runs), Inertia data-page, Shopify meta and product JSON, JSON data-\* attributes and <script type="application/json"> blocks. One fetch, exact values, and no LLM in the extraction path — the numbers come from the site's own serialized state, so they cannot be fabricated. Sources are reported in "found" with their sizes; use "path" to return one subtree instead of a multi-megabyte blob, keys_only to see its shape first, or find to list the paths of one key. A bot-defence wall or an empty shell carrying no state returns success: false, unbilled; with escalate: true the same call renders the page once in a stealth browser and reads the state from that instead.

`POST /tools/extract_embedded_state`

## Request

- Base URL: `https://www.crawlforge.dev/api/v1`
- URL: `https://www.crawlforge.dev/api/v1/tools/extract_embedded_state`
- Auth: API key in header `X-API-Key`

## Request body

- union
  - object
    - `url` string, uri, required: URL to read embedded state from
    - `path` string: Return one subtree instead of everything, e.g. "next_data.props.pageProps" or "json_scripts[0].data". Dotted keys and array indexes only — not JSONPath: no wildcards, filters, slices or recursive descent. A path that does not resolve is a 400 naming the keys that were available at the point it stopped, and costs no credits.
    - `user_agent` string: Override the User-Agent sent to the target
    - `respect_robots` boolean: Honour the target's robots.txt. Setting this to false is recorded against your API key.
    - `timeout` integer: Fetch timeout in ms (1000-60000)
    - `keys_only` false: Return keys instead of data: the selected data (after path, if given) described two levels deep, each leaf labelled "object", "array(<n>)", "string", "number", "boolean" or "null", and an array as { length, first } with the shape of its first element. found and bytes are unchanged, so one cheap call shows which path to ask for.
    - `find` string: Return `matches` instead of `data`: every property with this key name (case-insensitive) anywhere in the selected data (after `path`), in document order, each as {path, preview} with the first 200 characters of its value - at most 50, with `matches_total` and `matches_truncated`. Each match path already starts with `path`, so it can be passed straight back as `path`. Discover where a field lives without downloading the payload. Cannot be combined with keys_only
    - `raw` boolean: Also keep the undecoded __NUXT_DATA__ devalue array under json_scripts, beside the decoded nuxt_data. Default: false
    - `escalate` boolean: Retry a blocked page in a stealth browser, in this same call. The plain fetch always runs first; only a bot wall, an empty shell with no state, or a 403, 429 or 444 escalates — never a 404 or a 5xx. The rendered HTML goes through the same parser. The response carries escalated: true|false whenever this is set, plus stealth: { engine, vendor_detected } when the render ran. It costs the escalation add-on only when it ran. The stage needs the CrawlForge execution backend: where that is not configured it returns 503 TOOL_NOT_AVAILABLE and charges nothing. robots.txt is respected on the escalated path too. Reading window globals (window_state) is MCP-server only: the render here returns HTML.
    - `escalate_engine` 'playwright' | 'camoufox': Browser engine for the escalated render (camoufox only where installed on the backend). Ignored when no escalation runs.
    - `wait_for` integer: Extra wait after page load for the escalated render, in ms (0-30000), passed to stealth_mode for state that is written after DOMContentLoaded. Ignored when no escalation runs.
    - `max_inline_chars` integer: Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit). Here the response keeps url, path, bytes, found, data_keys (the top-level keys of data) and escalated/stealth, and preview is the head of the pretty-printed JSON cut at a line boundary, so no value is ever split and the response stays within max_inline_chars; read_result json_path reads any field of the stored result, e.g. "data.next_data.props".
  - object
    - `url` string, uri, required: URL to read embedded state from
    - `path` string: Return one subtree instead of everything, e.g. "next_data.props.pageProps" or "json_scripts[0].data". Dotted keys and array indexes only — not JSONPath: no wildcards, filters, slices or recursive descent. A path that does not resolve is a 400 naming the keys that were available at the point it stopped, and costs no credits.
    - `user_agent` string: Override the User-Agent sent to the target
    - `respect_robots` boolean: Honour the target's robots.txt. Setting this to false is recorded against your API key.
    - `timeout` integer: Fetch timeout in ms (1000-60000)
    - `keys_only` boolean: Return keys instead of data: the selected data (after path, if given) described two levels deep, each leaf labelled "object", "array(<n>)", "string", "number", "boolean" or "null", and an array as { length, first } with the shape of its first element. found and bytes are unchanged, so one cheap call shows which path to ask for.
    - `find` string: Return `matches` instead of `data`: every property with this key name (case-insensitive) anywhere in the selected data (after `path`), in document order, each as {path, preview} with the first 200 characters of its value - at most 50, with `matches_total` and `matches_truncated`. Each match path already starts with `path`, so it can be passed straight back as `path`. Discover where a field lives without downloading the payload. Cannot be combined with keys_only
    - `raw` boolean: Also keep the undecoded __NUXT_DATA__ devalue array under json_scripts, beside the decoded nuxt_data. Default: false
    - `escalate` boolean: Retry a blocked page in a stealth browser, in this same call. The plain fetch always runs first; only a bot wall, an empty shell with no state, or a 403, 429 or 444 escalates — never a 404 or a 5xx. The rendered HTML goes through the same parser. The response carries escalated: true|false whenever this is set, plus stealth: { engine, vendor_detected } when the render ran. It costs the escalation add-on only when it ran. The stage needs the CrawlForge execution backend: where that is not configured it returns 503 TOOL_NOT_AVAILABLE and charges nothing. robots.txt is respected on the escalated path too. Reading window globals (window_state) is MCP-server only: the render here returns HTML.
    - `escalate_engine` 'playwright' | 'camoufox': Browser engine for the escalated render (camoufox only where installed on the backend). Ignored when no escalation runs.
    - `wait_for` integer: Extra wait after page load for the escalated render, in ms (0-30000), passed to stealth_mode for state that is written after DOMContentLoaded. Ignored when no escalation runs.
    - `max_inline_chars` integer: Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit). Here the response keeps url, path, bytes, found, data_keys (the top-level keys of data) and escalated/stealth, and preview is the head of the pretty-printed JSON cut at a line boundary, so no value is ever split and the response stays within max_inline_chars; read_result json_path reads any field of the stored result, e.g. "data.next_data.props".

## Response `200`

The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).

- ToolSuccess: The envelope every tool returns on success. Credits are deducted only after the tool succeeded, so credits_used is the price of this call and an error response never charges.
  - `success` true, required
  - `data` object, required: The tool's result. Its shape is tool-specific and documented on the tool's docs page (x-docs-url on the operation).
  - `credits_used` integer, required: Credits charged for this call: the operation's x-credits, plus any add-on x-credits-note or x-extra describes.
  - `credits_remaining` integer, required: The account's credit balance after this call.
  - `processing_time` integer, required: Server-side processing time in milliseconds.
  - `warnings` string[]: Non-fatal notes: a robots.txt override that was recorded, a format that could not be produced, a redaction that changed the payload.

## Other responses

- `400`: VALIDATION_ERROR: the body does not match the request schema.
- `401`: MISSING_API_KEY or INVALID_API_KEY.
- `402`: INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.
- `429`: RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.
- `500`: TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.
- `default`: Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.

---

[API](https://skmtc.dev/crawlforge/apis/crawlforge-rest-api.md) · [All operations](https://skmtc.dev/crawlforge/apis/crawlforge-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/crawlforge/apis/crawlforge-rest-api/revisions/a5532fa8d5d5?raw)
