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:

  1. Hold. The API reserves the most the call could cost: an estimate of the input, plus all of maxTokens as output. If the wallet holds less than that, the call fails with 402 INSUFFICIENT_CREDITS and the model is never called.
  2. Call. The model runs as usual.
  3. 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.

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 free or pro) 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.

POST/v1/chat · /v1/objects · /v1/agents/run

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. null in the rare case the API couldn't write the wallet; the whole hold was kept.

Request

POST
/v1/chat
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/v1/credits/{userId}

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, inputTokens and outputTokens.

Request

GET
/v1/credits/{userId}
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.

Was this page helpful?