Error Codes Reference
Complete reference for AlterLab API errors, status codes, and how to handle them gracefully.
Error Response Structure
error,message, and optional details fields. Check the HTTP status code first, then parse the response body for details.HTTP Status Codes
| Code | Status | Description | Retry? |
|---|---|---|---|
| 200 | OK | Request succeeded (sync response with content) | N/A |
| 202 | Accepted | Job queued for async processing (poll job_id) | N/A |
| 204 | No Content | Successful deletion, no response body | N/A |
| 400 | Bad Request | Invalid request parameters or malformed JSON | No |
| 401 | Unauthorized | Missing, invalid, or expired API key/token | No |
| 402 | Payment Required | Insufficient balance or paywall detected | No |
| 403 | Forbidden | Access denied by target site or blocked by anti-bot | Maybe |
| 404 | Not Found | Resource not found (job, webhook, etc.) | No |
| 409 | Conflict | Duplicate resource (webhook URL already exists) | No |
| 413 | Payload Too Large | Content exceeds size limit (10MB) | No |
| 415 | Unsupported Media | URL content type not supported for requested mode | No |
| 422 | Unprocessable | All scraping tiers failed or content extraction failed | Maybe |
| 429 | Too Many Requests | Rate limit exceeded, check Retry-After header | Yes (after delay) |
| 500 | Internal Error | Unexpected server error | Yes |
| 502 | Bad Gateway | Upstream error or worker queue issue | Yes |
| 503 | Service Unavailable | Service temporarily unavailable or target site down | Yes |
| 504 | Gateway Timeout | Request timed out waiting for response | Yes |
Scraping Errors
These errors occur during the scraping process. Most result in automatic refunds.
BLOCKED_BY_ANTIBOTThe target site detected and blocked the scraping attempt. All tier escalations failed.
Solution: Try using max_tier: "4" to enable CAPTCHA solving, or contact support for sites with advanced protection.
CHALLENGE_DETECTEDA bot detection challenge (Cloudflare, reCAPTCHA, hCaptcha) was encountered.
Solution: Enable JavaScript rendering with mode: "js" or increase max tier.
TIMEOUTThe scraping operation timed out before completing.
Solution: Increase timeout parameter or use async mode with polling.
CONTENT_TOO_LARGEThe page content exceeds the 10MB size limit.
Solution: Target a specific section of the page or use a different scraping strategy.
UNSUPPORTED_CONTENT_TYPEThe URL returns a content type not supported by the selected mode.
Solution: Use the appropriate mode - pdf for PDFs, ocr for images.
EXTRACTION_FAILEDContent was fetched but could not be parsed or extracted.
Solution: Check if the URL returns valid HTML/content. Try a different extraction mode.
REQUIRES_AUTHThe page requires authentication to access.
Solution: This URL is behind a login wall and cannot be scraped without credentials.
PAYWALL_DETECTEDThe content is behind a paywall and requires subscription to access.
Solution: Content requires paid subscription. Cannot be accessed via scraping.
NETWORK_ERRORNetwork connection failed - DNS error, connection refused, or SSL error.
Solution: Verify the URL is valid and the site is online. Retry after a brief delay.
Authentication Errors
INVALID_API_KEYThe API key provided is invalid, malformed, or has been revoked.
Solution: Check that your API key starts with sk_live_ or sk_test_ and is active in your dashboard.
MISSING_API_KEYNo API key was provided in the request headers.
Solution: Include the X-API-Key header with your API key.
SESSION_EXPIREDThe session token (for dashboard APIs) has expired.
Solution: Log in again to get a fresh session token.
Billing Errors
INSUFFICIENT_CREDITSYour account has insufficient balance to complete this request.
Solution: Check your balance with GET /api/v1/usage and upgrade your plan or add funds.
EXCEEDS_COST_LIMITThe estimated cost exceeds your max_credits limit.
Solution: Increase max_credits or use a simpler scraping mode.
SUBSCRIPTION_INACTIVEYour subscription has been cancelled or payment failed.
Solution: Check your billing status in the dashboard and update payment method if needed.
Rate Limit Errors
RATE_LIMIT_EXCEEDEDYou have exceeded your plan's rate limit (requests per minute).
Solution: Check the X-RateLimit-Reset header for when to retry.
Rate Limit Headers:
X-RateLimit-Limit: Your plan's rate limitX-RateLimit-Remaining: Requests remaining in windowX-RateLimit-Reset: Unix timestamp when limit resets
| Plan | Rate Limit | Burst Limit |
|---|---|---|
| Free | 10 req/min | 5 req/sec |
| Starter | 60 req/min | 10 req/sec |
| Pro | 300 req/min | 30 req/sec |
| Ultra | 1000 req/min | 100 req/sec |
Refund Policy
Costs are automatically refunded for most error conditions where the scraping failed through no fault of the user.
Costs Refunded
- Site blocked access (403, anti-bot)
- Request timeout
- Network/connection errors
- Content extraction failed
- Unsupported content type
- Page requires authentication
- Paywall detected
- Server errors (5xx)
Costs NOT Refunded
- Successful scrape (any tier)
- Cache hit (free)
- Invalid API key
- Rate limit exceeded
- Invalid request parameters
- Cancelled by user
Refund Timing
billing.refundedfield in error responses to confirm.Error Response Format
{
"error": "INSUFFICIENT_CREDITS",
"message": "Your account has insufficient balance for this request",
"request_id": "req_550e8400-e29b-41d4-a716-446655440000",
"details": {
"required_credits": 5,
"available_credits": 2,
"estimated_tier": "2"
}
}| Field | Type | Description |
|---|---|---|
| error | string | Machine-readable error code |
| message | string | Human-readable error description |
| request_id | string? | Unique request ID for support inquiries |
| details | object? | Additional context about the error |
Handling Errors
import time
import requests
from requests.exceptions import RequestException
def scrape_with_error_handling(url: str, api_key: str, max_retries: int = 3):
"""Scrape with proper error handling and retries."""
for attempt in range(max_retries):
try:
response = requests.post(
"https://api.alterlab.io/api/v1/scrape",
headers={"X-API-Key": api_key},
json={"url": url},
timeout=60
)
# Success
if response.status_code in (200, 202):
return response.json()
# Parse error response
error = response.json()
error_code = error.get("error", "UNKNOWN")
# Don't retry client errors
if response.status_code in (400, 401, 402, 404, 409, 415):
raise Exception(f"Client error: {error_code} - {error.get('message')}")
# Rate limited - wait and retry
if response.status_code == 429:
reset_time = int(response.headers.get("X-RateLimit-Reset", 0))
wait_time = max(reset_time - time.time(), 60)
print(f"Rate limited. Waiting {wait_time}s...")
time.sleep(wait_time)
continue
# Server error - retry with backoff
if response.status_code >= 500:
wait_time = 2 ** attempt
print(f"Server error {response.status_code}. Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
# 422 - All tiers failed, might want to try different settings
if response.status_code == 422:
raise Exception(f"Scraping failed: {error.get('message')}")
except RequestException as e:
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
raise
raise Exception(f"Max retries ({max_retries}) exceeded")