Production checklist
Before you ship — the ten things that bite you in production.
Before you flip the switch
These are the ten failure modes we've seen real builders hit between dev and prod. Run through the list before pointing real traffic at your integration.
01 — Rotate test keys out
sk_test_… keys authenticate, meter, and rate-limit exactly like live keys — the prefix
exists so you can separate sandbox traffic from production traffic in your own logs and
rotate the two independently. If anything in your prod path still references a test key,
your production usage lands on the key you'll casually revoke during the next experiment.
Grep your secrets manager and CI config.
git grep -n "sk_test_"02 — Set a real User-Agent
Identify yourself so we can email you before deprecating something you depend on, and so support requests pull faster traces.
curl -H "User-Agent: yourapp/1.4 (you@yourdomain.com)" ...03 — Persist meta.request_id
Capture meta.request_id in your logs for every Statshawk call. When something goes wrong
you'll save a 30-minute back-and-forth — paste the IDs and we pull the traces directly.
04 — Retry 5xx with back-off; branch 429 on the error code
502/503 are transient — start at 1 second, double up to a 30-second cap, give up
after 3–5 attempts. A 429 carries one of two codes and they need different handling:
RATE_LIMIT_EXCEEDED means you tripped the per-second cap — back off a second or two and
continue. QUOTA_EXCEEDED means the monthly unit cap — retrying won't help; the
X-RateLimit-Reset header carries the period-end timestamp (RFC3339) it resets at.
05 — Watch X-RateLimit-Remaining
Implement client-side back-pressure once X-RateLimit-Remaining drops below 10% of
X-RateLimit-Limit. It's a thousand times better than getting throttled.
06 — Don't retry deterministic errors
A 400, 401, 403, or 404 will return the same result on retry, and so will 501 NOT_SUPPORTED (a capability gap, not an outage). Surface them to the caller; don't loop.
07 — Pin to meta.version
We don't bump versions casually, but every breaking change does bump. If you key on response
shape, log meta.version and alert when it changes — saves you a 3 a.m. incident.
08 — Use feature detection
Before assuming a competition exposes every stat phase or measure, call
/v1/competitions/{comp}/capabilities once and cache the result for an hour. A
501 NOT_SUPPORTED in production is a poor look when the detection call is two lines —
and if you do hit one, its error body carries machine-readable league and capability
fields you can key a fallback off.
09 — Cache budgets, not responses
We cache responses on our side. Caching them on yours adds a TTL stale-set you'll have to
debug during a live game. Trust meta.cache instead — HIT is a fresh cached copy, and
STALE means a background refresh is already running, so re-requesting shortly gets the
fresh value.
10 — Plan for quota exhaustion
On the free tier, 429 QUOTA_EXCEEDED is a hard stop until the period resets (the
X-RateLimit-Reset header carries the reset timestamp). Paid tiers roll past the included
allowance into per-unit overage, so a quota 429 there is rare and usually transient. Either
way, wire upgrade-or-degrade logic into your code path before launch — degrading gracefully
to "tonight's lineups unavailable" beats a 500.