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. A second endpoint on the same path returns what you have already spent, bucketed over time.

GET/v1/usage

Every 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"
200 OK — an account on a plan with spend windows
{
  "object": "usage",
  "plan": "ultra",
  "user": {
    "name": "Ada Lovelace",
    "email": "[email protected]",
    "avatar_url": "https://cdn.xkiro.com/avatars/…/a1b2c3.webp"
  },
  "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#

FieldTypeDescription
objectstringAlways “usage”.
planstring | nullPlan key currently in force, for example ultra. null means pay-as-you-go — no plan, or a plan whose term has already ended.
userobject | nullWho the key belongs to — name, email and avatar_url, each nullable. Enough for a client to show which account it is signed in as instead of a blank field. null if the account record cannot be read.
windowsarraySpend windows the plan enforces. Empty on PAYG, where the wallet is the only limit.
free_tokensobjectToday's allowance on free-tier models.
walletobject | nullWallet balance in USD. null if no wallet has been created yet, which is the case until the first top-up or credit.

windows[]

FieldTypeDescription
kindstringshort or long — the two windows a plan can define. Read the length from window_sec rather than assuming which is which.
window_secintegerWindow length in seconds. 18000 is five hours; 604800 is seven days.
spent_usdstringSpent inside the current window.
cap_usdstringThe cap for this window.
remaining_usdstringcap − spent, floored at zero. Never negative.
resets_in_secintegerSeconds 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

FieldTypeDescription
used_todayintegerFree-model tokens used since 00:00 UTC.
limit_per_dayinteger | nullToday's allowance. null means no free-token cap applies to this account.
remaininginteger | nulllimit_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.

200 OK — pay-as-you-go
{
  "object": "usage",
  "plan": null,
  "user": { "name": "Ada Lovelace", "email": "[email protected]", "avatar_url": 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.

Spend history#

GET/v1/usage/history

Where /v1/usage answers “what is left?”, this answers “what has gone?” — requests, tokens and spend bucketed over the last day, week or month. It reads the same request log the console charts are drawn from, so a client can render its own usage graph without scraping the dashboard.

curl "https://api.xkiro.com/v1/usage/history?period=week" \
  -H "Authorization: Bearer $XKIRO_API_KEY"

Query parameters

FieldTypeDescription
periodstringday (default) — 24 hourly buckets. week — 7 daily buckets. month — 30 daily buckets. Anything else falls back to day rather than returning 400; a read-only chart endpoint should not break a page over a typo in a query string.

Three fixed windows, not an arbitrary range

There is deliberately no start/end pair here. This endpoint has no rate limit, for the same reason /v1/usage has none, and an open-ended range on an unmetered endpoint is a full table scan waiting to be requested. Three fixed windows keep the query cost bounded and known in advance. The console has the arbitrary-range version.

200 OK — period=week
{
  "object": "usage.history",
  "period": "week",
  "bucket": "day",
  "points": [
    { "ts": "2026-09-05T00:00:00.000Z", "requests": 6,  "tokens": 10538, "spend_usd": "0.033062" },
    { "ts": "2026-09-06T00:00:00.000Z", "requests": 5,  "tokens": 1224,  "spend_usd": "0.006286" },
    { "ts": "2026-09-07T00:00:00.000Z", "requests": 5,  "tokens": 3576,  "spend_usd": "0.009392" },
    { "ts": "2026-09-08T00:00:00.000Z", "requests": 0,  "tokens": 0,     "spend_usd": "0.000000" },
    { "ts": "2026-09-09T00:00:00.000Z", "requests": 3,  "tokens": 1519,  "spend_usd": "0.000985" },
    { "ts": "2026-09-10T00:00:00.000Z", "requests": 15, "tokens": 62828, "spend_usd": "0.124429" },
    { "ts": "2026-09-11T00:00:00.000Z", "requests": 7,  "tokens": 84535, "spend_usd": "0.000000" }
  ],
  "total": { "requests": 41, "tokens": 164220, "spend_usd": "0.174153" }
}

Response fields

FieldTypeDescription
objectstringAlways “usage.history”.
periodstringThe window actually served — echoed back, so you can tell when a bad value fell back to day.
bucketstringhour or day — what one point covers. Read it rather than inferring the step from the gap between two ts values.
pointsarrayOne entry per bucket, oldest first. Empty buckets are included with zeros, so the array length is always 24, 7 or 30 and a chart can map it straight onto an axis without filling gaps itself.
totalobjectSum across the returned points — requests, tokens and spend_usd.

points[]

FieldTypeDescription
tsstringStart of the bucket, ISO-8601 in UTC. Convert for display if you need local time; the buckets themselves are cut on UTC boundaries.
requestsintegerRequests logged in this bucket, successful or not.
tokensintegerTotal tokens for the bucket — input, output, cache reads and reasoning together. Not the sum of two visible columns: on long sessions cache reads can be the overwhelming majority, so a chart built from prompt + completion alone reads far lower than the figure you are billed against.
spend_usdstringSpend attributed to the bucket, fixed-point with six decimals — same string format and same caveat as everywhere else on this page. "0.000000" with non-zero tokens means free-tier models: real usage, no charge.

The last bucket is still open

The newest point is the hour or day currently in progress, not a closed one. It will keep rising until the bucket ends. That is deliberate — dropping it would hide the most recent activity, which is the part most worth looking at — but it means the last point is not comparable with the ones before it, and a bar chart will always show a short final column near the start of a bucket.

Same account scope and same auth as /v1/usage: the numbers cover the account the key belongs to, not that one key. Also free to call, also unmetered.

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_usd approaching zero with a large resets_in_sec is 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 429 already carries Retry-After, and reacting to the real answer beats predicting it. See Rate limits.
Stop a batch job before it starts failing
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#

FieldTypeDescription
401authentication_errorMissing, unknown or disabled key.
500api_errorUnexpected. 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.

Was this page helpful?