Credits
Credits let your service charge model calls to your own users. Send userId with a call, and the call is paid from that user's wallet. When the wallet is empty, the call is refused with 402 before any model runs.
Credits are off until the Vovix admin turns them on for your service and sets its plans. Until then, userId is accepted and ignored.
How a call is metered
1 credit = $0.000001 of model cost. A call costs inputTokens × price.input + outputTokens × price.output credits, with prices in USD per million tokens from the catalog. Credits are counted by cost, not by raw tokens, because the same 1,000 tokens cost about 100× more on the most expensive model than on the cheapest.
Each metered call goes through three steps:
- Hold. The API reserves the most the call could cost: an estimate of the input, plus all of
maxTokensas output. If the wallet holds less than that, the call fails with402 INSUFFICIENT_CREDITSand the model is never called. - Call. The model runs as usual.
- Settle. The API charges the real cost and returns the rest of the hold. If the call fails, the whole hold comes back, except on
504 TIMEOUT, which keeps the hold because the model may already have run.
The hold is an atomic, conditional write. Twenty requests at once against a wallet that can pay for one of them: exactly one runs.
Keep maxTokens close to what you need. The hold counts every token you allow, so a user with 5,000 credits left can't start a call that might cost 20,000, even if it would really cost 300.
The input size is estimated without the model's tokenizer, so a call can occasionally cost more than was held. The balance then goes slightly negative, and the user's next call is refused until they top up or their allowance resets.
Allowance and purchased credits
Each wallet has two parts:
- Monthly allowance. Set by the user's plan (for example
freeorpro) and granted again at the start of each month, 00:00 UTC. Unused allowance does not carry over. - Purchased credits. Added by a top-up. They never expire.
Calls spend the allowance first, then purchased credits. A wallet is created by the user's first call, on your service's default plan.
Send the user
Add userId to the body of any model call. If your service is set to require it, a call without userId fails with 400 USER_REQUIRED.
Attribute
- Name
userId- Type
- string
- Description
A stable id for your user: a Cognito
sub, a UUID, an email. 1–128 characters from letters, digits and_ . : @ + = -. Must not contain#,/or spaces.
Response
Metered calls add a credits object to the usual response.
- Name
credits.userId- Type
- string
- Description
The wallet that paid.
- Name
credits.charged- Type
- integer
- Description
What this call cost, in credits.
- Name
credits.balance- Type
- integer | null
- Description
The wallet after this call.
nullin the rare case the API couldn't write the wallet; the whole hold was kept.
Request
curl https://api.llm.vovix.io/v1/chat \
-H "x-api-key: $LLM_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "google.gemma-3-12b-it",
"messages": [{ "role": "user", "content": "A red fox in the snow" }],
"maxTokens": 120,
"userId": "7f3c2a90-user"
}'
Response
{
"model": "google.gemma-3-12b-it",
"text": "Dawn light catches a red fox mid-step…",
"usage": { "inputTokens": 31, "outputTokens": 24 },
"costUsd": 0.00000975,
"credits": { "userId": "7f3c2a90-user", "charged": 10, "balance": 49990 },
"latencyMs": 842
}
Get a user's credits
Returns a user's wallet as of now. Use it to show a balance, or to check before starting a long job. A user who has never called gets the wallet their first call would start with, and exists is false. Returns 409 CREDITS_NOT_ENABLED if your service doesn't meter credits.
Response
- Name
plan- Type
- string
- Description
The user's plan.
- Name
balance- Type
- integer
- Description
Credits spendable now: allowance left plus purchased left. Can be negative after an overdrawn call.
- Name
allowanceLeft- Type
- integer
- Description
What's left of this month's allowance.
- Name
purchasedLeft- Type
- integer
- Description
Purchased credits left. They never expire.
- Name
monthlyAllowance- Type
- integer
- Description
What the plan grants each month.
- Name
resetsAt- Type
- string
- Description
When the allowance is granted again: the first day of next month, 00:00 UTC.
- Name
used- Type
- object
- Description
This month's
credits,calls,inputTokensandoutputTokens.
Request
curl https://api.llm.vovix.io/v1/credits/7f3c2a90-user \
-H "x-api-key: $LLM_API_KEY"
Response
{
"userId": "7f3c2a90-user",
"exists": true,
"plan": "free",
"period": "2026-09",
"monthlyAllowance": 50000,
"balance": 61200,
"allowanceLeft": 11200,
"purchasedLeft": 50000,
"resetsAt": "2026-10-01T00:00:00.000Z",
"used": { "credits": 38800, "calls": 412, "inputTokens": 190233, "outputTokens": 88412 },
"createdAt": "2026-09-02T08:14:51.203Z"
}
Handle an empty wallet
When a user runs out, the call fails with 402 and nothing is charged. Don't retry: the wallet stays empty until it's topped up or the month resets. The SDK never retries a 402.
402 response
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "User 7f3c2a90-user has 4 credits; this call needs up to 9 (lower maxTokens, or top up — the plan allowance resets 2026-10-01T00:00:00.000Z)"
}
}
try {
await llm.chat({ ...req, userId })
} catch (e) {
if (e instanceof VovixLlmError && e.code === 'INSUFFICIENT_CREDITS') return showUpgrade()
throw e
}
Top-ups
For now, the Vovix admin tops up wallets by hand from the admin pages, and changes a user's plan there. Each top-up carries a payment reference, so applying the same payment twice changes the balance only once. Moving a user to a bigger plan grants the difference right away. Moving to a smaller plan takes effect next month.
Credits are separate from your service's daily budget, and both apply: a call has to pass the service budget and then the user's wallet.