Skip to main content
API Documentation

Errors

Every error response uses a consistent JSON envelope. Status codes follow standard HTTP semantics.

Error envelope

All error responses have the shape:

{
"error": "rate_limit_exceeded",
"message": "Daily limit of 150 requests reached for IP 203.0.113.42",
"details": {
"limit": 150,
"remaining": 0,
"reset_at": "2026-05-21T00:00:00Z"
},
"request_id": "req_7c3d2f8a"
}
  • error — short machine-readable code; safe to switch on in client code.
  • message — human-readable explanation; safe to log, not safe to display directly to end-users.
  • details — optional, error-specific data.
  • request_id — include this when contacting support.

Status codes

CodeNameWhen you'll see it
400Bad RequestMalformed URL, missing required parameter, or invalid query string.
401UnauthorizedMissing or malformed Authorization header.
403ForbiddenValid key but no access to the endpoint or scope.
404Not FoundEndpoint path doesn’t exist (check spelling).
422UnprocessableURL parsed but the upstream content can’t be extracted (paywalled, login-walled, blocked by robots).
429Too Many RequestsRate limit or quota exceeded. Check `X-RateLimit-*` and `Retry-After`.
451Unavailable for LegalURL was blocked for compliance reasons (e.g. SSRF policy, private IP).
500Server ErrorUnexpected error on OG Fetch. Safe to retry.
502Bad GatewayUpstream site returned an unexpected response. Safe to retry.
503Service UnavailableOG Fetch is temporarily overloaded. Respect `Retry-After`.
504Gateway TimeoutUpstream site didn’t respond within the timeout window. Retry with backoff.

Retry strategy

5xx responses are usually transient — retry with exponential backoff. 429 responses include a Retry-After header (in seconds) you should respect. Anything 4xx other than 429will fail the same way on retry — fix the request first.

async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) return response;
const retryable = response.status >= 500 || response.status === 429;
if (!retryable || attempt === maxRetries) return response;
const retryAfter = Number(response.headers.get('Retry-After')) || Math.pow(2, attempt);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
}
throw new Error('unreachable');
}

See also