Error codes & retry semantics
Complete list of error codes the API returns, when to retry, and how to back off. Only 2xx responses are billed.
Error codes
Every non-2xx response is free. That’s the contract - if we didn’t give you a clean product, you didn’t pay for it.
All errors return a JSON body with the same shape:
{
"error": "string_code",
"message": "human-readable explanation",
"docs_url": "https://amazonscraperapi.com/docs/guides/errors#string_code",
"request_id": "req_9f2c8a1b3e4d"
}
The request_id is what we’ll ask for if you email support. docs_url deep-links to the row in this table.
Error table
| HTTP | error code | When it happens | Retry? |
|---|---|---|---|
400 | invalid_params | ASIN not 10 alphanumeric chars, unsupported domain value, language not in the enum, missing query. | No - fix the request. |
401 | unauthorized | Missing Authorization header, malformed Bearer token, or unknown key. | No - get a valid key. |
401 | revoked | Key was rotated out. | No - use your current key. |
402 | insufficient_credits | You’re out of credits and have no active auto-top-up. | Yes, after topping up. |
403 | forbidden_region | Egress to the requested country isn’t available on your plan. | No - upgrade or drop the country param. |
404 | not_found | You hit an unknown path. Check the endpoint URL. | No. |
408 | upstream_timeout | Amazon took > 45s to respond. | Yes - retry up to 3 times with 2s backoff. |
422 | blocked_by_amazon | Amazon served a challenge page or 503. We already retried internally. | Yes, but with a 60s+ delay. |
429 | rate_limited | You exceeded your per-key QPS or concurrency ceiling. Check the Retry-After header. | Yes - respect Retry-After. |
500 | internal_error | Unhandled exception on our side. Always get logged. | Yes, after ~5s. |
501 | not_implemented | You passed a roadmap param (render_js, screenshot). | No - wait for the feature. |
502 | target_unreachable | Amazon blocked every internal retry. | Yes, with a 60s+ delay. |
502 | extraction_failed | Amazon served HTML that our extractor couldn’t parse. Usually a new A/B layout; we auto-file a ticket. | Yes, typically works on retry. |
502 | asin_mismatch | You asked for B1, Amazon redirected to B2 (usually a new-edition swap). | No - the source ASIN is what’s stale. |
502 | generic_gallery_page | Amazon served a placeholder shelf (the product is delisted). | No - remove the ASIN from your set. |
503 | capacity | We’re rate-limiting you to protect the shared pool. Rare. | Yes, after Retry-After. |
Retry semantics - the defaults we recommend
Use exponential backoff with jitter for anything marked “Yes” above. Here’s a canonical loop you can port to any language:
async function scrapeWithRetry(fetchFn, maxAttempts = 4) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const res = await fetchFn();
if (res.ok) return res.json();
// Don't retry permanent errors.
if ([400, 401, 402, 403, 404, 501].includes(res.status)) {
throw new Error(`permanent: ${res.status}`);
}
if (attempt === maxAttempts) throw new Error(`gave up after ${attempt}`);
// Respect our Retry-After if we sent one.
const retryAfter = Number(res.headers.get("Retry-After")) * 1000;
const backoff = retryAfter || Math.min(2 ** attempt * 1000, 30_000);
await new Promise(r => setTimeout(r, backoff + Math.random() * 1000));
}
}
Our official SDKs (Node, Python, Go, CLI) apply this policy automatically. You only need to bake it in yourself if you’re calling the raw HTTP API.
Debugging a failed request
Every response carries request_id in the body. If you email info@amazonscraperapi.com with that ID, we can pull the full internal trace - upstream status, attempts, latency per attempt, extractor output. Keep it in your own logs for at least 7 days so we don’t both have to guess later.
Related
- Authentication - key format + rotation
- Rate limits & concurrency - per-plan ceilings
- Billing policy - only-2xx rule in detail