Error Handling

The OpenAlex API uses standard HTTP status codes to indicate success or failure.

HTTP Status Codes

CodeMeaningWhat to Do
200SuccessRequest completed successfully
301Moved PermanentlyEntity was merged; follow the redirect
400Bad RequestCheck your filter syntax or parameters
403ForbiddenYou don’t have access to this resource. (A collection you can’t read returns 404, not 403.)
404Not FoundEntity doesn’t exist
429Too Many RequestsRate limit or daily credit budget exceeded; slow down or wait for the reset
500Server ErrorTemporary issue; retry with backoff

Error Response Format

Error responses include a message explaining what went wrong:

{
  "error": "Invalid filter",
  "message": "Unknown filter field: author_name. Did you mean: authorships.author.id?"
}

Common Errors

Invalid Filter Syntax

400 Bad Request
"Invalid filter: publication_year:abc"

Fix: Ensure values match expected types. publication_year expects an integer.

Unknown Filter Field

400 Bad Request
"Unknown filter field: author_name"

Fix: Use the correct filter field. For authors, use authorships.author.id with an ID, not a name. See Resolve IDs.

Rate Limit Exceeded

429 Too Many Requests
"Rate limit exceeded"

Fix:

  • Check rate limit headers to see your remaining allowance
  • Add delays between requests
  • Use per_page=100 to reduce total requests
  • Consider a paid plan for higher limits

Entity Not Found

404 Not Found
"Work W9999999999 not found"

Fix: Verify the ID exists. The entity may have been merged—check if you’re being redirected.

Retry Logic

Implement exponential backoff for transient errors:

import time
import requests

def fetch_with_retry(url, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.get(url, timeout=30)

            if response.status_code == 200:
                return response.json()

            if response.status_code == 429:
                # Rate limited - wait longer
                wait_time = 2 ** attempt
                time.sleep(wait_time)
                continue

            if response.status_code >= 500:
                # Server error - retry
                wait_time = 2 ** attempt
                time.sleep(wait_time)
                continue

            # Client error - don't retry
            response.raise_for_status()

        except requests.exceptions.Timeout:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
            else:
                raise

    raise Exception(f"Failed after {max_retries} retries")

Rate Limit Headers

Every response includes headers showing your current status:

X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 8766
X-RateLimit-Credits-Used: 1
X-RateLimit-Reset: 43200

Error responses are free. Any 4xx or 5xx response costs 0 credits, and its X-RateLimit-Credits-Used header reports 0. A typo in a filter or search does not use up your daily budget.

An OQL query with a calculate step, a split by a list, bins or conditions, or a filter on its groups is priced from its plan (see Example costs). If it costs more than you have left today, it is refused before it runs, with HTTP 429 and "error": "not_enough_credits". The body names the price (cost, step by step) and how to fix it: run it after the daily reset, add prepaid usage, or narrow the query (fewer listed searches). The refusal itself is free.

Use these to:

  • Monitor your usage
  • Pause before hitting limits
  • Calculate when limits reset (seconds until midnight UTC)

Best Practices

  1. Always set timeouts — Use 30-second timeouts to avoid hanging requests
  2. Implement backoff — Don’t retry immediately; wait 1s, 2s, 4s, etc.
  3. Check headers — Monitor X-RateLimit-Remaining to avoid hitting limits
  4. Log errors — Record failures for debugging
  5. Use bulk endpoints — Batch requests to reduce total API calls

Last updated

View as Markdown