Skip to content

Latest commit

 

History

History
383 lines (309 loc) · 19.9 KB

billing.md

File metadata and controls

383 lines (309 loc) · 19.9 KB

Billing

A deployment can hold its own API key for a hosted provider. Every organisation's models of that provider then use that key, and the deployment bills each organisation for what it used, at the provider's list price.

How an organisation reads its bill and pays: Billing. This page explains how it works, how to set it up, and why.

This is how a public instance resells a provider. Without such a key, nothing changes: each organisation enters its own key, as described in providers.md.

A deployment can also charge a gateway fee on every other model: self-hosted ones, and ones on the organisation's own keys.

With a PostFinance Checkout account, organisations also pay in advance, by card, and you can charge an organisation a fixed fee every month or year.

Set a provider key

KEERA_PROVIDER_ANTHROPIC_API_KEY=sk-ant-...

The variables are named after the provider: KEERA_PROVIDER_<NAME>_API_KEY, with the name in capitals and hyphens as underscores, such as KEERA_PROVIDER_STEPPING_STONE_API_KEY. keera model catalogue lists the names.

Setting What it does
KEERA_PROVIDER_<NAME>_API_KEY The deployment's key at that provider.
KEERA_PROVIDER_INFOMANIAK_PRODUCT_ID Required with an Infomaniak key: the product id of your AI Service.

A misspelt KEERA_PROVIDER_* setting stops the start, so a key never quietly goes unused. So does a product id without its key, or for a provider that takes none.

What changes for a model of that provider

  • No key of its own. The panel hides the provider key field, and the control API refuses one. A key the organisation stored before is removed the next time the model is saved, and is never sent.
  • The provider's own endpoint. The backend is always the provider's address, whatever the model says. Otherwise an administrator could point the model at a server of their own and read the key.
  • The provider's list prices. The prices are the ones in the provider table, including the dearer ones for a long prompt (providers.md). The organisation cannot change them. Its budgets and reports use them too.
  • Only models in the catalogue. A model id the catalogue does not know cannot be priced, so it is refused. A model saved before the key was set is sent without a key, and the gateway logs why.
  • No hosted tools. Tools the provider runs itself, such as web search or code execution, are removed from every request (mcp.md).
  • No dearer modes. Fields that make the provider charge more than its list price are removed from every request: speed (Anthropic's fast mode), inference_geo, and a service_tier other than auto, default, flex or standard_only. The model answers at its normal speed and price. The response header X-Keera-Removed-Fields names what was removed.
  • Nothing stored at OpenAI, which every organisation shares (Three request shapes).

Through a router, these apply when any model the request may reach is on the deployment's key, whichever one answers it.

A subscription model is not affected: each person's own Claude plan pays for it (subscriptions.md). Self-hosted models are not affected either, except by the gateway fee.

The gateway fee

A model that is not on a deployment's key earns the deployment nothing, but every request still goes through the gateway. The gateway fee charges for that:

KEERA_BILLING_GATEWAY_FEE=0.05   # CHF per million tokens
Setting What it does
KEERA_BILLING_GATEWAY_FEE CHF per million tokens on every model not on the deployment's keys. Default 0: no fee. At most 100.

It applies to self-hosted models, models on the organisation's own provider keys, and subscription models. A Claude plan pays for the model, not for the gateway. Every token costs the same, input, output, cached or not, since the gateway handles each one the same way.

Who counts the tokens. A hosted provider's usage record is used as it is, when every backend of the model is on the provider's own host over https. Any other backend can report whatever it likes, so the gateway counts its tokens itself. That includes a self-hosted model, and a model that names a provider but is sent elsewhere, such as through an egress proxy. The count is made from what the gateway passed on: the whole request body it sent, and the text that came back (content, reasoning and tool calls), at 4 bytes a token. An image counts by its size in the request. The usage row still shows the backend's own numbers, but its cost is the gateway's count.

Self-hosted models cost the fee. The fee is every price of a self-hosted model: input, output, cached and cache write. Without a fee, they are 0. Nobody sets them: the panel shows them and does not let anyone change them, keera model refuses --price-* for a self-hosted model, and the control API replaces any price it is sent, as does a template file. Budgets and reports use the fee too, so they match the bill. A model saved with a price of its own is priced at the fee from the next start. Models at a hosted provider keep their own prices: the provider's bill is the organisation's.

Every call pays it, like a call on the deployment's key: the request itself, and the calls a filter or a router makes to such a model. keera model check too. Each is a row in the monthly report, marked as the gateway fee, apart from list-price rows.

It needs credit when organisations pay in advance, so switching on the fee stops every organisation without credit, its own models included. Grant credit, or warn organisations, before you set it. Without payments, the fee is only in the monthly report, to bill by invoice. Payments need no provider key when the fee is set.

What is billed

Every call to a model on the deployment's key is billed, at list price: the request itself, and the calls a filter or a router makes to it. Each call is one row in billing_lines, written in the same batch as the usage row.

keera model check, keera filter check and keera router check are billed too, and need credit like a request. They write no usage row. A model check is billed at its output ceiling, because it does not read the usage record.

Token counts are the provider's own. When a client hangs up, the gateway goes on reading the answer, for up to 15 minutes, until the provider's usage record arrives. Otherwise the reasoning a model never streamed would go unbilled. The request's row then has the real token counts and the provider's status, still marked as cancelled. For the same reason, a provider gets 15 minutes to start answering, not KEERA_UPSTREAM_HEADER_TIMEOUT.

Cache writes are billed at their own rate, like the rest of Keera's accounting (providers.md), including Anthropic's dearer one-hour cache writes.

When no usage record comes back, the call is billed the most it may have used: its whole body as prompt and its whole output ceiling as answer. This happens when the answer is too large to read (KEERA_MAX_RESPONSE_BYTES), when the provider takes longer than the gateway waits, or when it sends no record. For the request's own call, its usage row is marked as an estimate and the gateway logs a warning. The calls of filters and routers are billed the same way, but without the mark or the warning.

The monthly report

keera billing shows it (The monthly bill). An operator picks one organisation with KEERA_ORG=<org> keera billing.

With payments on, a last column says how much of the amount was taken from prepaid credit. For an organisation billed by invoice it is the whole amount, as its credit goes below zero.

Administrators see their own organisation. Operators see every organisation.

The same report is at GET /control/v1/billing?month=2026-09, and as CSV with &format=csv. Months are UTC, like budgets.

In the panel, only administrators and operators see it, and only on a deployment that holds at least one provider key or charges the gateway fee.

Pay in advance

With a PostFinance Checkout account, organisations buy credit in CHF before they use the deployment's keys. Each call takes its list price from the credit. When the credit is used up, calls to such models are refused with 402 credit_exhausted. So are calls while running requests hold all that is left. The organisation's own models keep working, and so do subscription models, unless the deployment charges the gateway fee.

Set it up

  1. In PostFinance Checkout, create an application user under Account > Users > Application Users, and give it a role that may create transactions. Write down its user id and authentication key: the key is shown only once.
  2. Under Space > Settings > General > Webhook URLs, add https://<your gateway>/control/billing/postfinance. Add a webhook listener for the entity Transaction with the states Fulfill, Failed, Decline and Voided.
  3. Set these, then restart:
KEERA_BILLING_POSTFINANCE_SPACE_ID=4711
KEERA_BILLING_POSTFINANCE_USER_ID=123456
KEERA_BILLING_POSTFINANCE_AUTH_KEY=...  # the authentication key, as shown
KEERA_BILLING_VAT=8.1                   # optional
Setting What it does
KEERA_BILLING_POSTFINANCE_SPACE_ID Your space in PostFinance Checkout. All three settings switch payments on.
KEERA_BILLING_POSTFINANCE_USER_ID The application user's id.
KEERA_BILLING_POSTFINANCE_AUTH_KEY The application user's authentication key, in base64 as PostFinance shows it.
KEERA_BILLING_VAT VAT added on top of each payment, in percent, such as 8.1. Default 0. It applies to every organisation alike.
KEERA_BILLING_POSTFINANCE_URL The API's address. Default https://checkout.postfinance.ch, which serves test spaces too.

Payments also need at least one KEERA_PROVIDER_<NAME>_API_KEY or KEERA_BILLING_GATEWAY_FEE, and KEERA_PUBLIC_URL set to an https address: PostFinance sends people back there after they pay. On your own machine, http://localhost, any *.localhost name and a loopback address such as http://127.0.0.1 work too.

A setting that cannot work stops the start: only some of the three KEERA_BILLING_POSTFINANCE_* settings, KEERA_BILLING_POSTFINANCE_URL or KEERA_BILLING_VAT without them, a space id that is not a number, an authentication key that is not base64, a VAT that is not a number from 0 to 100, neither a provider key nor a gateway fee, or a KEERA_PUBLIC_URL that is not https.

Turning it on

An organisation that never paid has no credit. So when you switch payments on, every organisation is refused on the deployment's keys until it pays, or until you grant it credit.

Paying

An administrator adds credit in the panel or with keera billing topup (Pay in advance). Afterwards PostFinance sends them back to the panel. The credit is added once PostFinance says the payment went through.

A payment is from CHF 1 to CHF 10,000, in whole centimes. VAT and a transaction fee are added on top. The fee is CHF 0.18 plus 1.55% of the credit, with the 1.55% at least CHF 0.20: CHF 0.38 on CHF 10, CHF 1.73 on CHF 100. It pays for the payment and is not added to the credit. The panel shows the VAT, the fee and the total under the amount. Automatic top-ups carry the fee too.

An organisation that signed itself up is limited until its first payment (What a new organisation can use).

Receipts

Every paid payment has a receipt: the invoice PostFinance makes for it, as a PDF.

Keera fetches the receipt from PostFinance each time, so it looks the way the invoice template of your space says. Put your company's address and VAT number there. A grant has no receipt, because nobody paid for it.

Automatic top-up

With a saved card, the organisation can top up by itself (Top up automatically). The card is charged without anyone on the payment page. The amount must be at least the threshold, so one charge lifts the credit above it. A card is charged at most once every 10 minutes, so a burst of traffic is not paid off by a run of charges. Every 30 seconds, one replica checks.

When a charge is declined, no other is tried. The panel shows why. Saving the automatic top-up again, or a top-up that is paid, tries again. A grant does not. When PostFinance cannot be reached, refuses Keera's credentials or asks it to slow down, the card is not at fault, and the next try is after 10 minutes.

Forgetting the card removes it here and at PostFinance, and turns the automatic top-up off. Saving a new card replaces the old one. An organisation with recurring charges cannot forget its card.

For operators

keera billing grant --org <org> --amount 50 --note "trial"
keera billing invoiced --org <org>

A grant adds credit without a payment, for a trial, a refund or a bank transfer. A negative amount takes credit away. The note is shown to the organisation. A grant does not lift the limit of an organisation that signed up; keera org set <org> --lift-limit does.

An organisation billed by invoice may use the keys without credit. Its use is still metered, and its credit goes below zero. Bill it from the monthly report: its "from credit" column is the whole amount, not money already paid. --off makes it pay in advance again.

Recurring charges

For a dedicated or enterprise customer, charge a fixed fee every month or year, such as for a dedicated model:

keera billing charge --org <org> --name "Dedicated model" --amount 2000 --every month
keera billing charge --org <org> --name "Support" --amount 12000 --every year --start 2027-01-01
keera billing charges                 # every organisation's, for an operator
keera billing end-charge <charge>

Each period is charged to the saved card when it starts, at midnight UTC, with VAT and the transaction fee on top. It is not credited. Periods count from the start day, so one from the 31st is charged on the last day of shorter months. --start is today or later, so no past period is charged at once. Ending a charge keeps its payments; a period not paid by then is no longer due.

A recurring charge needs a saved card. While a period is due and no saved card can pay it, every call of the organisation is refused with 402 charge_due, whichever model, the organisation's own ones too. This is the case when:

  • there is no saved card. Start the charge a few days ahead to give the organisation time to save one.
  • PostFinance declined the card. It is not tried again until a payment goes through.

An administrator then pays the period on the payment page, under Billing or with keera billing pay-charge, which also saves the card. Any paid payment clears a declined charge, so the card is tried again on the next round. Paying ahead is refused while a card that works will pay the period, and so is paying a period the saved card is being charged for.

The panel shows a banner on every screen while a charge is due, or will be without a card. The card stays while the organisation has charges: it can be replaced by paying with another, not forgotten.

A charge works like an automatic top-up: one replica charges due periods every 30 seconds, at most one at a time per charge, and not again within 10 minutes when PostFinance could not be reached. A period paid on the payment page in the last hour is not charged to the card as well. If both are paid anyway, the gateway logs an error naming the payment to refund.

How a payment is confirmed

PostFinance calls the webhook when a transaction changes. The call proves nothing, so Keera reads the transaction back over the authenticated API. A payment counts as paid only in PostFinance's state Fulfill, and only with the amount and currency Keera asked for. A payment Keera already marked failed is still read, so one paid late is still credited.

A lost webhook does not lose a payment. Opening Billing checks at once up to 5 pending payments the organisation started in the last hour, and every 30 seconds one replica checks those pending for more than a minute, for up to a day. After that, only the webhook settles a payment. A payment interrupted before PostFinance made a transaction for it is marked failed after 10 minutes. An automatic top-up still open after a day is marked failed, and the log names its transaction, so someone can look it up in PostFinance.

Keep in mind

  • The list prices are a table in the build. The comment above it in internal/catalog/providers.go says when each was last checked. A price change takes effect with the next build. Rows already written keep the price they were billed at.
  • One key for everyone. The provider's rate limits apply to all organisations together. Give each organisation a budget and rate limits, so one cannot use up the key for the rest.
  • Keep the rows. KEERA_USAGE_RETENTION does not delete billing rows or payments, and neither does deleting an organisation. That removes only its saved card, here and at PostFinance, and its recurring charges. Back up the database.
  • Running requests hold credit. A request is charged when it ends. Until then, it holds the most it may cost: its whole body as prompt, plus its output ceiling (64,000 tokens if it sets none), at the dearest model it may reach. Each route step's decision is added twice, since an unreadable answer is asked again, and so is a rewrite of the whole body by each filter. A new request starts only while some credit is not held. So many requests started together cannot all spend the same credit.
  • Credit overshoots a little. The last request let in may cost more than what was free. A request without an output ceiling may write more than it holds. Each replica holds only its own requests, and reads the balance every KEERA_SPEND_REFRESH. So credit can go slightly below zero.
  • A router or filter on the keys counts. A request needs credit when its model, any model its router may choose, the model a route step in it decides with, or one of its filters' models is on the deployment's keys.
  • Dropped events are not billed. If the database is away for longer than the usage buffer holds, events are dropped, and their billing rows with them. /readyz reports the count as usage_dropped. Events that carry billing rows have a buffer of their own, written first, so a flood of refused requests cannot push them out. When Postgres refuses one event, only that event is lost: its usage row and daily totals, and, if it has no billing rows, what it counted against budgets. The rest of its batch and its own billing rows are still written.