Quotas & metering
Usage is metered in SharpRelay credits. On ordinary routes, one credit includes up to 20 returned rows. Each additional started 20-row block costs one more credit, and an empty result costs one credit. There are no bandwidth charges.
| Rows returned | Credit cost |
|---|---|
| 0–20 | 1 |
| 21–40 | 2 |
| 41–100 | 3–5 |
| 1,000 | 50 |
Exact ordinary formula: max(1, ceil(rows / 20)).
GET /v1/events?live=1 has a variable cost because one request can
scan candidate events and verify current offers before returning a smaller set.
Its charge is the greater of returned-row credits and bounded verified
discovery/offer-check work, rounded through the same 20-unit blocks. Narrow
sport_id, league_id, kickoff filters, and
limit to control that work. An empty live result can therefore cost
more than one credit. X-Credits-Cost is authoritative.
Response headers — your live meter
Every metered response carries headers describing the cost and your remaining balance, so your client can throttle itself without any extra API calls:
| Header | Example | Meaning |
|---|---|---|
X-Credits-Limit | 100000 | Your plan's credit allowance for the current period |
X-Credits-Remaining | 99999 | Credits left in the period after this request |
X-Credits-Period-Limit | 1500000 | Exact plan-period limit |
X-Credits-Period-Remaining | 1499990 | Plan-period balance after this request |
X-Credits-Period-Reset | 2026-08-01T00:00:00Z | Exact plan-period quota reset; separate from a fixed private-beta access expiry |
X-Credits-Daily-Limit | 100000 | Independent UTC-daily guard |
X-Credits-Daily-Remaining | 99990 | UTC-daily balance after this request |
X-Credits-Daily-Reset | 2026-07-23T00:00:00Z | Next 00:00 UTC reset |
X-Credits-Cost | 1 | Credit cost of this response; present only when metered |
X-Result-Rows | 2 | Validated rows; always present on successful data responses and zero on metered errors |
X-Result-Truncated | false | Whether the successful result was reported as truncated |
X-Next-Cursor | eyJ2Ij… | Opaque continuation for the next live=1 event page. Absence means that scan is complete only when X-Result-Truncated is false; otherwise narrow the filters and restart. |
X-Cache | HIT | HIT or MISS only on cache-eligible responses while the disposable cache is available. It is absent for timeline routes, live=1, disabled caching, or cache unavailability. |
X-Request-Id | req_9f12… | Opaque support correlation ID returned on every response |
Retry-After | 86399 | On 429/503: seconds to wait before retrying |
Authentication, local validation, and pre-dispatch failures are free. A
normalized error can cost one credit when the request reached the contracted
data source. In that case the credit headers are present and
X-Credits-Cost gives the exact charge; if they are absent, no
buyer credit was charged.
Plan-period allowance and UTC-daily guard
Every plan has two independent credit limits. The plan-period allowance
is your total entitlement for the period shown on the plan. The UTC-daily guard
limits usage between 00:00 UTC and the next 00:00 UTC so one burst cannot spend
the entire period at once. A request must fit under both limits. Your plan
and the current plan-period window are visible via the account endpoint. The
wire field remains billing_period for compatibility:
$ curl -H "Authorization: Bearer sr_live_…" https://api.worksinprod.dev/v1/account
{
"email": "[email protected]",
"status": "active",
"plan": {
"slug": "pro",
"name": "Pro",
"daily_credit_limit": 100000,
"rate_limit_rps": 15
},
"quota": {
"period": "month",
"limit": 1500000,
"used": 10,
"committed": 10,
"remaining": 1499990,
"resets_at": "2026-08-01T00:00:00Z",
"billing_period": {"limit": 1500000, "used": 10, "in_flight": 0, "committed": 10, "remaining": 1499990},
"daily": {"limit": 100000, "used": 10, "in_flight": 0, "committed": 10, "remaining": 99990}
}
}
Rate limits
In addition to both credit limits, each plan has a per-second request rate
limit (see your plan card). This limit is shared across every API key on the
account; extra keys are for safe separation and rotation, not extra throughput.
Exceeding it returns 429 rate_limited with
Retry-After: 1 — back off briefly and continue. Quota
exhaustion returns 429 quota_exceeded with
Retry-After set to the exhausted limit's reset, or the later reset
when both limits are exhausted.
Every admitted request temporarily reserves its certified maximum buyer
charge in both quota windows before work begins. Settlement keeps only the
exact row-based cost and releases all unused headroom before data is delivered.
This prevents concurrent requests from overspending either limit. A request
that cannot reserve its complete envelope returns 429 quota_exceeded
without dispatching data work and without a buyer charge. The
X-Credits-Cost header remains the authoritative final amount. Use
incremental cursors and check both remaining headers before large history
queries.
| 429 code | Cause | Retry-After | Action |
|---|---|---|---|
rate_limited | Too many requests per second | 1s | Throttle your client; it resets instantly |
quota_exceeded | Daily guard or period allowance spent | Seconds until the exhausted limit resets (the later reset if both are exhausted) | Wait for reset; private-beta changes require separate review, while paid plan changes use the documented paid flow |
Monitoring usage
- Dashboard — /dashboard shows quota, a 30-day usage chart, and per-key activity at a glance.
- Usage API —
GET /v1/account/usage?days=30returns a zero-filled daily series (requests + credits) ready for your own charts. - Request Health — the authenticated dashboard and
GET /v1/account/request-healthseparate successful requests, uncharged failures, charged failures, and the exact credits spent on failed requests. They also provide normalized remediation without retaining query values or credentials. - Headers — for simple alerting: warn when
X-Credits-Remainingdrops below a threshold you choose.
Stretching your quota
1. Poll with since cursors so repeat calls return only
new rows (details).
2. Be careful with full_history=1 — a deep timeline means
a larger credit cost; request the available history, check
X-Result-Truncated, store the rows received, then poll incrementally.
3. Reference data (sports, leagues, periods) changes rarely — call it
on startup, not per request; it's also served from our cache at our lowest
latency.