> ## 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.

# Setting Up the Batch Scraper API

> Get started with the ZenRows Batch Scraper API: confirm beta access, find your API key, authenticate, submit your first batch job, and retrieve the results.

This guide walks you through setting up the ZenRows® Batch Scraper API. You'll confirm your access, locate your API key, authenticate, submit your first batch job, and download the results end to end.

<Note>
  The Batch Scraper API is currently in **private beta**. Access is granted per account, so you need an invitation before you can submit jobs. Your existing Universal Scraper API key works without any extra setup.
</Note>

## Initial Setup

Follow these steps to get ready:

<Steps>
  <Step title="Sign in to ZenRows">
    Visit the [Registration Page](https://app.zenrows.com/register) to create an account, or log in if you already have one. The Batch Scraper API uses the **same API key as the Universal Scraper API**, which you'll find in your dashboard.
  </Step>

  <Step title="Confirm private-beta access">
    The Batch Scraper API is invite-only during the beta. If requests return `401 unauthenticated` with a valid key, your account isn't enabled yet. If this is the case, reach out to the ZenRows team to request access.
  </Step>

  <Step title="Note the base URL">
    All REST requests go to `https://async.api.zenrows.com/v1`.
  </Step>
</Steps>

## Authentication

Authenticate every request with your API key in the `X-API-Key` header. For security, the key travels in a header and never in the URL.

```bash theme={null}
X-API-Key: YOUR_ZENROWS_API_KEY
```

Store the key in an environment variable so your code can read it instead of hard-coding it:

```bash theme={null}
export ZENROWS_API_KEY="YOUR_ZENROWS_API_KEY"
```

## Submit Your First Job

This minimal example submits a **closed job** (one where you know all URLs up front) with JavaScript rendering enabled. Replace `YOUR_ZENROWS_API_KEY` with your key.

<CodeGroup>
  ```python Python theme={null}
  ## pip install requests
  import requests, json

  headers = {'X-API-Key': 'YOUR_ZENROWS_API_KEY', 'Content-Type': 'application/json'}
  payload = {
      'type': 'regular',
      'status': 'closed',
      'zenrows_params': {'js_render': 'true'},
      'tasks': [
          {'url': 'https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie'},
          {'url': 'https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket'},
      ],
  }
  response = requests.post('https://async.api.zenrows.com/v1/jobs', headers=headers, data=json.dumps(payload))
  job = response.json()
  print('submitted', job['job_id'], job['latest_run']['status'])
  ```

  ```javascript Node.js theme={null}
  // npm install axios
  const axios = require('axios');

  axios({
      url: 'https://async.api.zenrows.com/v1/jobs',
      method: 'POST',
      headers: { 'X-API-Key': 'YOUR_ZENROWS_API_KEY' },
      data: {
          type: 'regular',
          status: 'closed',
          zenrows_params: { js_render: 'true' },
          tasks: [
              { url: 'https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie' },
              { url: 'https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket' },
          ],
      },
  })
      .then(response => console.log('submitted', response.data.job_id, response.data.latest_run.status))
      .catch(error => console.log(error));
  ```

  ```java Java theme={null}
  import org.apache.hc.client5.http.fluent.Request;
  import org.apache.hc.core5.http.ContentType;

  public class FirstJob {
      public static void main(final String... args) throws Exception {
          String body = "{"
              + "\"type\":\"regular\",\"status\":\"closed\","
              + "\"zenrows_params\":{\"js_render\":\"true\"},"
              + "\"tasks\":["
              + "{\"url\":\"https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie\"},"
              + "{\"url\":\"https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket\"}"
              + "]}";
          String response = Request.post("https://async.api.zenrows.com/v1/jobs")
                  .addHeader("X-API-Key", "YOUR_ZENROWS_API_KEY")
                  .bodyString(body, ContentType.APPLICATION_JSON)
                  .execute().returnContent().asString();
          System.out.println(response);
      }
  }
  ```

  ```php PHP theme={null}
  <?php
  $payload = json_encode([
      'type' => 'regular',
      'status' => 'closed',
      'zenrows_params' => ['js_render' => 'true'],
      'tasks' => [
          ['url' => 'https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie'],
          ['url' => 'https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket'],
      ],
  ]);
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, 'https://async.api.zenrows.com/v1/jobs');
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: YOUR_ZENROWS_API_KEY', 'Content-Type: application/json']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $response = curl_exec($ch);
  echo $response . PHP_EOL;
  curl_close($ch);
  ?>
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "io"
      "log"
      "net/http"
  )

  func main() {
      payload := []byte(`{
          "type": "regular",
          "status": "closed",
          "zenrows_params": { "js_render": "true" },
          "tasks": [
              { "url": "https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie" },
              { "url": "https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket" }
          ]
      }`)
      req, err := http.NewRequest("POST", "https://async.api.zenrows.com/v1/jobs", bytes.NewBuffer(payload))
      if err != nil {
          log.Fatalln(err)
      }
      req.Header.Set("X-API-Key", "YOUR_ZENROWS_API_KEY")
      req.Header.Set("Content-Type", "application/json")
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
          log.Fatalln(err)
      }
      defer resp.Body.Close()
      body, _ := io.ReadAll(resp.Body)
      log.Println(string(body))
  }
  ```

  ```ruby Ruby theme={null}
  # gem install faraday
  require 'faraday'
  require 'json'

  payload = {
      type: 'regular',
      status: 'closed',
      zenrows_params: { js_render: 'true' },
      tasks: [
          { url: 'https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie' },
          { url: 'https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket' }
      ]
  }
  conn = Faraday.new
  res = conn.post('https://async.api.zenrows.com/v1/jobs') do |req|
      req.headers['X-API-Key'] = 'YOUR_ZENROWS_API_KEY'
      req.headers['Content-Type'] = 'application/json'
      req.body = payload.to_json
  end
  print(res.body)
  ```

  ```bash cURL theme={null}
  curl -X POST "https://async.api.zenrows.com/v1/jobs" \
    -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "regular",
      "status": "closed",
      "zenrows_params": { "js_render": "true" },
      "tasks": [
        { "url": "https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie" },
        { "url": "https://www.scrapingcourse.com/ecommerce/product/adrienne-trek-jacket" }
      ]
    }'
  ```
</CodeGroup>

The response includes a `job_id` and the initial run status. Poll `GET /jobs/{id}` until the run reaches a **terminal** state (finished and no longer changing), then collect the results.

### Get Your Results

Once the run is complete, list the successful tasks and download each one's scraped content. Every result row carries a `result_url`, a presigned link to the scraped page that is valid for 2 hours. Replace `JOB_ID` with the `job_id` from the submit response.

<CodeGroup>
  ```python Python theme={null}
  ## pip install requests
  import requests

  headers = {'X-API-Key': 'YOUR_ZENROWS_API_KEY'}
  page = requests.get('https://async.api.zenrows.com/v1/jobs/JOB_ID/results',
                      headers=headers, params={'status': 'successful'}).json()
  for row in page['results']:
      html = requests.get(row['result_url']).text  # the scraped page
      print(row.get('external_id') or row['task_id'], len(html), 'chars')
  ```

  ```javascript Node.js theme={null}
  // npm install axios
  const axios = require('axios');

  const page = (await axios.get('https://async.api.zenrows.com/v1/jobs/JOB_ID/results', {
      headers: { 'X-API-Key': 'YOUR_ZENROWS_API_KEY' },
      params: { status: 'successful' },
  })).data;
  for (const row of page.results) {
      const html = (await axios.get(row.result_url)).data; // the scraped page
      console.log(row.external_id ?? row.task_id, String(html).length, 'chars');
  }
  ```

  ```java Java theme={null}
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import org.apache.hc.client5.http.fluent.Request;

  public class GetResults {
      public static void main(final String... args) throws Exception {
          ObjectMapper mapper = new ObjectMapper();
          JsonNode page = mapper.readTree(
                  Request.get("https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful")
                          .addHeader("X-API-Key", "YOUR_ZENROWS_API_KEY")
                          .execute().returnContent().asString());
          for (JsonNode row : page.path("results")) {
              String html = Request.get(row.path("result_url").asText())
                      .execute().returnContent().asString(); // the scraped page
              String id = row.path("external_id").asText(row.path("task_id").asText());
              System.out.println(id + " " + html.length() + " chars");
          }
      }
  }
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful');
  curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: YOUR_ZENROWS_API_KEY']);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $page = json_decode(curl_exec($ch), true);
  curl_close($ch);
  foreach ($page['results'] as $row) {
      $html = file_get_contents($row['result_url']); // the scraped page
      echo ($row['external_id'] ?? $row['task_id']) . ' ' . strlen($html) . ' chars' . PHP_EOL;
  }
  ?>
  ```

  ```go Go theme={null}
  package main

  import (
      "encoding/json"
      "io"
      "log"
      "net/http"
  )

  func main() {
      req, _ := http.NewRequest("GET", "https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful", nil)
      req.Header.Set("X-API-Key", "YOUR_ZENROWS_API_KEY")
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
          log.Fatalln(err)
      }
      defer resp.Body.Close()
      body, _ := io.ReadAll(resp.Body)
      var page struct {
          Results []struct {
              TaskID     string `json:"task_id"`
              ExternalID string `json:"external_id"`
              ResultURL  string `json:"result_url"`
          } `json:"results"`
      }
      json.Unmarshal(body, &page)
      for _, row := range page.Results {
          r, _ := http.Get(row.ResultURL) // the scraped page
          html, _ := io.ReadAll(r.Body)
          r.Body.Close()
          name := row.ExternalID
          if name == "" {
              name = row.TaskID
          }
          log.Println(name, len(html), "chars")
      }
  }
  ```

  ```ruby Ruby theme={null}
  # gem install faraday
  require 'faraday'
  require 'json'

  page = JSON.parse(Faraday.get('https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful') { |req|
      req.headers['X-API-Key'] = 'YOUR_ZENROWS_API_KEY'
  }.body)
  page['results'].each do |row|
      html = Faraday.get(row['result_url']).body # the scraped page
      puts "#{row['external_id'] || row['task_id']} #{html.length} chars"
  end
  ```

  ```bash cURL theme={null}
  # 1) List the successful results; each row has a presigned result_url valid 2h.
  curl "https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful" \
    -H "X-API-Key: YOUR_ZENROWS_API_KEY"

  # 2) Download one result's scraped HTML using its result_url from the response above.
  curl "RESULT_URL"
  ```
</CodeGroup>

<Tip>
  Want to see the cost before you run anything? You can estimate usage client-side with no API call. See [Estimate cost](/batch-scraper-api/developer-guide-restapi#6-cost).
</Tip>

## Next Steps

<CardGroup cols={2}>
  <Card title="Developer Guide: REST API" icon="code" href="/batch-scraper-api/developer-guide-restapi">
    Every feature against the REST API: open jobs, CSV upload, scheduling, webhooks, and more.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/batch-scraper-api/troubleshooting">
    Diagnose authentication, submission, download, and webhook issues fast.
  </Card>

  <Card title="FAQ" icon="circle-question" href="/batch-scraper-api/faq">
    Quick answers on access, pricing, limits, and scraping options.
  </Card>
</CardGroup>
