Skip to content

Usage and budgets

OpenBot records each call to a model. You can see how many tokens each member, Agent, and user token used, and what they cost. A token is a small piece of text that a model reads or writes. You can also set budgets that stop model calls when too much money is spent. A platform can give organizations credits to pay for the models that the platform shares.

Word Meaning
Usage event One model call: its tokens, its cost, and who made it.
Price What one million tokens of a model cost at one provider.
Budget A spending limit for the organization, a member, an Agent, or a user token, for one day or one month.
Credits Money that the platform gives an organization to pay for the models of the platform.
Micro-USD All money in the API is in micro-USD. One US dollar is 1,000,000 micro-USD.
Platform key A shared model provider. The platform pays for it.
Own key A model provider of the organization. The organization pays the model company itself.

What OpenBot records

OpenBot records each model call that gets an answer. Each usage event has a source:

source What made the call
turn A model step of a Turn in a Session.
compaction A summary of old messages, so the conversation fits the model.
session_memory The memory of a Session.
judge The model that judges Rules.
agent_memory The model that learns Agent memory.
other Any other call.

A usage event also has the Session, the Turn, the Agent, the member and the user token, when the call has them. In a Channel, the member is the person who wrote the message.

Some calls are not recorded:

  • A call that fails before the model answers.
  • A stream that stops before its end, for example when you interrupt a Turn. The model may still count these tokens.
  • A step that runs a host action again. It does not call a model.

Prices

A price belongs to one model at one provider, because the same model can cost a different amount at another provider. Many models cost more when a request is long, and some providers cost less at night. So a price has tiers by input size and periods by time of day.

Each tier has rates. Each rate is the cost of one million tokens, in micro-USD:

Field Which tokens
inputUncached Input tokens that the provider did not read from its cache.
inputCacheRead Input tokens that the provider read from its cache.
inputCacheWrite Input tokens that the provider wrote to its cache.
output Output text and tool calls.
outputReasoning Reasoning tokens. Send null to use the rate of output.

An administrator of the organization sets the prices of the organization’s own providers. These prices are an estimate of what the model company charges. A platform admin sets the prices of the shared providers. For this example, input costs 2.50 US dollars and output costs 10 US dollars for one million tokens, for all input sizes and at all times:

Terminal window
curl -X PUT -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4.1","tiers":[{"upToInputTokens":null,"inputUncached":2500000,"inputCacheRead":625000,"inputCacheWrite":0,"output":10000000,"outputReasoning":null}]}' \
https://api.example.com/v1/organization/model_providers/prov_4c1e/prices
Route What it does
GET /v1/organization/model_providers/:providerId/prices Lists the current price of each model of your own provider.
PUT /v1/organization/model_providers/:providerId/prices Sets a new price for one model of your own provider.
GET /v1/platform/model_providers/:providerId/prices Lists the current price of each model of a shared provider.
PUT /v1/platform/model_providers/:providerId/prices Sets a new price for one model of a shared provider.

A provider can only price the models that it links. Another model gives 400 with ModelProviderInvalid. The model catalog shows the current price of each model.

A new price applies to calls that start from now on. Each usage event keeps the cost from the time of the call, so old costs never change. OpenBot rounds the cost of each call up to a whole micro-USD.

A model without a price costs 0. Its calls are still recorded, but they do not count toward budgets and do not take credits.

Tiers by input size

tiers is a list, from small to large. Each tier has upToInputTokens: the most input tokens that a call in this tier can have. The input tokens of a call are all its input tokens, from the cache or not. The last tier has upToInputTokens: null, because it has no upper limit. You can have up to 20 tiers.

A call uses the first tier that its input fits. All tokens of the call then cost the rates of that tier, not only the tokens over the limit. Most providers bill long requests in this way.

For example, a model costs less up to 32,000 input tokens, more up to 128,000, and the most above that:

{
"model": "qwen-plus",
"tiers": [
{
"upToInputTokens": 32000,
"inputUncached": 800000,
"inputCacheRead": 160000,
"inputCacheWrite": 0,
"output": 2000000,
"outputReasoning": 8000000
},
{
"upToInputTokens": 128000,
"inputUncached": 2400000,
"inputCacheRead": 480000,
"inputCacheWrite": 0,
"output": 6000000,
"outputReasoning": 24000000
},
{
"upToInputTokens": null,
"inputUncached": 4800000,
"inputCacheRead": 960000,
"inputCacheWrite": 0,
"output": 12000000,
"outputReasoning": 48000000
}
]
}

A call with 50,000 input tokens uses the second tier.

Periods by time of day

periods gives other tiers for some times, such as a lower price at night. Each period has a name, one or more windows, and its own tiers. timeZone says in which time zone the windows are. It is an IANA name, such as UTC or Asia/Shanghai. Leave it out for UTC.

Window field What it is
start The start, as HH:MM. The window includes this minute.
end The end, as HH:MM, up to 24:00. The window stops before this minute.
days The days on which the window starts: mon, tue, wed, thu, fri, sat, sun. Leave it out for every day.

When end is before start, the window goes past midnight. It belongs to the day on which it starts. For example, {"start":"22:00","end":"02:00","days":["fri"]} holds Friday 23:00 and Saturday 01:00, but not Friday 01:00.

OpenBot takes the time at which the call starts. It uses the first period with a window that holds this time. When no period holds it, the call uses the tiers of the price. In each case, the call then picks its tier by input size.

For example, a provider costs half from 00:30 to 08:30 in Beijing time:

{
"model": "deepseek-chat",
"timeZone": "Asia/Shanghai",
"tiers": [
{
"upToInputTokens": null,
"inputUncached": 2000000,
"inputCacheRead": 500000,
"inputCacheWrite": 0,
"output": 8000000,
"outputReasoning": null
}
],
"periods": [
{
"name": "off-peak",
"windows": [{ "start": "00:30", "end": "08:30" }],
"tiers": [
{
"upToInputTokens": null,
"inputUncached": 1000000,
"inputCacheRead": 250000,
"inputCacheWrite": 0,
"output": 4000000,
"outputReasoning": null
}
]
}
]
}

Leave periods out when the price is the same at all times.


See the usage

GET /v1/organization/usage adds up the tokens and the cost in a time range. It needs usage.read.

Query What it does
from The start, as an ISO 8601 time. The default is the start of the current month in UTC.
to The end, as an ISO 8601 time. The default is now.
groupBy day (the default), model, provider, member, agent, token or source.
Terminal window
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \
"https://api.example.com/v1/organization/usage?groupBy=member"
{
"from": "2026-10-01T00:00:00.000Z",
"to": "2026-10-23T09:00:00.001Z",
"groupBy": "member",
"total": {
"calls": 42,
"inputUncached": 81200,
"inputCacheRead": 20400,
"inputCacheWrite": 0,
"outputText": 9800,
"outputReasoning": 1200,
"costMicros": 325250
},
"items": [
{
"key": "u_123",
"calls": 42,
"inputUncached": 81200,
"inputCacheRead": 20400,
"inputCacheWrite": 0,
"outputText": 9800,
"outputReasoning": 1200,
"costMicros": 325250
}
]
}

key is the value of the group, such as a user ID or a day. It is null for calls without that value, such as calls without an Agent.

GET /v1/organization/usage/events lists the single usage events, newest first. It takes from, to, limit (1 to 200, the default is 50) and after. Send the next value of a page as after to get the next page. Each event has priced and charged. charged is true when the cost came from the credits.


Budgets

A budget stops spending at a limit. You can set a budget on four scopes:

scope scopeId What counts
organization Leave it out. It is the organization ID. Every call of the organization.
member The user ID of a member. The calls that work for this member.
agent The ID of an Agent. The calls of this Agent.
token The ID of a user token. The calls of work that this token started.

Each budget has a period: day or month. Both start at midnight UTC. Each scope can have one budget for each period.

Each budget also has a mode:

  • A hard budget stops model calls when the spend in the period reaches limitMicros. The call fails, so the Turn fails with a model error. The next period starts again from 0.
  • A soft budget never stops a call. It only shows exceeded: true.

A budget counts every priced call, also the calls with the organization’s own key. OpenBot checks the budgets before each model call, not only at the start of a Turn. A long Turn stops at its next model call. So the spend can go over the limit by the cost of one call.

Terminal window
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"agent","scopeId":"agt_7d2c","period":"month","limitMicros":50000000,"mode":"hard"}' \
https://api.example.com/v1/organization/budgets
{
"id": "budget_5c9e",
"scope": "agent",
"scopeId": "agt_7d2c",
"period": "month",
"limitMicros": 50000000,
"mode": "hard",
"periodStart": "2026-10-01T00:00:00.000Z",
"spentMicros": 1250000,
"exceeded": false,
"version": 1,
"createdAt": "2026-10-23T09:00:00.000Z",
"updatedAt": "2026-10-23T09:00:00.000Z"
}
Route What it does
GET /v1/organization/budgets Lists the budgets, with their spend now.
POST /v1/organization/budgets Adds a budget.
GET /v1/organization/budgets/:budgetId Reads one budget.
PATCH /v1/organization/budgets/:budgetId Changes limitMicros or mode. Send the version as expectedVersion.
DELETE /v1/organization/budgets/:budgetId Removes a budget.

Reading budgets needs budgets.read. Changing them needs budgets.write. Each change goes to the audit log.


Credits

Credits let a platform sell or give the use of its shared models. They are off by default. Then an organization can use the shared models without limit, unless a budget stops it.

A platform admin turns credits on for one organization, and gives credits:

Terminal window
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":true}' \
https://api.example.com/v1/platform/organizations/org_8f3a/credits
curl -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"amountMicros":20000000,"note":"Trial for October"}' \
https://api.example.com/v1/platform/organizations/org_8f3a/credits/grants

A negative amountMicros takes credits back. Each grant goes to the platform audit log.

While credits are on:

  • Each call to a model of the platform takes its cost from the balance.
  • When the balance is 0 or less, calls to the models of the platform fail. Calls with the organization’s own key still work.
  • The balance can go below 0 by the cost of one call, because OpenBot checks the balance before a call and takes the cost after it.

Calls with the organization’s own key never take credits, also when credits are on.

GET /v1/organization/credits shows enabled, balanceMicros and the newest 50 grants. It needs usage.read. A platform admin reads the same with GET /v1/platform/organizations/:organizationId/credits.

OpenBot has no payments yet. A platform that sells credits records the payment in its own system, then gives the credits with the API.


When a call is stopped

When a budget or the credits stop a call, the model call fails with an AiError of the reason QuotaExhaustedError. The Turn ends with the status Failed and the code model_error. The metadata of the error says why, in openbot.reason: budget or credits. For a budget, openbot.budgetId names it.

When OpenBot cannot read the budgets, for example because the database is down, it stops the call too.


Rate limit

A platform admin can limit how many API requests each caller sends in one minute. Each user token counts on its own. A signed-in person without a token counts as one caller. Every instance of OpenBot shares the count. Set rateLimit in the platform settings:

Terminal window
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"expectedVersion":4,"rateLimit":{"requestsPerMinute":600}}' \
https://api.example.com/v1/platform/settings

null means no limit. This is the default. A caller over the limit gets 429 with RateLimited. retryAfterSeconds says when the next minute starts. The limit covers the API of organizations. It does not cover /v1/platform, sign-in, or webhooks.


Errors

Status Error When
400 UsageQueryInvalid from or to is not an ISO 8601 time, or from is not before to.
400 BudgetInvalid The scope ID is missing, or no member or token of the organization has it.
400 ModelProviderInvalid The provider does not link the model that you want to price.
403 PermissionDenied The caller does not have usage.read, budgets.read or budgets.write.
404 BudgetNotFound No budget of the organization has this ID.
409 BudgetConflict The scope already has a budget for this period, or the budget changed.
429 RateLimited The caller sent too many requests in this minute.
503 BillingUnavailable OpenBot cannot reach its database.

See the full shapes in the Usage reference and the Budgets reference.