

## Before you flip the switch [#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 [#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.

```bash title="quick grep"
git grep -n "sk_test_"
```

### 02 — Set a real `User-Agent` [#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.

```bash
curl -H "User-Agent: yourapp/1.4 (you@yourdomain.com)" ...
```

### 03 — Persist `meta.request_id` [#03--persist-metarequest_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 [#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` [#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 [#06--dont-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` [#07--pin-to-metaversion]

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 [#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 [#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 [#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.

<Callout type="tier" title="enterprise tier">
  On the enterprise tier, the monthly ceiling is a per-account contract term rather than a
  fixed plan cap — size it to your real peak before launch. Email
  [`sales@statshawk.ai`](mailto:sales@statshawk.ai) before you need it.
</Callout>

## See also [#see-also]

* [Errors](/docs/getting-started/errors) and [Error codes](/docs/getting-started/error-codes)
* [Rate limits](/docs/getting-started/rate-limits)
* [Cache contract](/docs/reference/cache)
