Expand AI logo
DocsAPI ReferenceAPI Reference
Glow Active
Login

API Reference

Getting Started

API ReferenceRate LimitingError Handling

Endpoints

Start Batched FetchCancel Batched FetchFetchFetch JSON ModeHighlightsGet Batched Status
Browse docs

Getting Started

API ReferenceRate LimitingError Handling

Endpoints

Start Batched FetchCancel Batched FetchFetchFetch JSON ModeHighlightsGet Batched Status

Error Handling

Understanding API errors and how to handle them

The Expand API uses conventional HTTP response codes to indicate the success or failure of a request.

HTTP Status Codes

CodeDescription
200
Success - The request completed successfully
400Bad Request - Invalid parameters or request body
401Unauthorized - Invalid or missing API key
403Forbidden - The target page blocked the fetch, or your account is not permitted
413Payload Too Large - The request body or the target page exceeded a size limit
429Too Many Requests - Rate limit exceeded
500Internal Error - Server error
502Bad Gateway - The browser could not navigate to the target page
503Service Unavailable - The service is temporarily unavailable
504Gateway Timeout - No publishable capture result reached the API before the request deadline
529Capacity Timeout - The fetch stayed queue-only until the request deadline

Error Response Format

All errors follow a consistent format with a _tag field identifying the error type:

{
  "_tag": "ErrorType",
  // Additional fields depend on error type
}

Error Types

Validation Errors (400)

Returned when the request body doesn't match the expected schema:

{
  "_tag": "HttpApiDecodeError",
  "message": "Invalid request parameters",
  "issues": [
    {
      "_tag": "Missing",
      "path": ["url"],
      "


Common validation issues:

  • Missing required url field
  • Invalid URL format (must be http:// or https://)
  • Invalid regex pattern in include.links.includePatterns
  • Empty search.query

Authentication Errors (401)

Returned when authentication fails:

{
  "_tag": "AuthFailed",
  "reason": "InvalidApiKey",
  "description": "The provided API key is not valid"
}
ReasonDescription
InvalidApiKeyThe API key is missing or invalid
InvalidTokenThe bearer token is invalid
InvalidSessionThe session has expired
InvalidTenant

Rate Limit Errors (429)

Returned when you exceed your rate limit. See Rate Limiting for details.

{
  "_tag": "TooManyRequests"
}

Fetch Errors (403, 413, 502, 504, 529)

Returned when Fetch cannot return page content. FetchCaptureTimeout is the conservative fallback when no publishable result arrived by the request deadline and Expand cannot prove the run stayed queue-only. It does not prove that capture started or that the target page caused the timeout. FetchCapacityTimeout means Expand positively identified a queue-only capacity timeout:

_tagCodeMeaning
FetchBlocked403The site refused automated access (bot protection, auth wall, CAPTCHA).
FetchPageTooLarge413The page exceeded the capture size limit.
{
  "_tag": "FetchNavigationFailed",
  "url": "https://example.com",
  "failureType": "browserInternalDocument"
}

The SDKs do not retry the page verdicts or FetchCaptureTimeout: 403 and 413 are not retryable statuses, and the 502 and 504 tags are explicitly exempted. Keeping ambiguous 504s out of automatic retry loops avoids amplifying a control-plane incident; a deliberate manual retry may still be appropriate. The SDKs do retry 529 FetchCapacityTimeout with their normal backoff; the response also advertises Retry-After for direct HTTP clients.

Capture Timeouts (504)

Returned when no publishable result reached the API inside the server-side request deadline and Expand could not prove the run remained queue-only. This includes captures known to have started and the conservative fallback when classification is missing, fails, or exceeds its five-second budget:

{
  "_tag": "FetchCaptureTimeout",
  "url": "https://example.com",
  "timeoutMs": 150000
}
  • Synchronous Fetch uses one 150-second capture-work budget created before Hatchet dispatch. Dispatch, retries, worker reassignments, browser work, and result publication all share its immutable epoch. At expiry, timeout classification has one additional five-second budget and is disconnected from a hung control-plane call. A result that arrives during classification can still win and be returned successfully; otherwise, a typed timeout is produced by 155 seconds at the latest.
  • timeoutMs reports the configured request ceiling (150000 by default), not a remaining worker budget.
  • The SDK does not retry this automatically. This avoids retry amplification when the control plane is unhealthy; it is not proof the page was at fault. A deliberate manual retry may still be appropriate and starts a separate capture that can consume another full 150-second budget.
  • Use a synchronous client timeout of 180000 ms. If timeout classification wins, the server produces a typed timeout by 155 seconds, leaving 25 seconds for response serialization and network transit. The TypeScript SDK's timeoutMs default of 60000 aborts long captures client-side. See TypeScript SDK.
  • Batched items receive their epoch at browser-child dispatch. A child that first starts after expiry persists FetchCapacityTimeout; a replacement for a child that started earlier stays on the 504 side.

Capacity Timeouts (529)

Returned when Expand proves the request remained queue-only until the request deadline:

{
  "_tag": "FetchCapacityTimeout",
  "url": "https://example.com",
  "timeoutMs": 150000
}
  • This is Expand capacity exhaustion, not a verdict about the target page.
  • Responses include Retry-After: 5, a short overload backoff that avoids an immediate retry stampede while using little of the next request's 150-second budget.
  • The TypeScript and Python SDKs retry this automatically. Direct HTTP clients should wait at least the advertised interval before retrying.
  • If a worker started and later disappeared, the request remains 504 FetchCaptureTimeout; the historical start proves it was not queue-only.

Internal Errors (500)

Returned when the request failed inside Expand rather than at the target page:

{
  "_tag": "InternalError"
}

Service Errors (503)

Returned when the service is temporarily unavailable:

{
  "_tag": "ServiceUnavailable"
}

This typically indicates a temporary issue. Retry your request after a short delay.

Handling Errors

With the TypeScript SDK

import Expand, { ExpandError, APIError, RateLimitError } from 'expandai'

const client = new Expand({ apiKey: '{{API_KEY}}' })

try {
  const result = await client.fetch({ url: 'https://example.com' })
} catch (error) {
  if (error instanceof RateLimitError) {
    // Handle rate limiting - wait and retry
    console.log('Retry after:', error.retryAfter)
  } else if







With cURL

When using cURL or raw HTTP, check the response status code and parse the JSON body for error details:

response=$(curl -s -w "\n%{http_code}" -X POST https://api.expand.ai/v1/fetch \
  -H "x-expand-api-key: {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}')

status_code=$(echo "$response" | tail -n 1)
body=$(echo "$response" | sed '$d')



Best Practices

  1. Always check status codes before processing responses
  2. Log error details including the _tag field for debugging
  3. Implement retries for transient errors (429, 503, 529), respecting Retry-After
  4. Do not automatically retry page verdicts (403, 413, 502) or the conservative timeout fallback (504). A deliberate 504 retry may still be appropriate, but each attempt re-runs a full capture
  5. Use a 180000 ms synchronous client timeout: after the 150-second capture-work budget, bounded classification produces a typed timeout by 155 seconds at the latest if no result wins first, leaving 25 seconds for response delivery
  6. Validate inputs before making requests to avoid 400 errors
  7. Handle errors gracefully in your application to provide good user experience

On This Page

HTTP Status CodesError Response FormatError TypesValidation Errors (400)Authentication Errors (401)Rate Limit Errors (429)Fetch Errors (403, 413, 502, 504, 529)Capture Timeouts (504)Capacity Timeouts (529)Internal Errors (500)Service Errors (503)Handling ErrorsWith the TypeScript SDKWith cURLBest Practices
message
"
:
"is missing"
}
]
}
The organization was not found
FetchNavigationFailed
502
The browser could not navigate to the page.
FetchCaptureTimeout504No result arrived by the deadline, and a queue-only timeout was not proven.
FetchCapacityTimeout529Expand proved the request stayed queue-only until the deadline because capacity was unavailable.
(error instanceof APIError) {
// Handle API errors (4xx, 5xx)
console.log('Status:', error.status)
console.log('Message:', error.message)
} else if (error instanceof ExpandError) {
// Handle SDK errors
console.log('Error:', error.message)
}
}
if [ "$status_code" -ne 200 ]; then
echo "Error ($status_code): $body"
fi