Billing

Billing

The wallet is prepaid. There is no invoice and no postpaid billing period: a call is
priced from the model’s published rate and debited from your balance the moment it
completes.

The unit

Money is stored in micro-USD: one millionth of a dollar, as an integer. A call that
costs a fraction of a cent charges exactly that fraction.

Every amount is reported twice, *_usd and *_micro_usd. Read the micro figure when you
reconcile. The dollar value is rounded for display, and at these sizes rounding is the
whole number.

1 credit = $0.001

Credits still appear on some surfaces as a convenience, because the earliest version of
this product counted them. GET /balance returns both credits and credit_value_usd,
so the two can be reconciled instead of guessed at. New integrations should read
balance_micro_usd.

How a call is priced

fresh_input  = input_tokens - cached_tokens
cached_input = cached_tokens

price_usd = fresh_input  / 1,000,000 * input_per_mtok
          + output       / 1,000,000 * output_per_mtok
          + cached_input / 1,000,000 * cached_rate

pricing.input_per_mtok and pricing.output_per_mtok come from the model row, which is
the same number GET /models publishes. Cached reads are discounted where the model
supports a prompt cache; a model without one bills the whole prompt at the input rate.

The charge is converted to micro-USD and rounded up at the last step, so a fraction of
a micro-dollar never becomes free. A minimum charge applies to a trivially small call,
because a zero-cost row in the ledger is indistinguishable from a model that failed to
bill.

Free models

A model with is_free true settles at zero. Its catalogue entry publishes 0.0 for both
rates, so a client that computes its own cost from pricing shows nothing rather than a
charge that never happened.

Free is not the same as no gate

A free call still needs a small standing balance, because it consumes upstream capacity.
An account spent down to nothing is refused with 402, and the message names the floor
and how to clear it. Reading that as a billing bug is a reasonable mistake: the model
costs nothing per token, but the account is not allowed to be empty.

Reading your own costs

GET /usage/summary?period=30d groups your spend by model with totals, token counts and
an effective usd_per_1k_tokens. That is the report to pass through to your own
customers.

GET /transactions gives per-call rows. Negative is money out, positive is money in,
matching the wallet, so you never have to learn a per-type sign convention.

Each row carries the token triple that produced the charge, so you can recompute any
figure yourself:

{
  "id": "01M73N2HP4HDDZH803J1C120HT",
  "type": "usage",
  "amount_usd": -0.000943,
  "amount_micro_usd": -943,
  "balance_after_micro_usd": 4819057,
  "model": "glm-5.3-flash",
  "provider": "Hisoka",
  "input_tokens": 18630,
  "output_tokens": 65,
  "cached_tokens": 0
}

Reconciling a charge

The only proof that a bill is right is arithmetic that lands on the recorded integer.
Take the row’s tokens, apply the rate from /models, and compare with amount_micro_usd.

# 1. the rate
curl -s https://hisoka.ai/api/v1/models/glm-5.3-flash | jq '.pricing'

# 2. the charge
curl -s "https://hisoka.ai/api/v1/transactions?limit=1" \
  -H "Authorization: Bearer $HISOKA_API_KEY" \
  | jq '.data[0] | {input_tokens, output_tokens, cached_tokens, amount_micro_usd}'

If your arithmetic and the row disagree, that is worth a report, and the request_id
from the original response is what makes it actionable.

A charge that already happened is not re-priced

Rows in your history record what was actually taken. If a model’s rate changes later,
your past rows keep their original amount: re-pricing history at the current list would
erase a charge you can see, which is the one thing a billing page must never do.

Topping up

Top-ups are made in the dashboard and appear as topup rows. Grants from a redeem code
or a promotion appear as grant, and a manual correction appears as adjustment. All
four are visible in /transactions with the same type filter.