ParlayAPI best practices

Production-grade integration patterns. Every section here came out of a real customer issue, a real outage, or a real support ticket. If your integration is going to run unattended, work through this list end-to-end.

Authentication

Rate limits and headers

Every response carries:

HeaderMeaning
X-RateLimit-LimitCredits allotted for the current period. Paid tiers return unlimited; free tier returns the per-second cap.
X-RateLimit-RemainingCredits remaining before the next period.
X-RateLimit-ResetEpoch seconds when the limit resets.
Retry-AfterPresent only on 429 responses. Seconds to wait before retrying.

Paid tiers don't have a per-second rate limit; you're metered on monthly credits. Free tier is 60 req/s for anti-abuse during evaluation. Estimate your monthly burn before committing.

Retries and backoff

Retry only on these classes of failure:

Do NOT retry on:

Concrete Python pattern:

import httpx
import time

def fetch_with_retry(url, headers, max_attempts=5):
    backoff = 1.0
    for attempt in range(max_attempts):
        try:
            r = httpx.get(url, headers=headers, timeout=10.0)
            if r.status_code == 429:
                wait = int(r.headers.get("Retry-After", "60"))
                time.sleep(wait)
                continue
            if r.status_code in (502, 503, 504):
                time.sleep(backoff)
                backoff = min(60.0, backoff * 2)
                continue
            return r
        except (httpx.ConnectError, httpx.ReadTimeout):
            time.sleep(backoff)
            backoff = min(60.0, backoff * 2)
    return None  # caller decides what to do

Idempotency keys

All POST, PUT, PATCH, DELETE endpoints accept an Idempotency-Key header. Pass a per-request UUID; we cache the response by key for 24 hours. Replays return the original response with X-Idempotency-Replay: true.

Use cases: webhook deliveries that you retry on transient failure, CLV history grading that's expensive and you don't want billed twice, bet imports where the customer might double-click.

Error taxonomy

Every error response is structured: {"error": "MACHINE_CODE", "message": "human readable", "request_id": "...", "docs_url": "..."}. Log the request_id for every non-2xx; we use it to look up the exact request in our logs.

Full code list at /errors.

WebSocket reconnection

The most common WebSocket bug we see: clients reconnect on every long quiet window instead of trusting the heartbeat.

Reference clients at examples/ws_reference_client.py and .js. Full cookbook at /docs/websocket/troubleshooting.

Caching + ETags

Metadata endpoints (/v1/meta/*, /v1/sports, /v1/bookmakers) carry ETag headers and honor If-None-Match for cheap 304 polling. Use this on dashboards and SDK boot logic.

Live odds endpoints have short server-side caches (5-15s depending on cadence) but no ETag; the data changes too often to make conditional polling useful. Build your own client-side cache keyed by (sport_key, regions, markets) with the same TTL.

Observability

Every response carries:

Send X-Request-Id: <your-trace-id> to propagate your tracing ID through our logs. We honor W3C Trace Context (traceparent header) end-to-end.

Security

Cost management

Degraded-mode handling

Sportsbook ingestion is never 100% green. WAFs block, books rotate auth, networks blip. Build your integration assuming SOME source will be stale at any given moment.

SDKs and tooling