GALENAPI

Errors & Rate Limits

How to handle error responses and work within rate limits for reliable integration with the Galen API.

Error response format

All API errors return a consistent JSON structure. Every error response includes an error message and a machine-readable code.

FieldTypeDescription
errorstringHuman-readable error message
codestringMachine-readable error code for programmatic handling
detailstring?Additional context (may be null)
degradedbooleanTrue if the response contains partial results due to backing service unavailability
unavailable_sourcesstring[]List of data sources that were unavailable (empty if all healthy)

Graceful degradation: When a backing database (e.g., ChEMBL, cBioPortal) is temporarily unavailable, the API may return partial results with degraded: true and the affected sources listed in unavailable_sources. Check these fields to handle partial data gracefully.

HTTP status codes

The API uses standard HTTP status codes. Successful requests return 200. Errors return the appropriate 4xx or 5xx status code with a JSON error body.

400The request was malformed or missing required parameters.
{
  "error": "Bad Request",
  "code": "bad_request",
  "detail": "Missing required parameter: entity_id",
  "degraded": false,
  "unavailable_sources": []
}
401No API key provided, or the key is invalid or inactive.
{
  "error": "Unauthorized",
  "code": "unauthorized",
  "detail": "API key required. Pass via X-API-Key header.",
  "degraded": false,
  "unavailable_sources": []
}
403Your API key tier does not have access to this endpoint.
{
  "error": "Forbidden",
  "code": "forbidden",
  "detail": "Endpoint requires higher tier. Your tier: free",
  "degraded": false,
  "unavailable_sources": []
}
404The requested resource (entity, endpoint) does not exist.
{
  "error": "Not Found",
  "code": "not_found",
  "detail": "Entity not found: gene:FAKEGENE",
  "degraded": false,
  "unavailable_sources": []
}
429You have exceeded your rate limit. Check the Retry-After header.
{
  "error": "Too Many Requests",
  "code": "rate_limited",
  "detail": "Rate limit exceeded: 30/min for free tier",
  "degraded": false,
  "unavailable_sources": []
}
500An unexpected error occurred. If this persists, contact support.
{
  "error": "Internal Server Error",
  "code": "internal_error",
  "detail": null,
  "degraded": false,
  "unavailable_sources": []
}
503The service or a backing database is temporarily unavailable. The response may include partial results.
{
  "error": "Service Unavailable",
  "code": "service_unavailable",
  "detail": "Database temporarily unavailable",
  "degraded": true,
  "unavailable_sources": [
    "chembl",
    "depmap"
  ]
}

Rate limits

Rate limits are enforced per API key using a sliding window. Limits are applied at three levels: per minute, per hour, and per day.

TierPer minutePer hourPer day
Explorer (Free)305001,000
Researcher ($49/mo)601,0005,000
Pro ($199/mo)30010,00050,000
Enterprise (From $999/mo)CustomCustomUnlimited

Rate limit headers

Every authenticated response includes rate limit headers so you can track your usage:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds to wait before retrying (only on 429)

Retry strategy

When you receive a 429 or 503 response, use exponential backoff with jitter. Always respect the Retry-After header when present.

import time
import httpx

BASE_URL = "https://research.usegalen.com/api/v1"

def request_with_retry(path, api_key, max_retries=3):
    headers = {"X-API-Key": api_key}
    for attempt in range(max_retries):
        response = httpx.get(f"{BASE_URL}{path}", headers=headers)

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

        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
            print(f"Rate limited. Retrying in {retry_after}s...")
            time.sleep(retry_after)
            continue

        if response.status_code >= 500:
            time.sleep(2 ** attempt)  # Exponential backoff
            continue

        # 4xx client error — don't retry
        error = response.json()
        raise Exception(f"{error['code']}: {error.get('detail', error['error'])}")

    raise Exception("Max retries exceeded")

Tier access levels

Each API key tier has access to a different set of endpoint groups. Attempting to access an endpoint above your tier returns a 403.

TierAvailable Endpoints
ExplorerKnowledge Graph, All Databases, Hypotheses, System
ResearcherEverything in Explorer + Causal Inference (incl. do-calculus), Predictions
ProEverything in Researcher + Counterfactual, Dynamic Simulation, Patient Interpretation, Bulk Export
EnterpriseEverything + dedicated infrastructure, SLA, priority support