Skip to main content
Quick answers to the questions teams ask most when they start with Batch. For step-by-step code, see the API reference. For error-specific help, see Troubleshooting.

Getting Started

Yes. Batch is available to every Zenrows account. You can start submitting jobs right away with your existing API key, with no extra setup.
No. Batch uses the same API key as your Fetch. There’s nothing extra to generate.
All REST requests go to https://async.api.zenrows.com/v1. Send your key in the X-API-Key header on every request.
Batch is a REST API, so you can call it from any language with an HTTP client. The API reference shows every request in Python, Node.js, Java, PHP, Go, Ruby and cURL, and the same calls work from any HTTP client. Official SDKs exist for Python, Node.js and Go.

How It Works

A job is the template: your URLs and configuration. A run is one execution of that job, where each rerun or scheduled fire creates a new run. A task is a single URL within a run, with its own status and result.
A rerun replays every task in the job as a fresh run. Retry failed re-executes only the tasks that failed (and optionally those that never started), carrying over the successful results so you only pay to re-scrape what failed. Both create a new run on the same job.
Two ways: attach a webhook and the API sends a run.completed event when the run completes (or run.failed if it fails on its own; stopping a run sends no event), delivered best effort with retries for up to 3 days, or poll the job until latest_run.status is terminal (completed, failed, or stopped) and read the stats counters (completed === successful + failed). Webhooks are a convenience; the run state remains the source of truth.
Yes. POST /jobs/{id}/pause reversibly suspends the current run and POST /jobs/{id}/resume continues it, while POST /jobs/{id}/stop (alias POST /jobs/{id}/cancel) ends it for good. These act on the job’s current run, which is different from pausing a scheduled job’s recurring schedule. See Control the current run.
No. POST /jobs returns 201 Created for fewer than 10,000 tasks and 202 Accepted for 10,000 or more, inline or from a CSV upload. Every rerun returns 202 too. The response body is identical and you track completion the same way, so treat both the same. For a large 202, GET /jobs/{id}/results may return partial pages for a few seconds while the task list finishes writing, but stats.total is correct from the start.
Yes. Open a job, add tasks in batches over time (up to 10,000 per add-tasks call and 100,000 per job), then close it with last_batch: true. See open jobs.
An account runs 3 active jobs by default, shared with Crawl, and keeps up to 5 scheduled jobs. One more of either returns 429 quota_exceeded. A job counts as active until you delete it, or until it’s closed and its latest run has ended, so an open job always counts. An open job that receives no tasks for 48 hours closes automatically. To run more than 3 at once, use the chat in your dashboard. Email success@zenrows.com if you can’t use the chat.

Scraping Options

The same zenrows_params as Fetch, including js_render, premium_proxy, proxy_country, mode (auto), wait_for, wait, css_extractor, extract, extract_fields, json_response, custom_headers, and more. Set them once on the job, and override any of them per URL. Screenshot parameters aren’t supported, and an unknown key rejects the submit with 400. A task can also fetch its target with POST and a body of up to 16 KiB; see POST target fetches.
Yes. Options set on the job apply to every URL; options set on an individual URL override the job-level ones for that URL. This lets you configure the common case once and tune the exceptions (for example, a different proxy_country for a handful of URLs).
Yes. A job is just a list of URLs plus the options to apply to them, so you can mix targets in a single job and use per-URL overrides where a specific site needs different handling.
Raw HTML, parsed JSON, Markdown, plain text, and PDF. The API derives the output format from your options (for example response_type: markdown) and records it on each result.

Results and Limits

Each result_url lasts 2 hours; re-list the results to get a fresh one. The result itself stays available for 14 days by default, counted from the task’s updated_at: when it finished, or when a partial rerun carried it over. After that, the task’s result row reads status: "expired" with no result_url, and its content endpoint returns 410 result_expired. The stored body is deleted automatically within 30 days of being scraped. You don’t need to delete anything to stay within storage limits. If you want to remove results sooner, you can delete a single run’s results or an entire job yourself. See Delete a job or a run.
Yes. Use DELETE /jobs/{id}/runs/{run_id} to delete one run’s stored results while keeping the job, or DELETE /jobs/{id} to delete the whole job (all runs and results). Stopping a run only ends it and keeps the results, so use delete when you want the data removed. See Delete a job or a run.
Up to 100,000 URLs per job submit. On an open job, each add-tasks call accepts up to 10,000 URLs, up to 100,000 in total, and CSV uploads support up to 50 MB / 100,000 rows. Results page at ~1,000 per page. Every limit is in the Limits table.
Attach an external_id to each URL. The API returns it unchanged on every result, so you can correlate results to your records without tracking the API’s task IDs. It doesn’t need to be unique, but it must use only letters, digits, ., _ and -, up to 128 characters; put free text in metadata.
Yes. Start a bulk export to bundle a run’s result bodies into a single ZIP. The download link is valid for 2 hours, the export for 12 hours, and a run whose combined results exceed 100 GiB can’t be exported as one archive. Page through the results instead.

Scheduling

Yes. An account keeps up to 5 scheduled jobs. Schedule a job as one-shot (a single future time), interval (every N minutes, hours, or days, minimum 60 minutes between runs), or calendar (specific times on specific days). Schedules are timezone-aware using IANA names. You can pause and resume any time without losing the schedule.
Times of day must be full hours: use "09:00", not "09:30". Also provide exactly one schedule type (at, rate, or calendar) and a valid IANA timezone for at and calendar.

Pricing

Pricing is identical to Fetch, in credits. Successful tasks are billed, and so are tasks whose target answered 404 or 410. The credit cost depends on the scraping tier each URL uses.
Mostly no. A task that fails because the target answered 404 Not Found or 410 Gone is billed, as on Fetch, even though Batch records it as failed because it has no usable content. Other failed tasks aren’t billed. A task’s attempt history shows what each attempt was charged, and stats.spend on the run shows the total.
Yes. Because the rate card is small and stable, you can estimate credits client-side with no API call, then show your users an accurate range up front. After a run finishes, you can also read the actual credits it consumed. See Estimate cost and Check actual cost.
Use Adaptive Stealth Mode (mode=auto). It starts each request with the cheapest viable setup and escalates to JavaScript rendering or premium proxies only when a site actually needs them, so you pay for the lightest configuration that succeeds.

Reliability and Security

The API retries transient failures automatically, so a page that’s briefly unavailable still has a strong chance of being fetched. Requests are also paced to stay polite to the sites you scrape.
An idempotency key (the Idempotency-Key header) makes a retry safe. It works on submits, reruns, and add-tasks calls, and lasts 24 hours. If you never saw the answer, such as after a timeout, resend the same request with the same key: the API replays the original response if the request went through, so you never get a second job, run or set of tasks. A submit or rerun the API refused, for example with 429 quota_exceeded, doesn’t use up its key, so you can fix the cause and retry with it. An add-tasks call keeps its key, so send a corrected body with a new key. The same key with a different request, or while the first attempt is still running, returns 409 idempotency_key_conflict. See Idempotency.
Set signature: true when you attach the webhook. The API then HMAC-signs each delivery, and your receiver verifies the X-Signature header against the raw request body with your organization’s key, base64-decoded. The SDKs include a helper for it. See Verify signed webhooks.

Didn’t Find Your Answer?

Check the Troubleshooting page for error-specific guidance, or reach out to support with your job_id, the request body (API key redacted), and the exact error code and detail.