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
| Code | Name | When you'll see it |
|---|---|---|
| 400 | Bad Request | Malformed URL, missing required parameter, or invalid query string. |
| 401 | Unauthorized | Missing or malformed Authorization header. |
| 403 | Forbidden | Valid key but no access to the endpoint or scope. |
| 404 | Not Found | Endpoint path doesn’t exist (check spelling). |
| 422 | Unprocessable | URL parsed but the upstream content can’t be extracted (paywalled, login-walled, blocked by robots). |
| 429 | Too Many Requests | Rate limit or quota exceeded. Check `X-RateLimit-*` and `Retry-After`. |
| 451 | Unavailable for Legal | URL was blocked for compliance reasons (e.g. SSRF policy, private IP). |
| 500 | Server Error | Unexpected error on OG Fetch. Safe to retry. |
| 502 | Bad Gateway | Upstream site returned an unexpected response. Safe to retry. |
| 503 | Service Unavailable | OG Fetch is temporarily overloaded. Respect `Retry-After`. |
| 504 | Gateway Timeout | Upstream 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
- Rate Limiting — header semantics and quota reset timing.
- Authentication — 401/403 scenarios in detail.