Error Codes
HTTP status codes and application error types returned by the API.
Error response format
All errors follow a consistent JSON structure:
{
"error": true,
"statusCode": 400,
"message": "The 'url' field is required.",
"code": "MISSING_URL",
"requestId": "req_01j8x..."
}
| Field | Type | Description |
|---|---|---|
error | true | Always true for error responses |
statusCode | number | HTTP status code |
message | string | Human-readable description |
code | string | Machine-readable error code |
requestId | string | Include this when contacting support |
HTTP status codes
400 Bad Request
The request body is malformed or missing required fields.
| Code | Message |
|---|---|
MISSING_URL | The url field is required |
INVALID_URL | URL must start with https:// |
INVALID_BATCH | urls array must contain 1–20 items |
INVALID_STRATEGY | Unknown strategy name in strategies array |
INVALID_REQUEST_ID | requestId contains invalid characters |
401 Unauthorized
{
"error": true,
"statusCode": 401,
"message": "Invalid or missing API key.",
"code": "UNAUTHORIZED"
}
Check that you're passing the key correctly:
-H "Authorization: Bearer pp_live_your_key"
Common mistakes:
- Forgot the
Bearerprefix - Copied extra whitespace around the key
- Used a
pp_test_key against production (or vice versa)
403 Forbidden
{
"error": true,
"statusCode": 403,
"code": "PLAN_LIMIT",
"message": "This feature requires the Pro plan."
}
Returned when a Pro/Scale-only parameter (includeScreenshot, forceRefresh, etc.) is used on a Free plan key.
422 Unprocessable Entity
The URL resolved successfully but didn't contain extractable product data.
| Code | Message |
|---|---|
NOT_A_PRODUCT_PAGE | URL appears to be a category, homepage, or search page |
PAGE_LOAD_FAILED | The page returned an error (4xx/5xx from the target server) |
EXTRACTION_FAILED | All extraction strategies returned no usable data |
LOW_CONFIDENCE | Data was found but confidence is below the minimum threshold |
If you're getting NOT_A_PRODUCT_PAGE on a valid product URL, try adding an extractionHint describing what you're looking for, or use strategies: ["visual"] to force visual extraction.
429 Too Many Requests
{
"error": true,
"statusCode": 429,
"code": "RATE_LIMIT",
"message": "Rate limit exceeded. Retry after 12 seconds.",
"retryAfter": 12
}
The Retry-After response header and retryAfter body field both indicate the number of seconds to wait before retrying.
Implement exponential back-off for batch processing:
async function parseWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await parseProduct(url)
} catch (err) {
if (err.statusCode === 429 && i < retries - 1) {
const wait = (err.retryAfter ?? 5) * 1000 * Math.pow(2, i)
await new Promise(r => setTimeout(r, wait))
continue
}
throw err
}
}
}
500 Internal Server Error
{
"error": true,
"statusCode": 500,
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred.",
"requestId": "req_01j8x..."
}
These are rare. Check the status page first. If the issue persists, open a support ticket with the requestId and the URL you were trying to parse.
Handling errors in code
JavaScript
const res = await fetch('https://api.productparse.dev/v1/parse', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PP_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url }),
})
if (!res.ok) {
const err = await res.json()
// err.code, err.message, err.requestId
throw new Error(`[${err.code}] ${err.message}`)
}
const data = await res.json()
Python
import httpx
try:
response = client.post("/v1/parse", json={"url": url})
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
err = e.response.json()
raise ValueError(f"[{err['code']}] {err['message']}")