> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Get structured JSON from any page

> Turn product, listing and article pages into clean JSON with Zenrows Extract, and keep only the fields you need.

Add `extract=auto` to a Fetch request and Zenrows returns the page's data as JSON: names, prices, availability, images, breadcrumbs and more, with no selectors to write or maintain. Reach for it whenever you want fields, not HTML.

**Uses:** [Extract](/extract/setup) (`extract=auto`, [`extract_fields`](/extract/setup#selecting-the-fields-you-need)), the [Extract endpoints](/extract/endpoints) and [Adaptive Stealth Mode](/fetch/features/adaptive-stealth-mode) (`mode=auto`)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use Zenrows to get every product on https://www.scrapingcourse.com/ecommerce/ as JSON
with name, price and URL, plus how many products the store has in total.
```

## Extract a page as JSON

Set `extract=auto` and pair it with `mode=auto`, so Zenrows renders JavaScript and switches to premium proxies when the page needs them. The response is JSON with two fields: `parsed` holds the structured data, `html` holds the raw page for validation.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import json
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.scrapingcourse.com/ecommerce/",
      "mode": "auto",
      "extract": "auto",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()

  data = response.json()
  print(json.dumps(data["parsed"], indent=2))
  ```

  ```javascript Node.js theme={"dark"}
  const params = new URLSearchParams({
    apikey: "YOUR_ZENROWS_API_KEY",
    url: "https://www.scrapingcourse.com/ecommerce/",
    mode: "auto",
    extract: "auto",
  });

  const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const { parsed } = await response.json();
  console.log(JSON.stringify(parsed, null, 2));
  ```

  ```bash cURL theme={"dark"}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&mode=auto&extract=auto"
  ```

  ```bash CLI theme={"dark"}
  zenrows extract https://www.scrapingcourse.com/ecommerce/
  ```
</CodeGroup>

The CLI prints `parsed` directly. Its `zenrows extract` command sends `extract=auto` with `mode=auto` by default, and when a domain isn't prepared yet it retries the page with `autoparse`, so you always get JSON back. See the [CLI guide](/cli/extract/introduction).

## Example output

The `parsed` field for the catalog page, trimmed to two products:

```json theme={"dark"}
{
  "page_context": {
    "current_page": 1,
    "results_per_page": 69,
    "total_pages": 12,
    "total_results": 188
  },
  "products": [
    {
      "availability": "in_stock",
      "currency": "USD",
      "image_url": "https://www.scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh09-blue_main.jpg",
      "name": "Abominable Hoodie",
      "price": 69,
      "product_id": "246",
      "product_type": "variable",
      "product_url": "https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/",
      "sku": "MH09"
    },
    {
      "availability": null,
      "currency": "USD",
      "image_url": "https://www.scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/wj08-gray_main.jpg",
      "name": "Adrienne Trek Jacket",
      "price": 57,
      "product_id": "1864",
      "product_type": "variable",
      "product_url": "https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket/",
      "sku": "WJ08"
    }
  ]
}
```

A field the page leaves empty comes back as `null`. Field names follow what each website exposes and stay stable for that website. New fields arrive as extraction grows, so read the fields you use and let the rest pass through.

## Keep just the fields you need

Add `extract_fields` with a comma-separated list. A bare name (`products`) keeps a whole group, and a dot path (`products.price`) keeps one field inside a group, on every element of a list. Run one request without it first to read the field names the page offers.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.scrapingcourse.com/ecommerce/",
      "mode": "auto",
      "extract": "auto",
      "extract_fields": "products.name,products.price,products.availability,page_context.total_results",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()
  print(response.json()["parsed"])
  ```

  ```javascript Node.js theme={"dark"}
  const params = new URLSearchParams({
    apikey: "YOUR_ZENROWS_API_KEY",
    url: "https://www.scrapingcourse.com/ecommerce/",
    mode: "auto",
    extract: "auto",
    extract_fields: "products.name,products.price,products.availability,page_context.total_results",
  });

  const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
    signal: AbortSignal.timeout(180_000),
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const { parsed } = await response.json();
  console.log(JSON.stringify(parsed, null, 2));
  ```

  ```bash cURL theme={"dark"}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.scrapingcourse.com%2Fecommerce%2F&mode=auto&extract=auto&extract_fields=products.name%2Cproducts.price"
  ```
</CodeGroup>

```json Output (trimmed) theme={"dark"}
{
  "page_context": { "total_results": 188 },
  "products": [
    { "availability": "in_stock", "name": "Abominable Hoodie", "price": 69 },
    { "availability": null, "name": "Adrienne Trek Jacket", "price": 57 },
    { "availability": null, "name": "Aeon Capri", "price": 48 }
  ]
}
```

## Extract a page from a major store

The same request works on the hundreds of domains Zenrows has prepared, including Amazon, Walmart, eBay, Best Buy, Zillow and Booking.com. This Walmart product page returns pricing, fulfillment, features and media:

```bash cURL theme={"dark"}
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.walmart.com%2Fip%2FAirPods-Pro-3%2F17835006350&mode=auto&extract=auto"
```

```json Output (parsed, trimmed) theme={"dark"}
{
  "product": {
    "brand": "Apple",
    "name": "Apple AirPods Pro 3",
    "item_id": "17835006350",
    "canonical_url": "https://www.walmart.com/ip/AirPods-Pro-3/17835006350"
  },
  "pricing": {
    "currency": "USD",
    "current_price": 199,
    "original_price": 249,
    "savings_amount": 50,
    "price_qualifier": "Price when purchased online"
  },
  "fulfillment": {
    "pickup_available": true,
    "shipping_available": true,
    "shipping_cost": "Free",
    "shipping_arrival_label": "Arrives tomorrow",
    "sold_by": "Walmart.com"
  },
  "ratings_summary": { "average_rating": 4.4 }
}
```

## Check a domain before you send traffic

`GET /v1/extract/domains` lists every domain Extract serves right now. It's free, so check it before you send traffic to a new site:

```python Python (Requests) theme={"dark"}
import requests

response = requests.get(
    "https://api.zenrows.com/v1/extract/domains",
    headers={"X-API-Key": "YOUR_ZENROWS_API_KEY"},
    timeout=30,
)
response.raise_for_status()

covered = {entry["domain"]: entry["verticals"] for entry in response.json()["domains"]}
for domain in ["walmart.com", "zillow.com", "example-shop.com"]:
    print(domain, "->", "ready" if domain in covered else "prepare it")
```

```text Output theme={"dark"}
walmart.com -> ready
zillow.com -> ready
example-shop.com -> prepare it
```

## Prepare a new domain

For a domain that isn't on the list yet, ask Zenrows to prepare it. Send one example URL per kind of page you care about, such as a product page and a search page, then poll until `status` is `ready`. Preparation usually takes minutes, and every Zenrows API key can use the domain once it's ready.

```python Python (Requests) theme={"dark"}
import time
import requests

headers = {"X-API-Key": "YOUR_ZENROWS_API_KEY"}
base = "https://api.zenrows.com/v1/extract/prepared-domains"

# 1. Ask for the domain
response = requests.post(
    base,
    headers=headers,
    json={
        "domain": "example-shop.com",
        "example_urls": ["https://www.example-shop.com/product/blue-shirt"],
    },
    timeout=30,
)
print(response.status_code, response.json()["state"])  # 202 received

# 2. Poll until it is ready
while True:
    body = requests.get(f"{base}/example-shop.com", headers=headers, timeout=30).json()
    if body["status"] != "preparing":
        break
    time.sleep(30)

print(body["status"])  # ready, or failed with a failure block that says what to do
```

Branch on `status`: `preparing`, `ready` or `failed`. A failed preparation carries `failure.code`, and the [endpoint reference](/extract/endpoints#when-a-preparation-fails) maps each code to the next step.

## Use the Zenrows SDKs

Both SDKs have an `extract()` method that sends `extract=auto` plus `mode=auto` for you. Pass `extract_fields` like any other parameter. When a domain isn't prepared yet, `extract()` retries with `autoparse`; turn that off with `fallback_to_autoparse=False` (Python) or `fallbackToAutoparse: false` (Node.js) to get the `REQS007` response instead.

<CodeGroup>
  ```python Python (SDK) theme={"dark"}
  # pip install zenrows
  from zenrows import ZenRowsClient

  client = ZenRowsClient("YOUR_ZENROWS_API_KEY")

  response = client.extract(
      "https://www.scrapingcourse.com/ecommerce/",
      params={"extract_fields": "products.name,products.price"},
      timeout=180,
  )
  response.raise_for_status()
  print(response.json()["parsed"])
  ```

  ```javascript Node.js (SDK) theme={"dark"}
  // npm install zenrows
  import { ZenRows } from "zenrows";

  const client = new ZenRows("YOUR_ZENROWS_API_KEY");

  const response = await client.extract(
    "https://www.scrapingcourse.com/ecommerce/",
    { extract_fields: "products.name,products.price" },
  );
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

  const { parsed } = await response.json();
  console.log(parsed);
  ```
</CodeGroup>

```text Output (trimmed) theme={"dark"}
{'products': [{'name': 'Abominable Hoodie', 'price': 69}, {'name': 'Adrienne Trek Jacket', 'price': 57}, {'name': 'Aeon Capri', 'price': 48}, ...]}
```

## Customize it

| Change | How |
| - | - |
| The same field names across websites (real estate) | `extract=standard`, see [Extraction modes](/extract/setup#extraction-modes) and [Extract real estate listings](/academy/extract-property-data) |
| Content from a specific country | Add `proxy_country=us` (any ISO code), see [Proxy country](/fetch/features/proxy-country) |
| Data that loads late | Use `js_render=true` with `wait=3000` instead of `mode=auto`, see [Combining with other features](/extract/setup#combining-with-other-features) |
| Fields you define with selectors | Use `css_extractor`, see [Extract data with CSS selectors](/academy/how-to-extract-data) |
| Thousands of pages | Put `"extract": "auto"` in a Batch job's `zenrows_params`, see [Scrape a list of URLs](/academy/scrape-from-a-list-of-urls) |

## Troubleshooting

* **`403` with `REQS007`**: the domain, or that kind of page on it, isn't prepared yet. The request isn't charged. [Prepare the domain](#prepare-a-new-domain) with that URL in `example_urls`, then retry once it reads `ready`.
* **`400` with `REQS004` naming `extract_fields`**: none of the names you asked for exist on this page. The error lists the fields the page offers; copy names from there, or omit `extract_fields`.
* **`422` with `no_data_extracted`**: Extract knows the page type, and the fields came back empty, usually because the page hadn't finished loading. The request isn't charged. Retry with `js_render=true&wait=3000` (and `premium_proxy=true` for protected sites), or drop `extract_fields` to see every field.
* **A preparation ends `failed` with `unpreparable`**: the example URLs returned no usable HTML. Send other example URLs from the same site.
* **The request takes long to return**: set your HTTP client timeout to 180 seconds or more so `mode=auto` has time to escalate.

## Related recipes

<CardGroup cols={2}>
  <Card title="Extract real estate listings and property data" icon="home" href="/academy/extract-property-data">
    One schema for Zillow, Redfin and Idealista, from search to every listing.
  </Card>

  <Card title="Extract data with CSS selectors" icon="code" href="/academy/how-to-extract-data">
    Pick exact elements with css\_extractor, or parse the HTML yourself.
  </Card>

  <Card title="Scrape a list of URLs" icon="list" href="/academy/scrape-from-a-list-of-urls">
    Run Extract on thousands of pages as one Batch job.
  </Card>

  <Card title="Turn any web page into LLM-ready markdown" icon="markdown" href="/academy/fetch/markdown-for-llms">
    Get the whole page as clean markdown instead of fields.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.