Platform
Billing & quotas
How calls are metered in credit units, the free daily quota, and token-weighted AI pricing.
The two-band model
Every billable call is metered in credit units. Metering runs in two bands, evaluated per app on each request:
- Free daily quota. Each app gets a free allowance of credit units per UTC calendar day. Calls draw down this allowance first and cost you nothing.
-
Prepaid wallet. Once an app has consumed its free allowance for the day, further
billable calls debit the owner’s prepaid credit balance
(
ai_users.tokens_balance) — but only while platform billing is enabled. If billing is disabled, over-quota calls remain free; they are still metered so your usage counters stay accurate.
The wallet debit is the single hard gate. Everything else about metering — advancing the daily
counter, recording telemetry — is best-effort and never fails your request. If the wallet cannot
cover a billable call, the request is rejected with 402
insufficient_credits and no work is performed.
Credit units are the platform’s internal accounting unit, shared with the CLI and chat billing. They are not dollars — how many units a credit dollar buys, and how the wallet is topped up, is a platform (staff) decision. This page documents how calls consume units.
The free daily quota
The free allowance is a number of credit units per app per UTC day. It defaults to
10,000 units/day (DEVELOPER_API_FREE_QUOTA_UNITS) and can be overridden
per app on developer_apps.free_quota_override. A value of 0 means unlimited
— the app is never billed and never leaves the free band.
Consumption is tracked in a durable daily counter keyed by app id and UTC date. Because a new date
produces a new counter row, the quota resets at UTC midnight by construction —
there is no scheduled job to wait on. At 00:00 UTC your used_units_today
returns to zero.
An app is considered over quota once its consumed units for the day reach the allowance. Admission is read before the counter is incremented for the current call, so a concurrent burst can overshoot the allowance by the number of in-flight requests. This is an accepted characteristic of v1, not a bug: the overshoot is bounded by your concurrency and is never larger than one call’s worth of units per in-flight request.
Token-metered AI calls
Chat completions are priced by tokens, not per call, because a coding turn can range from a few hundred to tens of thousands of tokens. The credit cost is computed from the prompt and completion token counts using a weighted formula.
The weighted-unit formula
Weighted credit units for one AI call are:
units = max(min_units, ceil((prompt_tokens + completion_tokens * output_weight) / divisor))
The inputs are platform-configured with these defaults:
| Term | Config key | Default | Meaning |
|---|---|---|---|
output_weight | AI_CHAT_TOKEN_BILLING_OUTPUT_WEIGHT | 1.5 | Multiplier applied to completion tokens (output is weighted heavier than input). Must be finite and ≥ 0, else falls back to 1.5. |
divisor | AI_CHAT_TOKEN_BILLING_DIVISOR | 100 | Tokens per credit unit; clamped to a minimum of 1. |
min_units | AI_CHAT_TOKEN_BILLING_MIN_UNITS | 1 | Floor per call so a trivial turn still costs at least one unit. |
With the defaults, a call using 1,200 prompt tokens and 800 completion tokens costs
ceil((1200 + 800 × 1.5) / 100) = ceil(24.0) = 24 units.
Reserve, then settle
Because token counts are not known until inference finishes, an AI call is billed in two steps:
-
Pre-authorization. Before the model runs, if the app is over its free quota
and billing is enabled, a fixed reserve is debited from the wallet
(
AI_CHAT_TOKEN_BILLING_PREAUTH_UNITS, default 250 units). If the wallet cannot cover the reserve, the call is rejected with402before any inference runs, so you are never charged for a model you did not get to use. In the free band (or with billing off), nothing is reserved. - Settlement. After inference, the real weighted units are computed. The daily counter is always advanced by the weighted units — even for free-band calls, so free usage still counts toward your quota. The reserve is then reconciled against the wallet: if actual usage was less than the reserve, the difference is refunded; if it was more, the extra is debited (with a drain-to-zero fallback that charges whatever the wallet can still cover).
If inference itself fails (the provider is unavailable, a stream breaks), the full reserve is refunded
and no units are charged — the endpoint returns 502 upstream_error.
Estimated tokens when usage is omitted
Settlement uses the provider’s reported usage when present. When a provider (or a
streaming turn) omits token counts, Krallo estimates them from the request and response
text at roughly 4 characters per token, so the meter still tracks real token volume
instead of collapsing every call to the 1-unit floor. Estimation only ever runs as a fallback; a
provider-reported count always wins.
The usage object returned in a chat completion reflects the provider’s reported
prompt/completion tokens (0 if the provider omitted them). The credit units actually metered are
derived from the effective counts — provider-reported when available, otherwise estimated.
Flat-priced calls
Non-AI endpoints — listing personas and memories,
reading guilds, and browsing
webhook deliveries — are not token-shaped, so they are priced at
a flat units per call. This defaults to 1 unit
(DEVELOPER_API_UNITS_PER_CALL). Setting it to 0 makes these endpoints free:
they never consume quota and never bill (the request counter still advances for analytics).
Like AI calls, a flat call draws from the free quota first and only debits the wallet once the app is over quota and billing is enabled.
Image generation
Image generation (POST /api/v1/ai/imagine) is flat-priced
per image because the cost is known up front, at AI_IMAGE_TOKEN_COST credit units
(default 10 units, minimum 1). The charge is applied when the job is accepted and
refunded in full if the image is not delivered — a failed job, or one that does
not finish within the request’s wait_timeout_ms, is cancelled and the units are
returned (the endpoint then returns 504 timeout or 502
upstream_error). You are never charged for an image you did not receive.
Refunds on failure
A billable call that is charged and then fails is reversed. The refund backs out every meter the call added: the wallet debit, the daily credit-unit advance, and the request-count increment. The reversal is pinned to the exact UTC day the charge landed on, so it reverses the correct counter row even across a midnight tick. Refunds are idempotent and best-effort — a call is never charged twice, and a refund failure is logged without affecting the response you already received.
Concretely, you are refunded when:
- a flat-priced handler is charged but then returns an error (for example a
404after the meter ran); - an image job fails, is cancelled, or times out before delivering;
- an AI call’s inference fails before settlement (the pre-authorized reserve is returned in full).
Running out of credits
When an app is over its free quota, billing is enabled, and the wallet cannot cover the call, the request
is rejected with HTTP 402 and the code insufficient_credits. No work is
performed and nothing is charged. The error carries the units the call needed and your current balance so
you can decide whether to top up or back off.
Native (envelope) endpoints return the standard error shape:
{
"error": {
"code": "insufficient_credits",
"message": "Insufficient credit balance for this request.",
"required_units": 1,
"balance": 0
},
"meta": { "request_id": "a3d0f9b2-4c6e-4b8a-9d1c-7e5f2a0b8c64" }
}
The OpenAI-compatible chat completions endpoint returns the same information in OpenAI’s error shape (here the pre-authorization reserve could not be covered):
{
"error": {
"message": "Insufficient credit balance for this request.",
"type": "insufficient_credits",
"required_units": 250,
"balance": 0
}
}
See Errors for the full status-code and error-code reference.
Checking your usage
The usage endpoint reports the authenticated app’s quota status for today plus a day-by-day series
you can chart. It is deliberately unmetered — quota visibility stays reachable even
when you are over quota or out of credits — and requires the usage:read scope.
/api/v1/usageQuery parameters
| Name | Type | Required | Description |
|---|---|---|---|
days | integer | No | Length of the returned daily series, ending today (UTC). Clamped to [1, 90]; defaults to 7. Invalid values fall back to 7. |
Response fields
| Field | Type | Description |
|---|---|---|
app_id | string | The authenticated app. |
date | string | Today’s UTC date (YYYY-MM-DD) — the current quota window. |
free_quota_units | integer | The app’s free daily allowance. 0 means unlimited free. |
used_units_today | integer | Credit units consumed so far today. |
remaining_free_units | integer | null | Free units left today (free_quota_units − used_units_today, floored at 0). null when the quota is unlimited. |
billing_enabled | boolean | Whether over-quota calls currently debit the wallet. |
units_per_call | integer | The flat price of a non-AI billable call. |
window_days | integer | The resolved days value for the series. |
daily | array | Dense series, oldest → today, one entry per UTC day: { "date", "credit_units", "requests" }. Days with no traffic are zero-filled. |
Request
curl "https://exe.krallo.com/api/v1/usage?days=7" \
-H "Authorization: Bearer krallo_live_..."
Response
{
"data": {
"app_id": "01J8ZBQ2C4D6E8F0G2H4J6K8M0",
"date": "2026-07-15",
"free_quota_units": 10000,
"used_units_today": 148,
"remaining_free_units": 9852,
"billing_enabled": false,
"units_per_call": 1,
"window_days": 7,
"daily": [
{ "date": "2026-07-09", "credit_units": 0, "requests": 0 },
{ "date": "2026-07-10", "credit_units": 512, "requests": 34 },
{ "date": "2026-07-11", "credit_units": 96, "requests": 12 },
{ "date": "2026-07-12", "credit_units": 0, "requests": 0 },
{ "date": "2026-07-13", "credit_units": 1240, "requests": 71 },
{ "date": "2026-07-14", "credit_units": 380, "requests": 22 },
{ "date": "2026-07-15", "credit_units": 148, "requests": 9 }
]
},
"meta": { "request_id": "b7e1c3a5-9d2f-4e6b-8a0c-4f7d1e9b3a52" }
}
Errors
| Status | Code | When |
|---|---|---|
| 401 | unauthorized · invalid_token | Missing (unauthorized) or invalid (invalid_token) bearer key. See Authentication. |
| 403 | insufficient_scope | The key lacks usage:read. See Scopes. |
| 405 | method_not_allowed | Any method other than GET. |
| 429 | rate_limited | Per-key rate limit exceeded. See Rate limits. |
| 500 | internal_error | Counter store outage. The endpoint fails closed rather than reporting a wrong number. |
Configuration reference
All pricing is platform-configured; these are the keys and their defaults. Per-app overrides (the free allowance) live on the app record. You do not set these — they are listed so you can reason about what a call will cost.
| Key | Default | Controls |
|---|---|---|
DEVELOPER_API_FREE_QUOTA_UNITS | 10000 | Free credit units per app per UTC day (0 = unlimited). Overridable per app via free_quota_override. |
DEVELOPER_API_BILLING_ENABLED | false | Whether over-quota calls debit the prepaid wallet. Off = over-quota calls stay free (still metered). |
DEVELOPER_API_UNITS_PER_CALL | 1 | Flat credit cost of a non-AI billable call (0 = never charge / never consume quota). |
AI_IMAGE_TOKEN_COST | 10 | Flat credit cost of one image generation (minimum 1). |
AI_CHAT_TOKEN_BILLING_OUTPUT_WEIGHT | 1.5 | Completion-token multiplier in the weighted-unit formula. |
AI_CHAT_TOKEN_BILLING_DIVISOR | 100 | Tokens per credit unit (minimum 1). |
AI_CHAT_TOKEN_BILLING_MIN_UNITS | 1 | Floor units per AI call. |
AI_CHAT_TOKEN_BILLING_PREAUTH_UNITS | 250 | Units reserved from the wallet before an over-quota AI call runs. |
Related
- Chat completions — the token-metered AI endpoint in depth.
- Errors — status codes and the
insufficient_creditsshape. - Rate limits — the per-key throttle that runs before metering.
- Authentication — keys, scopes, and linked identities.