@zenrows/cli 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +83 -12
  2. package/agent-plugin/README.md +1 -1
  3. package/agent-plugin/skills/batch-jobs/SKILL.md +16 -3
  4. package/agent-plugin/skills/cost-control/SKILL.md +2 -2
  5. package/agent-plugin/skills/extract/SKILL.md +49 -9
  6. package/agent-plugin/skills/protected-fetch/SKILL.md +1 -1
  7. package/agent-plugin/skills/zenrows/SKILL.md +3 -2
  8. package/dist/src/adapters/batch.js +237 -59
  9. package/dist/src/adapters/extract.js +18 -19
  10. package/dist/src/cli/commands/batch.js +665 -126
  11. package/dist/src/cli/commands/crawl.js +1 -1
  12. package/dist/src/cli/commands/extract-prepare.js +168 -0
  13. package/dist/src/cli/commands/extract.js +21 -13
  14. package/dist/src/core/batch-api.js +398 -145
  15. package/dist/src/core/config.js +1 -1
  16. package/dist/src/core/errors.js +6 -0
  17. package/dist/src/core/http.js +78 -20
  18. package/dist/src/core/policy.js +13 -6
  19. package/dist/src/core/prepared-domains-api.js +289 -0
  20. package/evals/extract-smoke/README.md +1 -1
  21. package/package.json +1 -1
  22. package/recipes/batch-estimate/RECIPE.md +6 -3
  23. package/registry/capabilities.json +3 -3
  24. package/registry/skills.json +2 -2
  25. package/registry/templates.json +1 -1
  26. package/skills/batch-jobs/SKILL.md +160 -43
  27. package/skills/crawl/SKILL.md +1 -1
  28. package/skills/extract/SKILL.md +50 -6
  29. package/skills/trace-debug/SKILL.md +17 -2
  30. package/skills/zenrows/SKILL.md +9 -5
  31. package/templates/batch-jsonl-pipeline/README.md +10 -5
  32. package/templates/extract-pricing-pages/README.md +3 -5
  33. package/workflows/competitor-intelligence/WORKFLOW.md +2 -2
package/README.md CHANGED
@@ -37,7 +37,7 @@ that endpoint with extraction parameters, not a separate product.
37
37
  | Command | What it does | Status (this build) |
38
38
  | --- | --- | --- |
39
39
  | `zenrows fetch` | Fetch: retrieve a protected page | **available**: `GET https://api.zenrows.com/v1/` |
40
- | `zenrows extract` | Extract: turn a page into structured data with `extract=auto` on any prepared domain (falls back to Autoparse on one not prepared yet), CSS selectors, or Markdown | **available**: `GET https://api.zenrows.com/v1/?extract=auto` |
40
+ | `zenrows extract` | Extract: turn a page into structured data with `extract=auto` on any prepared domain (prepare one yourself with `zenrows extract prepare`), CSS selectors, or Markdown | **available**: `GET https://api.zenrows.com/v1/?extract=auto` |
41
41
  | `zenrows batch` | Batch: run Fetch and Extract over many URLs in one job; specs validate and estimate locally with no key | **available**: `POST https://async.api.zenrows.com/v1/jobs` |
42
42
  | `zenrows crawl` | Crawl (new): collect the URLs behind one start page, optionally with each page's HTML | **available**: `POST https://api.zenrows.com/v1/crawls` |
43
43
  | `zenrows browser` | Browser Sessions: a real cloud browser over REST (same backend as MCP `browser_*`); escalation-only, billed by bandwidth + time | **available**: `POST https://mcp.zenrows.com/browser/sessions` |
@@ -56,7 +56,7 @@ npm install -g @zenrows/cli # puts `zenrows` on PATH (466 ms, zero deps)
56
56
  zenrows init
57
57
  zenrows fetch https://httpbin.io/html # auto-provisions a Free plan account on first use
58
58
  zenrows extract https://www.owler.com/company/meltwater # extract=auto on a prepared domain
59
- zenrows extract https://www.scrapingcourse.com/ecommerce/ --autoparse # Autoparse (any domain)
59
+ zenrows extract https://www.scrapingcourse.com/ecommerce/ --css '{"title":"h1"}' # CSS selectors (any domain)
60
60
  ```
61
61
 
62
62
  > Prefer no global install? Prefix each command with `npx -y @zenrows/cli`
@@ -118,30 +118,101 @@ zenrows fetch <url> --proxy-country us --wait-for ".price"
118
118
 
119
119
  ```bash
120
120
  zenrows extract https://www.owler.com/company/meltwater # extract=auto (prepared domain)
121
- zenrows extract https://www.scrapingcourse.com/ecommerce/ --autoparse # Autoparse (any domain)
121
+ zenrows extract https://www.scrapingcourse.com/ecommerce/ --autoparse # deprecated Autoparse (any domain)
122
122
  zenrows extract <url> --css '{"title":"h1","price":".price"}' --validate
123
123
  zenrows extract <url> --outputs emails,links # built-in output filters → JSON
124
124
  zenrows extract <url> --output markdown
125
125
  ```
126
126
 
127
+ On a domain Extract hasn't prepared yet, `zenrows extract` fails with
128
+ `EXTRACT_DOMAIN_NOT_PREPARED` (API code REQS007) and prints the command to
129
+ prepare it. Preparing takes a few minutes and costs no credits; each new domain
130
+ uses one slot of the monthly allowance. `EXTRACT_PAGE_NOT_RECOGNISED` (also
131
+ REQS007) is different: the domain is prepared, but this page is not one Extract
132
+ extracts. Try a product, article or listing page instead, retry it once in a
133
+ few minutes if it should work, or use `--css`; preparing again does not change
134
+ it. `zenrows extract` never falls back to Autoparse. The `fallbackAutoparse`
135
+ adapter option is deprecated and has no effect.
136
+
137
+ ```bash
138
+ zenrows extract prepare example.com --url https://example.com/p/1 --url https://example.com/list --wait
139
+ zenrows extract prepare status example.com # progress + monthly allowance
140
+ zenrows extract https://example.com/p/1 # then extract again
141
+ ```
142
+
127
143
  ## 9. Batch
128
144
 
129
145
  Zenrows **Batch** (`https://async.api.zenrows.com/v1`) fans a
130
146
  protected fetch/extract out over many URLs. The cloud subcommands work
131
- with any active API key and spend the same credits as Fetch. Local spec
132
- validation + credit estimation always work (no key needed).
147
+ with any active API key and cost the same credits as Fetch: Batch is the
148
+ reliable path at scale, at Fetch's prices. Local spec validation + credit
149
+ estimation always work (no key needed).
133
150
 
134
151
  ```bash
135
- zenrows batch estimate jobs.jsonl # validate the spec + estimate credits (local)
136
- zenrows batch create jobs.jsonl --wait # submit a job and wait for it to finish
137
- zenrows batch status <job-id> # run status + progress stats
138
- zenrows batch results <job-id> --out results.jsonl # collect results (paginated)
139
- zenrows batch cancel <job-id> # stop an in-flight run
152
+ zenrows batch estimate jobs.jsonl # validate the spec + estimate credits (local)
153
+ zenrows batch create jobs.jsonl --follow # submit a job and wait for it to finish
154
+ zenrows batch list # your jobs, newest first
155
+ zenrows batch status <job-id> # run status, progress stats, pause state
156
+ zenrows batch wait <job-id> --timeout 30m # wait for the run to end
157
+ zenrows batch results <job-id> --out results.jsonl # collect results (paginated)
158
+ zenrows batch content <job-id> <task-id> # one task's result body
159
+ zenrows batch pause <job-id> # pause the run (resume <job-id> continues it)
160
+ zenrows batch cancel <job-id> # stop an in-flight run
161
+ zenrows batch retry-failed <job-id> --include-pending # rerun every task that did not succeed
162
+ zenrows batch rerun <job-id> # rerun failed + pending tasks (--all: every task)
163
+ zenrows batch delete <job-id> --yes # delete the job, its runs and results
140
164
  ```
141
165
 
142
166
  `jobs.jsonl` is one JSON object per line, each with a `url` (plus optional
143
167
  per-line overrides like `js_render`, `premium_proxy`, `proxy_country`,
144
- `extract`). Validate and estimate locally first, then submit with `create`.
168
+ `extract`, nested `zenrows_params`, an `external_id`, string `metadata`, and
169
+ `"method": "POST"` with a `body` of up to 16 KiB). `estimate` and `create`
170
+ check every line against the API's limits before anything is sent: `url` up
171
+ to 2048 bytes, `external_id` up to 128 letters, digits, `.`, `_` or `-`,
172
+ and `metadata` up to 20 string entries (4 KiB). Validate and estimate locally
173
+ first, then submit with `create`. `estimate` takes `create`'s
174
+ `--manual` / `--js-render` / `--premium-proxy` / `--proxy-country` flags and
175
+ prices the same request, as a range (Adaptive Stealth Mode costs 1 to 25).
176
+
177
+ `create` also takes `--name`, `--external-id` and `--metadata key=value`
178
+ (repeatable) for the job itself. It sends an `Idempotency-Key` (generated, or
179
+ `--idempotency-key`) and retries network errors and 502/503/504 under it; an
180
+ outcome that stays unknown prints the command that repeats the request with
181
+ that key, which cannot create a second job.
182
+
183
+ The local policy caps a run at 10,000 URLs and 250,000 estimated credits by
184
+ default (an Adaptive Stealth Mode task counts 25), so a default run of up to
185
+ 10,000 URLs passes in any mode. Raise them for larger jobs (the API takes up to
186
+ 100,000 tasks per job): `zenrows policy set max_pages_per_run 100000` and
187
+ `zenrows policy set max_credits_per_run 2500000`. A workspace created by an
188
+ earlier version keeps its caps (1,000 and 10,000) in `policy.json`.
189
+
190
+ `wait`, `create --follow` and `rerun --follow` poll until the run ends, the run
191
+ is paused, or `--timeout` runs out (default 10 minutes; `30s`, `5m`, `1h`; a
192
+ bare number is milliseconds, as before). When the time runs out, or the run is
193
+ paused, the command exits 0 and prints the run with a `note` and the next
194
+ command; the job keeps running. Ctrl-C stops the wait, not the job, and exits
195
+ 130. A run that ends `failed` exits 1: out of credits is
196
+ `POLICY_MAX_CREDITS_EXCEEDED`, an inactive subscription `AUTH_INVALID`, a key
197
+ cap `KEY_CREDIT_CAP_REACHED` (the same codes a refused request gets), anything
198
+ else `BATCH_FAILED`, each with the run's `failure_reason` as `server_code` and
199
+ the `job_id`. Then run the unfinished tasks with
200
+ `zenrows batch rerun <job-id> --status failed,pending,processing`. With no
201
+ flag, `rerun` reruns the `failed` and `pending` tasks, so successful tasks are
202
+ never billed again by surprise; `--status <list>` picks the statuses and
203
+ `--all` reruns every task (not with `--status`). A partial rerun works for 10
204
+ days after the run started; `--all` always does.
205
+
206
+ With `--json`, `create`, `status`, `wait`, `cancel`, `pause`, `resume` and
207
+ `rerun` print `{ok, job_id, run_id, status, stats, job}` (`jobId` is kept for
208
+ older scripts), plus `paused`, `ingest_status`, `accepted_tasks`, `rerun_of`,
209
+ `retried_tasks` and `inherited_tasks` when the API sends them. Results are
210
+ kept 14 days by default and then read `expired`; a `result_url` is valid 2
211
+ hours. Lifecycle errors get their own codes (`BATCH_RUN_IN_PROGRESS`,
212
+ `BATCH_NO_MATCHING_TASKS`, `BATCH_RERUN_WINDOW_EXPIRED`,
213
+ `BATCH_REQUEST_IN_FLIGHT`, `BATCH_RESULT_EXPIRED`, ...) and carry the API's code
214
+ as `server_code`. Schedules, webhooks, ZIP exports, CSV inputs and open jobs
215
+ are available through the Batch API and the SDKs, not this CLI.
145
216
 
146
217
  **Extract in Batch.** Set `"extract": "auto"` on a line to run that URL through
147
218
  Extract — structured data instead of raw HTML:
@@ -186,7 +257,7 @@ zenrows crawl stop <crawl-id> # stop a running crawl
186
257
  follows links until `--max-items` or `--max-pages` stops it, or nothing is left
187
258
  to open. `--max-items` and `--max-pages` cap the crawl (API default 100 each),
188
259
  and `--max-pages` bounds the cost, since each page is one fetch. The local policy `max_pages_per_run` caps `--max-pages` (default
189
- 1000). `--output-format html` uses up the page budget: each kept page is one
260
+ 10000). `--output-format html` uses up the page budget: each kept page is one
190
261
  more fetch, and `--max-pages` counts it. `--include-patterns` /
191
262
  `--exclude-patterns` are repeatable substring filters on the URL. A crawl whose
192
263
  pages held nothing new to follow ends `completed` with no results and exits 0.
@@ -62,7 +62,7 @@ Use MCP tool names — not CLI `zenrows fetch`:
62
62
  | --- | --- |
63
63
  | `zenrows` | Decision tree across all primitives |
64
64
  | `protected-fetch` | `scrape` |
65
- | `extract` | `extract` (fallback: `scrape` + autoparse / css_extractor) |
65
+ | `extract` | `extract` (fallback: `scrape` + css_extractor); on REQS007 with reason `domain_not_prepared`, `prepare_domain` then `prepared_domain_status` |
66
66
  | `batch-jobs` | `batch_create`, `batch_status`, `batch_results`, `batch_cancel` |
67
67
  | `interact-browser` | `browser_*` (escalation only) |
68
68
  | `cost-control` | Cost multipliers + escalate-with-evidence |
@@ -7,7 +7,8 @@ version: 0.1.0
7
7
  # Batch Jobs (MCP)
8
8
 
9
9
  Process large workloads asynchronously. Batch is where Zenrows' high-scale
10
- anti-bot advantage becomes obvious — thousands to millions of URLs.
10
+ anti-bot advantage becomes obvious: thousands to 100,000 URLs per job, at
11
+ Fetch's prices.
11
12
 
12
13
  > Cloud Batch lives on `async.api.zenrows.com/v1`. A 403 maps to
13
14
  > `BATCH_ACCESS_DENIED`. Do not fake a successful
@@ -33,10 +34,22 @@ Optional if exposed by the server: `batch_wait` to block until terminal status.
33
34
  `autoparse`, `extract`, `external_id`, `metadata`). `extract: "auto"`
34
35
  returns structured data instead of raw HTML, at the same credit cost as a
35
36
  regular task; the result carries `html` and `parsed` keys.
36
- 3. Poll `batch_status` until terminal (`completed` / `stopped` / …).
37
- 4. Collect with `batch_results`.
37
+ 3. Poll `batch_status` until terminal (`completed`, `stopped`, `failed` or
38
+ `deleted`). A paused run (`pause_state: paused`) is not terminal and picks
39
+ up no new task until resumed: report it instead of polling on.
40
+ 4. Collect with `batch_results`. Results are kept 14 days by default (then a
41
+ row reads `expired`); a `result_url` is valid 2 hours.
38
42
  5. `batch_cancel` if the user aborts or the run should stop early.
39
43
 
44
+ A run ends `failed` when an account problem stops it: `failure_reason`
45
+ `insufficient_credits` (add credits), `subscription_inactive` (fix it in
46
+ Billing) or `api_key_cap_reached` (the key's own cap; the account still has
47
+ credits). The tasks not yet run stay `pending`: once fixed, rerun them with
48
+ status `failed,pending,processing` rather than resubmitting the job.
49
+
50
+ Schedules, webhooks, ZIP exports and CSV inputs are available through the
51
+ Batch API and the SDKs.
52
+
40
53
  ## Rules
41
54
 
42
55
  - **Never batch before validating** on a single page ([[extract]] /
@@ -19,8 +19,8 @@ These multipliers apply when you enable the flags (on `scrape` or `extract`):
19
19
  - Both: **25×**
20
20
 
21
21
  `extract` `mode=auto` is currently free; billing may apply
22
- later. Do not promise permanent free extract pricing. Autoparse / CSS still
23
- follow the multipliers above when stealth flags are set.
22
+ later. Do not promise permanent free extract pricing. CSS (and the deprecated
23
+ Autoparse) still follow the multipliers above when stealth flags are set.
24
24
 
25
25
  ## Browser sessions (`browser_*`)
26
26
 
@@ -14,28 +14,67 @@ Convert protected pages into structured data. Value is **protected page access
14
14
  Use **`extract`** when you need structured JSON fields rather than a full page:
15
15
 
16
16
  ```
17
- extract({ url }) # mode=auto (default)
18
- extract({ url, mode: "autoparse" }) # general Autoparse (any domain)
17
+ extract({ url }) # mode=auto (default), returns `parsed` only
18
+ extract({ url, include_html: true }) # also return the full page HTML
19
+ extract({ url, mode: "autoparse" }) # deprecated Autoparse (any domain)
19
20
  extract({ url, mode: "css", css_extractor: '{"title":"h1","price":".price"}' })
20
21
  extract({ url, proxy_country: "US" }) # geo-targeted
21
22
  ```
22
23
 
23
- `mode=auto` (`extract=auto`) works on any domain Zenrows has prepared, with any
24
- key, and is currently free; billing may apply later. If the
25
- domain is not enabled (AUTH010) or not prepared yet (REQS007), the tool retries
26
- once with Autoparse by default (`fallback_autoparse`).
24
+ `mode=auto` (`extract=auto`) works on any prepared domain, with any key, and
25
+ is currently free; billing may apply later. The result carries `parsed` only;
26
+ the page HTML is most of the response size, so ask for it with
27
+ `include_html: true` only when you need it.
27
28
 
28
29
  Every mode uses Adaptive Stealth Mode by default: pass just the URL, even for
29
30
  protected or dynamic sites. Set `js_render` or `premium_proxy` only to force a
30
31
  fixed configuration; that turns Adaptive Stealth Mode off. Check `empty` in the
31
32
  result: true means no field came back with a value.
32
33
 
34
+ ## REQS007: prepare the domain, then extract again
35
+
36
+ `mode=auto` works on any prepared domain, and you can prepare one yourself.
37
+ When Extract refuses a page, `extract` returns a tool error with code
38
+ `REQS007` (legacy `AUTH010`) and a `reason`. It never retries with Autoparse.
39
+ `fallback_autoparse` is deprecated and has no effect; don't send it.
40
+
41
+ | `reason` | Meaning | Next step |
42
+ | --- | --- | --- |
43
+ | `domain_not_prepared` | The domain is not prepared yet. | `prepare_domain` (the error's `next_tool` holds its arguments), then poll, then `extract` again. |
44
+ | `page_not_recognised` | The domain is prepared, but Extract did not recognise this page as one it can extract (a page with nothing to extract, such as a home page, or a transient failure). | Try a product, article or listing page on the same site instead. If this page should work, retry it once in a few minutes; do not retry in a loop. Otherwise use `mode=css`. Do not call `prepare_domain`: preparing again does not change this. |
45
+ | `domain_not_enabled` | Extract is not enabled for the site. | Use `mode=css`, or contact Zenrows support to have the site enabled. Preparing does not change this. |
46
+
47
+ Preparing a domain:
48
+
49
+ ```
50
+ extract({ url }) # 1. REQS007, reason domain_not_prepared
51
+ prepare_domain({ domain: "example.com",
52
+ example_urls: ["https://example.com/p/1", "https://example.com/list"] })
53
+ # 2. returns at once, status "preparing"
54
+ prepared_domain_status({ domain: "example.com" }) # 3. about every 30 seconds until "ready"
55
+ extract({ url }) # 4. extract again
56
+ ```
57
+
58
+ - `example_urls`: 1 to 5 real pages you want to extract, one per page kind
59
+ (for example a product page and a listing page).
60
+ - The domain is the host: `shop.example.com` and `example.com` are prepared
61
+ separately, and a leading `www.` is dropped.
62
+ - Preparing takes a few minutes and costs no credits. Each new domain uses one
63
+ slot of the account's monthly allowance; `prepared_domain_status` returns it
64
+ (`used` / `limit` / `remaining`) and a `next` field with what to do now.
65
+ - `out_of_allowance`: no slots left this month; use `mode=css`.
66
+ `overloaded` / `disabled`: retry in a few minutes; do not loop.
67
+ A `failed` status carries `failure.code` and `failure.slot_consumed`; follow `next`.
68
+ - A domain that is already prepared answers `ready` at once
69
+ (`already_prepared: true`) and uses no slot: call `extract`. If it then
70
+ returns `page_not_recognised`, follow that row of the table, not `prepare_domain`.
71
+
33
72
  ## Fallback: `scrape` extraction params
34
73
 
35
74
  If `extract` is unavailable, similar outcomes are available on **`scrape`**:
36
75
 
37
76
  ```
38
- scrape({ url, autoparse: true })
77
+ scrape({ url, autoparse: true }) # deprecated
39
78
  scrape({ url, css_extractor: '{"title":"h1","price":".price"}' })
40
79
  scrape({ url, outputs: "emails,links" }) # or "*" for all built-ins
41
80
  ```
@@ -46,7 +85,8 @@ scrape({ url, outputs: "emails,links" }) # or "*" for all built-ins
46
85
  ## When to use which
47
86
 
48
87
  - **`mode=auto`**: site-tailored fields on any prepared domain.
49
- - **`mode=autoparse`**: any website; also the automatic AUTH010 / REQS007 fallback.
88
+ - **`mode=autoparse`** (deprecated): any website, only when asked for. Do not
89
+ build new work on it.
50
90
  - **`mode=css`**: known fields/selectors; deterministic.
51
91
  - **Markdown via `scrape`**: feed clean content to an LLM yourself.
52
92
 
@@ -57,7 +97,7 @@ Stealth flags (`js_render`, `premium_proxy`) still apply the usual multipliers
57
97
  ## Rules
58
98
 
59
99
  - Validate on a single page before `batch_create` ([[batch-jobs]]).
60
- - If Autoparse misses fields, switch to `css_extractor`, or add `js_render`
100
+ - If a field is missing, switch to `css_extractor`, or add `js_render`
61
101
  for JS-heavy pages.
62
102
  - Prefer MCP `extract` / `scrape` over CLI `zenrows extract` when the MCP
63
103
  server is connected.
@@ -34,7 +34,7 @@ scrape({ url, js_render: true, wait_for: ".price" })
34
34
  - Prefer `response_type: "markdown"` for LLM consumption; use `html` only when
35
35
  you need raw markup.
36
36
  - For **structured** fields, prefer the `extract` tool (or `scrape` with
37
- `autoparse` / `css_extractor` / `outputs`) — see [[extract]].
37
+ `css_extractor` / `outputs`) — see [[extract]].
38
38
  - On failure, escalate with evidence before `browser_*` ([[interact-browser]]).
39
39
  - Cost model: [[cost-control]].
40
40
 
@@ -37,7 +37,7 @@ If the user has a known URL and wants page content:
37
37
 
38
38
  If the user has a known URL and wants structured data:
39
39
  → Use Extract. (MCP tool: extract)
40
- Fallback: scrape with autoparse / css_extractor / outputs
40
+ Fallback: scrape with css_extractor / outputs
41
41
 
42
42
  If the user has many URLs (bulk):
43
43
  → Use Batch. (validate one page with scrape/extract first,
@@ -67,7 +67,8 @@ If the user needs login, clicks, forms, sessions, or persistent state:
67
67
  | Primitive | MCP tools | Notes |
68
68
  | --- | --- | --- |
69
69
  | Protected Fetch | `scrape` | Default markdown; HTML / plaintext / PDF / screenshot options |
70
- | Extract | `extract` | Structured JSON (`extract=auto`, autoparse, css_extractor). `extract=auto` is currently free; billing may apply later |
70
+ | Extract | `extract` | Structured JSON (`extract=auto` on any prepared domain, css_extractor; autoparse deprecated). `extract=auto` is currently free; billing may apply later |
71
+ | Prepare a domain | `prepare_domain`, `prepared_domain_status` | After an `extract` REQS007 with reason `domain_not_prepared`: prepare the domain with 1 to 5 example URLs, poll about every 30 seconds until ready, extract again. No credits; uses one monthly allowance slot. Not for `page_not_recognised`: that domain is already prepared |
71
72
  | Batch | `batch_create`, `batch_status`, `batch_results`, `batch_cancel` | Same credits as Fetch; a 403 maps to `BATCH_ACCESS_DENIED` |
72
73
  | Browser | `browser_*` (start with `browser_navigate`) | Escalation-only; same backend as Browser Sessions |
73
74