AlterLabAlterLab
PricingComparePlaygroundBlogDocsChangelog
    AlterLabAlterLab
    PricingComparePlaygroundBlogDocsChangelog
    IntroductionQuickstartInstallationYour First Request
    REST APICrawl APIMap APISearch APISERP APINewExtract APIAIJob PollingAPI KeysSessions APINewEnterprise APIEnterprise
    AccountAutoAlertsAutoAuthAutoBillingAutoCrawlAutoExtractAutoIntegrationsAutoKeysAutoMapAutoMonitorsAutoOrganizationsAutoSchedulesAutoScrapeAutoSearchAutoSessionsAutoUser WebhooksAutoV1 EndpointsAutoWebhooksAuto
    OverviewPythonNode.js
    JavaScript RenderingBrowser ActionsOutput FormatsPDF & OCRCachingWebhooksJSON Schema FilteringWebSocket Real-TimeBring Your Own ProxyProSticky SessionsProAuthenticated ScrapingNewHTTP Methods & BodiesNewStructured ExtractionAIWeb SearchSite MappingWeb CrawlingBatch ScrapingSchedulerChange DetectionCloud Storage ExportSpend LimitsOrganizations & TeamsAlerts & NotificationsExtraction ProfilesAIBYOK ExtractionAIOAuth2 Machine-to-MachineSupport & TicketsUnsupported Targets
    Structured ExtractionAIE-commerce ScrapingNews MonitoringPrice MonitoringMulti-Page CrawlingMonitoring DashboardAI Agent / MCPMCPAI Research AgentAISite CrawlingData Pipeline to Cloud
    E-commerceLead GenerationChange MonitoringRAG & AI PipelinesAIResearch
    PricingRate LimitsError CodesChangelogVersioning
    From FirecrawlFrom ApifyFrom ScrapingBee / ScraperAPIFrom Crawl4AIFrom SpiderFirecrawl v0 API ReferenceLegacy
    OverviewMCP ServerAIn8n NodeLangChainAICrewAIAILlamaIndexAISupabaseChrome ExtensionSoon
    PlaygroundPricingStatus

    Up to 5,000 free requests · No credit card

    Reference

    Error Codes Reference

    Complete reference for AlterLab API errors, status codes, and how to handle them gracefully.

    Error Response Structure

    All errors return a consistent JSON structure with error,message, and optional details fields. Check the HTTP status code first, then parse the response body for details.

    HTTP Status Codes

    CodeStatusDescriptionRetry?
    200OKRequest succeeded (sync response with content)N/A
    202AcceptedJob queued for async processing (poll job_id)N/A
    204No ContentSuccessful deletion, no response bodyN/A
    400Bad RequestInvalid request parameters or malformed JSONNo
    401UnauthorizedMissing, invalid, or expired API key/tokenNo
    402Payment RequiredInsufficient balance or paywall detectedNo
    403ForbiddenAccess denied by target site or blocked by anti-botMaybe
    404Not FoundResource not found (job, webhook, etc.)No
    409ConflictDuplicate resource (webhook URL already exists) or organization billing misconfiguredNo
    413Payload Too LargeContent exceeds size limit (10MB)No
    415Unsupported MediaURL content type not supported for requested modeNo
    422UnprocessableAll scraping tiers failed, content extraction failed, or invalid parameter combination (e.g. render_js=true with max_tier below 4)Maybe
    429Too Many RequestsRate limit exceeded, or the same request failed the same way several times in a row (repeat-failure back-off, no credits charged). Check the Retry-After headerYes (after delay)
    500Internal ErrorUnexpected server errorYes
    502Bad GatewayUpstream error or worker queue issueYes
    503Service UnavailableService, API-key authentication, or organization billing temporarily unavailable, or target site down. Honor Retry-AfterYes
    504Gateway TimeoutRequest timed out waiting for responseYes

    Scraping Errors

    These errors occur during the scraping process. Most result in automatic refunds.

    BLOCKED_BY_ANTIBOT
    403
    Refunded

    The 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_DETECTED
    403
    Refunded

    A bot detection challenge (anti-bot wall, reCAPTCHA, hCaptcha) was encountered.

    Solution: Enable JavaScript rendering with mode: "js" or increase max tier.

    TIMEOUT
    504
    Refunded

    The scraping operation timed out before completing.

    Solution: Increase timeout parameter or use async mode with polling.

    CONTENT_TOO_LARGE
    413
    Refunded

    The page content exceeds the 10MB size limit.

    Solution: Target a specific section of the page or use a different scraping strategy.

    UNSUPPORTED_CONTENT_TYPE
    415
    Refunded

    The URL returns a content type not supported by the selected mode.

    Solution: Use the appropriate mode - pdf for PDFs, ocr for images.

    EXTRACTION_FAILED
    422
    Refunded

    Content was fetched but could not be parsed or extracted.

    Solution: Check if the URL returns valid HTML/content. Try a different extraction mode.

    REQUIRES_AUTH
    401
    Refunded

    The page requires authentication to access.

    Solution: This URL is behind a login wall and cannot be scraped without credentials.

    PAYWALL_DETECTED
    402
    Refunded

    The content is behind a paywall and requires subscription to access.

    Solution: Content requires paid subscription. Cannot be accessed via scraping.

    NETWORK_ERROR
    503
    Refunded

    Network 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.

    render_js + max_tier below 4
    422
    Not charged

    The request sets advanced.render_js: true together with a cost_controls.max_tier below "4". Browser rendering runs on tier 4, so the request is rejected at submission on /api/v1/scrape, /api/v1/batch items, and /api/v1/crawl, before any balance is reserved or charged. In production the response body is {"detail": "Invalid request parameters"}.

    Solution: Set max_tier to "4", omit it, or use render_js: "auto", which lets AlterLab decide when browser rendering is needed. Do not retry the same request unchanged.

    error.hint (forced-render hint)
    Additive field

    When a request forces browser rendering (advanced.render_js: true or cost_controls.force_tier: "4") and the job fails because the page returned a verification challenge or did not finish loading in time, the error object includes a hint. It appears on the synchronous 422 response, on GET /api/v1/jobs/{job_id}, and on failed crawl pages. Successful jobs and jobs that do not force rendering never include it. Existing fields and status codes are unchanged.

    JSON
    {
      "detail": "anti_bot_blocked: ...",
      "error": {
        "error_code": "anti_bot_blocked",
        "message": "...",
        "protection_type": "cloudflare",
        "hint": {
          "code": "render_js_auto",
          "reason": "challenge",
          "message": "This request forced browser rendering and the page returned a verification screen instead of content. Retrying with render_js=\"auto\" lets the platform start at the lowest tier that returns the page, which succeeds more often for requests like this one."
        }
      }
    }

    Fields: code is always render_js_auto; reason is challenge or timeout; message is human-readable guidance.

    Solution: Retry with render_js: "auto" (and without force_tier) so AlterLab starts at the lowest tier that returns the page.

    Authentication Errors

    INVALID_API_KEY
    401

    The 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_KEY
    401

    No API key was provided in the request headers.

    Solution: Include the X-API-Key header with your API key.

    SESSION_EXPIRED
    401

    The session token (for dashboard APIs) has expired.

    Solution: Log in again to get a fresh session token.

    auth_unavailable
    503

    API-key verification is briefly at capacity. The key itself is valid: this is returned instead of 401 so clients do not treat a load spike as a revoked key. The response carries a Retry-After header. WebSocket connections are closed with code 1013 (Try Again Later) in the same situation.

    Solution: Retry after the number of seconds in the Retry-After header. Do not rotate or disable the key. The official SDKs retry automatically.

    Session Errors (Authenticated Scraping)

    Errors related to authenticated scraping (BYOS) session management and usage.

    SESSION_NOT_FOUND
    404

    The provided session_id does not exist or belongs to another user.

    Solution: Verify the UUID. Use GET /sessions to list your valid sessions.

    SESSION_EXPIRED
    410

    The session cookies have expired or the session was marked as expired after repeated validation failures.

    Solution: Log in to the target site again and call POST /sessions/:id/refresh with fresh cookies.

    SESSION_DOMAIN_MISMATCH
    400

    The scrape URL domain does not match the session's stored domain. For example, using an amazon.com session to scrape walmart.com.

    Solution: Create a separate session for each target domain.

    SESSION_INACTIVE
    410

    The session has been deleted or disabled.

    Solution: Create a new session with fresh cookies.

    SESSION_LIMIT_REACHED
    429

    Maximum number of sessions per account (50) has been reached.

    Solution: Delete unused or expired sessions. Use GET /sessions?status=expired to find candidates for cleanup.

    SESSION_COOKIES_CONFLICT
    400

    Both session_id and cookies were provided in the scrape request. These parameters are mutually exclusive.

    Solution: Use either session_id (for stored sessions) or cookies (for inline one-off use), not both.

    Billing Errors

    Organization API keys

    Requests made with an organization API key, including service-account keys, are billed to the organization's billing account for scrape, batch, crawl, extract, map, search, workflow and scheduled runs. Keys whose user or organization owner has been deleted are rejected with 401 Invalid API key. Any organization-key request can return the two organization billing errors below.
    INSUFFICIENT_CREDITS
    402

    Your 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_LIMIT
    400

    The estimated cost exceeds your max_credits limit.

    Solution: Increase max_credits or use a simpler scraping mode.

    SUBSCRIPTION_INACTIVE
    402

    Your subscription has been cancelled or payment failed.

    Solution: Check your billing status in the dashboard and update payment method if needed.

    org_billing_misconfigured
    409

    The request uses an organization API key (member or service account), but the organization has no active owner to bill. No personal wallet is charged instead. Not retryable.

    Solution: An organization administrator must assign an active owner. The error is returned in detail.error with a readable detail.message.

    org_billing_unavailable
    503

    The organization billing account could not be resolved for this request. The request is rejected rather than billed to the wrong account.

    Solution: Retry after the number of seconds in the Retry-After header.

    Rate Limit Errors

    RATE_LIMIT_EXCEEDED
    429

    You have exceeded your plan's rate limit (requests per minute).

    Solution: Wait the number of seconds in the Retry-After header (also detail.retry_after_seconds in the body), then retry.

    Rate Limit Headers:

    • Retry-After: Whole seconds to wait before retrying
    • RateLimit-Limit: The request-rate limit that was exceeded (not sent for spend, concurrency, crawl-slot or dispatch limits)
    • RateLimit-Remaining: Requests remaining in the window (0 on a 429)
    • RateLimit-Reset: Seconds until a retry can succeed (equals Retry-After)
    • X-RateLimit-Scope: Which limit was hit (for example account, org, ip)
    REPEAT_FAILURE_BACKOFF
    429

    After 3 consecutive identical failures of the same URL with the same request options, POST /api/v1/scrape pauses that exact request and returns repeat_failure_backoff instead of running it again. No job is created and no credits are charged (X-AlterLab-Credits: 0). Retry-After is short (about a minute) when the page returned a verification screen and longer (up to 15 minutes) for repeated errors from the site. Any successful result resets the count.

    JSON
    {
      "error": "repeat_failure_backoff",
      "message": "This exact request returned a verification screen 3 times in a row, so it was paused for 60s and no credits were charged. ...",
      "detail": {
        "scope": "url",
        "limit": null,
        "account_tier": null,
        "retry_after_seconds": 60,
        "consecutive_failures": 3,
        "outcome_class": "challenge",
        "failure_code": "anti_bot_blocked",
        "hint": {
          "code": "repeat_failure_backoff",
          "message": "This exact request returned a verification screen 3 times in a row, so it was paused for 60s and no credits were charged. Retry after the Retry-After delay, or retry now with different request options (for example render_js=\"auto\") or with force_refresh=true."
        }
      },
      "docs_url": "https://alterlab.io/docs/reference/error-codes#repeat-failure-backoff"
    }

    Fields: outcome_class is challenge, timeout, origin or other; hint.code is always repeat_failure_backoff.

    Solution: Wait for Retry-After, or retry right away with different request options (for example render_js: "auto") or with force_refresh: true, which always runs the request.

    Per-account limits refill continuously. After an idle period a full minute's allowance can be sent back to back; sustained traffic is held to the per-minute rate. See Rate Limits & Quotas for every limit.

    PlanRate LimitBurst After Idle
    Free30 req/min30 requests
    Basic60 req/min60 requests
    Growth100 req/min100 requests
    Scale300 req/min300 requests
    High Volume500 req/min500 requests
    Enterpriseup to 10,000 req/min10,000 requests

    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

    Refunds are processed instantly when an error occurs. Check the billing.refundedfield in error responses to confirm.

    Error Response Format

    JSON
    {
      "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"
      }
    }
    FieldTypeDescription
    errorstringMachine-readable error code
    messagestringHuman-readable error description
    request_idstring?Unique request ID for support inquiries
    detailsobject?Additional context about the error
    hintobject?Present on job errors when a forced-render request fails on a challenge or timeout. See forced-render hint.

    Handling Errors

    Python
    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:
                    wait_time = int(response.headers.get("Retry-After", 1))
                    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 - Invalid parameters (e.g. render_js with max_tier < 4) or
                # all tiers failed: change the request before retrying
                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")
    Rate LimitsChangelog
    Last updated: June 2026

    On this page