Usage quotas
Your plan meters request volume, over two windows. Concurrency is not metered, so a client may issue as many simultaneous calls as its workload needs.
| Window | Resets | Scope |
|---|---|---|
| Per day | 00:00 UTC daily | Per account, shared across every key |
| Per calendar month | 00:00 UTC on the 1st | Per account, shared across every key |
Both windows key on your license, not on the individual key, so creating more keys does not buy more volume. Your active numbers are shown on the OneSource dashboard under Your Plan.
On the API key plan those are 200,000 requests per day and 2,000,000 per calendar month. The daily figure is the monthly figure divided by ten: it exists so a runaway client cannot spend a whole month's volume in an afternoon and then be stopped until the 1st. A daily wall clears in hours; a monthly wall does not.
The monthly quota resets at 00:00 UTC on the first of the calendar month. Your Stripe billing date is separate and does not move it.
Quota headers
Every API-key response carries your position in each limited window, so a client can pace itself from the response it already has instead of polling the dashboard:
| Header | Value |
|---|---|
X-Quota-Day-Limit | Requests allowed in the current UTC day. |
X-Quota-Day-Used | Requests used in the current UTC day, including this one. |
X-Quota-Month-Limit | Requests allowed in the current calendar month. |
X-Quota-Month-Used | Requests used in the current calendar month, including this one. |
$ curl -i https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
HTTP/2 200
x-quota-day-limit: 200000
x-quota-day-used: 1483
x-quota-month-limit: 2000000
x-quota-month-used: 41902
...
A header pair can be absent, and absence never means "blocked":
- An uncapped window emits nothing, so treat a missing pair as "this window does not bound you".
- If the metering store is briefly unreachable, the request is served and no headers are set.
Write your client to use the headers when present and to carry on when they are not.
Calls authenticated with x402 or MPP carry no quota headers. Those flows are settled per call and are not quota-bound at all.
What a rejection looks like
Every limit returns HTTP 429 with the flat gateway error shape, distinguished by the
error field:
error | Meaning | What to do |
|---|---|---|
daily_limit_exceeded | Your account's daily volume is spent. | Wait for the 00:00 UTC reset, shift the overflow to x402 / MPP, or contact support for a higher daily cap. |
monthly_limit_exceeded | Your account's monthly volume is spent. | Route overflow to x402 / MPP until the 1st, or contact support for a higher monthly cap. |
rate_limited | You tripped the backend's abuse guard by issuing an extreme burst. | Back off briefly and retry. Retry-After: 1 is set. |
rate_limited is a backend safety valve rather than a plan limit: it guards against a
looping or runaway client saturating the service. Seeing it during normal operation is
worth reporting rather than working around.
A 429 from either volume window still carries the X-Quota-* headers, so the response
tells you which window you hit and how large it was.
Backoff pattern
Retry 429 on the advertised window, and use exponential backoff with jitter for the
transient 502/503 you will occasionally see from upstream RPC nodes and the
facilitator:
async function callWithRetry(url: string, init: RequestInit, attempt = 0): Promise<Response> {
const res = await fetch(url, init);
if (res.status === 429) {
// Volume walls (daily_limit_exceeded / monthly_limit_exceeded) will not clear
// on a retry loop; only the transient abuse guard sets Retry-After.
const retryAfter = res.headers.get('retry-after');
if (!retryAfter || attempt >= 5) return res;
await new Promise(r => setTimeout(r, Number(retryAfter) * 1000));
return callWithRetry(url, init, attempt + 1);
}
if (res.status < 500 || attempt >= 5) return res;
const backoff = 2 ** attempt * 500 + Math.random() * 250;
await new Promise(r => setTimeout(r, backoff));
return callWithRetry(url, init, attempt + 1);
}
Retrying into a spent volume quota just burns requests against a wall that will not move
until the window resets. Branch on the error field before you retry.
See Error codes for the full retry catalog.
Monitoring usage
- In your client: track
X-Quota-Day-UsedagainstX-Quota-Day-Limit. The daily window is the one that trips first and the one you can act on the same day. - On the dashboard: app.onesource.io → Your Plan → Account shows a Current Cycle Utilization bar, the share of your account's monthly allowance used so far in the current calendar month (aggregated across all your keys, not per key). This is the same counter the API enforces against, so the bar and the headers agree.
Each API response also carries a meta.request_id you can pin to log entries on your
side.
When you need more
- Spike or overflow traffic: route bursts to pay-per-call with x402 or MPP. No quota, you pay per request in USDC, and it runs in parallel with your Bearer key.
- A sustained higher cap: contact support from app.onesource.io. Daily and monthly caps are raised on your individual account.
Free endpoints
A handful of endpoints are public and don't count against your quota:
GET /api/pricing: per-endpoint pricingGET /api/networks: the networks the API servesGET /openapi.jsonandGET /.well-known/openapi.json: OpenAPI specGET /llms.txt: LLM-optimized index
GET /health is also unauthenticated, but it is not one of these. It answers from
the routing layer in front of the API rather than from the API itself, so a 200
means "the front door is up", not "your next /api/chain/* call will succeed". It
stays green during an upstream outage.
/healthIf you want to know whether the API is actually serving, poll a real endpoint. A
free one such as /api/networks exercises the full path; a paid one such as
/api/chain/block-number additionally proves your credential and quota are good.