Errors
Every non-2xx response has the same shape:
{ "success": false, "error": "nothing-extracted", "note": "the page was fetched and nothing in it looked like a body. A JavaScript-only page may answer to depth:\"deep\".", "retryable": "after-fix"}| field | for | promise |
|---|---|---|
error | your code | a stable slug. Match on it exactly. It does not change when we reword the explanation. |
note | you | a sentence. It will change. Never branch on it. |
retryable | your code | whether coming back helps, and how |
Read retryable, not the status code. HTTP numbers cannot say what a caller needs to know — 404 and 422 both mean “do not send this again”, 429 and 503 both mean “do, later”, and 400 means either depending on which 400 it is. The field says it outright.
retryable | what to do |
|---|---|
never | the same request will not start working. Change something other than the request, or stop. |
after-fix | the request itself is fixable. Correct it and send again. |
after-wait | wait, then send the same request. Retry-After carries the seconds. |
backoff | transient on our side. Exponential backoff with jitter. |
⚠️ never does not mean “forever”. credits-exhausted clears on your monthly reset — but that is not a retry, so it is not after-wait, and the reset time is in the body rather than in a header you might sleep on.
The stops
Section titled “The stops”Reading
Section titled “Reading”| status | error | retryable | means |
|---|---|---|---|
| 422 | nothing-extracted | after-fix | we fetched the page and found nothing that reads as a body. depth:"deep" may reach a rendering the cheap path cannot. |
| 400 | http-404, http-403, http-410… | never | the site answered, and what it answered was that number |
| 400 | http-429, http-500, http-503… | after-wait | same thing, but the site is throttling or down — the number is the site’s, not ours |
| 424 | dns-failed | after-fix | the host name did not resolve, so nothing was fetched. Check the spelling — and note that a name which only exists on a private network will never resolve from here. |
| 424 | unreachable | after-wait | the host resolved but the connection never completed (refused, timed out, or TLS did not come up). Nothing was fetched. |
⚠️ dns-failed and unreachable are about the site you asked for; worker-unreachable is about our own hop and is not your doing. The three sit next to each other on purpose — the one you get names which connection failed.
⚠️ The status stays 400 for every http-… stop, and that is deliberate. The number you should
act on is the site’s, which is in the name; what you should do is in retryable. A page that is
gone and a page that is briefly down are the same 400 and different retryable — which is the whole
reason that field exists.
🚨 A stop that says the page was fetched means it was. nothing-extracted is a statement about the page’s content, never about a page we could not reach. If you get it, the bytes arrived and nothing in them read as a body.
| 400 | url-required | after-fix | no url in the body |
| 400 | urls-required / urls-too-many | after-fix | batch input is empty, or over the 50-URL limit |
| 400 | max-pages-invalid | after-fix | max_pages is outside 1–25 |
| 400 | unsupported formats: … | after-fix | a format name we never had — check the spelling |
| 410 | monitoring-withdrawn | never | changeTracking, sections and /v1/monitor* are frozen. There is no substitute name; asking for another one is refused too. |
Keys, credits and limits
Section titled “Keys, credits and limits”| status | error | retryable | means |
|---|---|---|---|
| 401 | api-key-missing | after-fix | send Authorization: Bearer mrw_… |
| 401 | api-key-invalid | never | the key is unknown or revoked |
| 402 | credits-exhausted | never | out of credits this month. credits_reset_at says when they return. |
| 403 | monitor-limit-reached | never | your plan’s concurrent monitor slots are full |
| 404 | api-key-not-found / subscription-not-found | never | nothing on record for that key or email |
| 404 | monitor-not-found | never | ⚠️ the monitor does not exist, or it is not yours. We answer the same way for both on purpose — otherwise this endpoint would tell strangers which monitor IDs are real. |
| 409 | plan-not-yet-for-sale | never | the plan exists and is not being sold yet. Different from an unknown plan, which is a 400 — one means come back later, the other means check the spelling. |
| 400 | plan-invalid | after-fix | no such plan |
| 429 | daily-rate-limit | after-wait | your plan’s per-day request cap. Retry-After is the seconds until the UTC reset. |
| 429 | signup-throttled | after-wait | too many signups from one network today |
| 400 | email-required | after-fix | the billing endpoint needs email |
| 400 | email-invalid | after-fix | the address did not parse |
Ours, not yours
Section titled “Ours, not yours”| status | error | retryable | means |
|---|---|---|---|
| 4xx/5xx | worker-unreachable | backoff | the extraction worker did not answer. note carries whatever it did say. |
Two things a stop never does
Section titled “Two things a stop never does”A stop never comes back as a 200 with an empty body. That shape existed and was removed: a success carrying nothing is indistinguishable from a success carrying a page, right up until your parser finds no rows.
A stop is never billed. Credits are spent on reads that returned something. This is the reason nothing-extracted is a 422 and not a 200 — the billing rule keys on the response class, so the two cannot drift apart quietly.