Skip to main content

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.

WindowResetsScope
Per day00:00 UTC dailyPer account, shared across every key
Per calendar month00:00 UTC on the 1stPer 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.

Quota windows are UTC, not your billing cycle

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:

HeaderValue
X-Quota-Day-LimitRequests allowed in the current UTC day.
X-Quota-Day-UsedRequests used in the current UTC day, including this one.
X-Quota-Month-LimitRequests allowed in the current calendar month.
X-Quota-Month-UsedRequests 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:

errorMeaningWhat to do
daily_limit_exceededYour 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_exceededYour account's monthly volume is spent.Route overflow to x402 / MPP until the 1st, or contact support for a higher monthly cap.
rate_limitedYou 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-Used against X-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.ioYour 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 pricing
  • GET /api/networks: the networks the API serves
  • GET /openapi.json and GET /.well-known/openapi.json: OpenAPI spec
  • GET /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.

Don't build an API monitor on /health

If 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.