Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.backquant.com/llms.txt

Use this file to discover all available pages before exploring further.

When a request fails, the API returns a non-2xx HTTP status and an error envelope with a machine-readable code field, a human-readable message, and (sometimes) a structured details payload.
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters",
    "details": {
      "errors": [
        { "type": "literal_error", "loc": ["query", "symbol"],
          "msg": "Input should be 'BTCUSDT', 'ETHUSDT', 'SOLUSDT' or 'HYPEUSDT'" }
      ]
    }
  },
  "meta": {
    "version": "2.0",
    "timestamp": "2026-04-30T12:00:00Z",
    "request_id": "req_a1b2c3..."
  }
}

All error codes

CodeHTTP statusWhen it fires
UNAUTHORIZED401Missing, invalid, or revoked X-API-Key header.
FORBIDDEN403Key is valid but the subscription tier doesn’t allow this resource.
NOT_FOUND404Symbol, expiry, or other resource not present in cache or DB.
VALIDATION_ERROR400 / 422Bad query parameter — out of range, wrong type, or unknown enum value.
RATE_LIMIT_EXCEEDED429Per-tier rate limit hit. Retry after meta.rate_limit.reset or the Retry-After header.
UPSTREAM_ERROR502 / 503 / 504Cache (Redis) or database (Postgres) temporarily unreachable. Retry with backoff.
INTERNAL_ERROR500Unexpected server-side failure. Include meta.request_id when reporting.
The full list is also available programmatically at /v2/meta — useful if you want to keep your client’s error map in sync with the API.

Handling errors in code

Always check success before reading data. Use error.code to branch your error handling.
import requests
import time

def get_gex_levels(api_key, symbol="BTCUSDT", max_retries=3):
    for attempt in range(max_retries):
        resp = requests.get(
            "https://api.backquant.com/v2/gex/levels",
            params={"symbol": symbol},
            headers={"X-API-Key": api_key},
        )
        body = resp.json()

        if body["success"]:
            return body["data"]

        code = body["error"]["code"]

        if code == "UNAUTHORIZED":
            raise ValueError(
                "Invalid API key. Get/rotate at https://backquant.com/api-access"
            )
        if code == "RATE_LIMIT_EXCEEDED":
            retry_after = int(resp.headers.get("Retry-After", "60"))
            print(f"Rate limited; sleeping {retry_after}s")
            time.sleep(retry_after)
            continue
        if code == "NOT_FOUND":
            # 404 = no cache for this symbol/expiry. Don't retry — handle as no-data.
            return None
        if code == "UPSTREAM_ERROR":
            # Redis/DB blip — exponential backoff
            sleep = 2 ** attempt
            print(f"Upstream error; sleeping {sleep}s")
            time.sleep(sleep)
            continue
        if code == "VALIDATION_ERROR":
            raise ValueError(f"Invalid parameters: {body['error']}")

        raise RuntimeError(
            f"Unexpected error: {body['error']} "
            f"(request_id={body['meta'].get('request_id')})"
        )
    raise RuntimeError("Max retries exceeded")

Guidance per code

UNAUTHORIZED — Check the X-API-Key header is present, the value has no leading/trailing space, and the key hasn’t been revoked at backquant.com/api-access. FORBIDDEN — Your subscription doesn’t allow this resource. Usually a sign you’ve downgraded a paid tier or your subscription lapsed. NOT_FOUND — Don’t retry. Either the symbol/expiry doesn’t have cache yet (cold start, or unsupported symbol), or the path is wrong. VALIDATION_ERROR — Read error.details.errors for the failing parameter list. Common causes: passing BTC instead of BTCUSDT, unsupported ?greek= value, out-of-range numeric param. RATE_LIMIT_EXCEEDED — Read Retry-After header (or meta.rate_limit.reset) and wait. The /v2/options/chain is the most expensive single endpoint — if you’re polling it aggressively, batch with multi-symbol bundles or pull only the contracts you need with ?max_contracts. UPSTREAM_ERROR — Transient. Exponential backoff (2s, 4s, 8s) usually clears it within ~30s. If it persists past a minute, check /v2/health. INTERNAL_ERROR — Always include meta.request_id when reporting to dev@backquant.com. We trace by it.

Always-present headers

Even on errors, the response carries:
  • X-RateLimit-Limit — your tier’s per-minute cap
  • X-RateLimit-Remaining — calls left in the current window
  • X-RateLimit-Reset — Unix timestamp when the window resets
  • Retry-After — seconds until safe to retry (on 429 only)
  • X-Request-ID — the same UUID as meta.request_id

See also

Rate limits

Per-tier limits and how to think about backoff.

Authentication

Avoiding UNAUTHORIZED errors.

Data freshness

What NOT_FOUND actually means per endpoint.