Using the API
Usage & limits
What is left before something stops you: the spend windows on your plan, today's free token allowance, and your wallet balance — all readable with the key you already have.
/v1/usageEvery number here comes from the same counters the gateway checks when it decides whether to accept a request. What this endpoint shows is what will actually block you — not a separate report that can drift away from it.
curl https://api.xkiro.com/v1/usage \
-H "Authorization: Bearer $XKIRO_API_KEY"{
"object": "usage",
"plan": "ultra",
"windows": [
{
"kind": "short",
"window_sec": 18000,
"spent_usd": "0.000000",
"cap_usd": "200.000000",
"remaining_usd": "200.000000",
"resets_in_sec": 5005
},
{
"kind": "long",
"window_sec": 604800,
"spent_usd": "0.000356",
"cap_usd": "1320.000000",
"remaining_usd": "1319.999644",
"resets_in_sec": 199405
}
],
"free_tokens": {
"used_today": 412030,
"limit_per_day": 300000000,
"remaining": 299587970
},
"wallet": {
"balance_usd": "683.950000",
"held_usd": "0.000000"
}
}Authentication#
Requires a key. Both header styles work, the same as everywhere else on the API — see Authentication.
Authorization: Bearer sk-xt-…x-api-key: sk-xt-…
The figures are for the account the key belongs to, not for that key alone. Two keys on the same account return the same numbers. Per-key spending limits are a separate control, set in the console.
Free to call
This endpoint costs nothing, consumes no tokens, and does not count against your rate limit or spend windows. Reading your remaining balance is never the thing that exhausts it.
Response fields#
| Field | Type | Description |
|---|---|---|
object | string | Always “usage”. |
plan | string | null | Plan key currently in force, for example ultra. null means pay-as-you-go — no plan, or a plan whose term has already ended. |
windows | array | Spend windows the plan enforces. Empty on PAYG, where the wallet is the only limit. |
free_tokens | object | Today's allowance on free-tier models. |
wallet | object | null | Wallet balance in USD. null if no wallet has been created yet, which is the case until the first top-up or credit. |
windows[]
| Field | Type | Description |
|---|---|---|
kind | string | short or long — the two windows a plan can define. Read the length from window_sec rather than assuming which is which. |
window_sec | integer | Window length in seconds. 18000 is five hours; 604800 is seven days. |
spent_usd | string | Spent inside the current window. |
cap_usd | string | The cap for this window. |
remaining_usd | string | cap − spent, floored at zero. Never negative. |
resets_in_sec | integer | Seconds until this window rolls over and the spend resets to zero. |
Amounts are strings, not numbers
USD amounts are fixed-point strings with six decimals ("0.000356") so that small per-request costs survive JSON parsing intact. Parse them with a decimal type, or compare them as strings — parseFloat is fine for display and a poor idea for accounting.
free_tokens
| Field | Type | Description |
|---|---|---|
used_today | integer | Free-model tokens used since 00:00 UTC. |
limit_per_day | integer | null | Today's allowance. null means no free-token cap applies to this account. |
remaining | integer | null | limit_per_day − used_today, floored at zero. null whenever limit_per_day is null. |
Free tokens and paid spend are separate budgets
Exhausting the free-token allowance does not touch your wallet or your plan windows — paid models keep working normally. They are two independent limits, which is why they are two separate objects in this response rather than one combined number.
Pay-as-you-go accounts#
With no plan, windows is empty and the wallet is what matters. A request is rejected with 402 when the balance cannot cover it — see Error codes.
{
"object": "usage",
"plan": null,
"windows": [],
"free_tokens": { "used_today": 124035, "limit_per_day": 5000000, "remaining": 4875965 },
"wallet": { "balance_usd": "4.812300", "held_usd": "0.150000" }
}held_usd is money reserved for requests currently in flight. A generation is held at an estimate when it starts and settled at the real cost when it finishes, so this rises and falls on its own during heavy use — it is not a deduction.
Using it well#
- Poll sparingly. Once a minute is plenty for a dashboard. Nothing here changes faster than your own traffic changes it.
- Check before a batch, not before every call. Read it once at the start of a long job to decide whether to run at all; per-request checks double your round trips to learn something the next request would have told you anyway.
- Alert on the window, not the wallet, if you are on a plan.
remaining_usdapproaching zero with a largeresets_in_secis the situation worth warning about — that is hours of waiting, whereas a low wallet is a top-up. - Do not use it to build your own rate limiter. A
429already carriesRetry-After, and reacting to the real answer beats predicting it. See Rate limits.
const res = await fetch("https://api.xkiro.com/v1/usage", {
headers: { Authorization: `Bearer ${process.env.XKIRO_API_KEY}` },
});
const usage = await res.json();
const shortWindow = usage.windows.find((w: { kind: string }) => w.kind === "short");
if (shortWindow && Number(shortWindow.remaining_usd) < 1) {
throw new Error(
`Only $${shortWindow.remaining_usd} left in this window; it resets in ` +
`${Math.ceil(shortWindow.resets_in_sec / 60)} minutes.`,
);
}Errors#
| Field | Type | Description |
|---|---|---|
401 | authentication_error | Missing, unknown or disabled key. |
500 | api_error | Unexpected. Individual sections degrade rather than fail — if a counter cannot be read, that part comes back at zero or null instead of failing the whole response. |
