Every request that generates content is paid from your prepaid USD balance. You can also limit how much each API key can spend. This page shows how to read the balance and the limit of a key, and what to do when a request is rejected because of either one.
Both endpoints need an API key sent as Authorization: Bearer. See Authentication.
Read your balance
Section titled “Read your balance”GET /v1/balance returns the balance of your account.
curl "https://api.sakuranbo.ai/v1/balance" \ -H "Authorization: Bearer $SAKURANBO_API_KEY"const response = await fetch("https://api.sakuranbo.ai/v1/balance", { headers: { Authorization: `Bearer ${process.env.SAKURANBO_API_KEY}`, },});const data = await response.json();console.log(data);import os
import requests
response = requests.get( "https://api.sakuranbo.ai/v1/balance", headers={ "Authorization": f"Bearer {os.environ['SAKURANBO_API_KEY']}", },)print(response.json())The response has these fields:
| Name | Type |
|---|---|
object | string |
currency | string |
available_micro_usd | integer |
available_usd | string |
held_micro_usd | integer |
held_usd | string |
available_micro_usdandavailable_usdare the amount that new requests can use.held_micro_usdandheld_usdare the amount that running requests have reserved.
Each amount is given twice: as an integer in micro USD (1 USD = 1,000,000 micro USD), and as a decimal string in USD.
To add balance, open the Billing page of the dashboard. See Pricing and billing.
Reservations
Section titled “Reservations”Sakuranbo AI reserves an amount when it accepts a request that costs money. The reserved amount moves from available to held. When the request ends, the actual cost is charged and the difference returns to available. A request that is cancelled or rejected, or that fails before the model generates any output, gets its whole reservation back, and you are not charged. If a chat stream breaks after the model has started generating output (including reasoning output), you are charged for the request, including the output that was generated, and the rest of the reservation returns to available. See Chat.
In rare cases, an image or video job fails before Sakuranbo AI can confirm whether the model provider received it. The job is then failed with upstream_error. Its reservation stays in held until Sakuranbo AI confirms whether the request is billed, and it does not return to available on its own. If it stays in held, contact support with the job ID.
Because of the reservation, available can be lower than your total balance while requests are running. A new request is checked against available.
Limit what a key can spend
Section titled “Limit what a key can spend”A spending limit belongs to one API key. You set it in the dashboard when you create the key on the API keys page. The limit has an amount in USD and a period. The period is one of none, day, month.
day: the amount that the key can spend in one UTC calendar day. The counter starts again at 00:00 UTC.month: the amount for one UTC calendar month. The counter starts again on the first day of the month at 00:00 UTC.none: the amount for the whole life of the key. The counter never starts again.
A key without a limit can spend as much as your balance allows. It still counts its usage. You can also give a key an expiry date. A request with an expired key returns 401. See Authentication.
Requests that you run in the Playground of the dashboard do not use an API key, so they do not count toward the limit of any key.
Read the limit of a key
Section titled “Read the limit of a key”GET /v1/key returns the key that sent the request, with its limit and its usage.
curl "https://api.sakuranbo.ai/v1/key" \ -H "Authorization: Bearer $SAKURANBO_API_KEY"const response = await fetch("https://api.sakuranbo.ai/v1/key", { headers: { Authorization: `Bearer ${process.env.SAKURANBO_API_KEY}`, },});const data = await response.json();console.log(data);import os
import requests
response = requests.get( "https://api.sakuranbo.ai/v1/key", headers={ "Authorization": f"Bearer {os.environ['SAKURANBO_API_KEY']}", },)print(response.json())The response has these fields:
| Name | Type |
|---|---|
object | string |
id | string |
name | string |
last4 | string |
environment | string |
created_at | string |
expires_at | string | null |
limit | object | null |
usage | object |
limitisnullwhen the key has no limit. Otherwise it has the amount and the period.usage.used_micro_usdandusage.used_usdare the amount that running requests have reserved plus the amount already charged in the current period.usage.remaining_micro_usdandusage.remaining_usdare the amount left under the limit. They arenullwhen the key has no limit.usage.resets_atis the time when the counter starts again. It isnullwhen the period isnone.
When a request is rejected
Section titled “When a request is rejected”A request is rejected with 402 in two cases. error.details.limit_source tells them apart.
balance_insufficient with limit_source set to balance: your available balance is lower than the amount that the request needs to reserve. error.details has requested_micro_usd, available_micro_usd, and shortfall_micro_usd, and the same three amounts in USD as requested_usd, available_usd, and shortfall_usd. Add balance and send the request again.
key_limit_exceeded with limit_source set to key: the usage of the key plus the amount that the request needs to reserve is more than the limit of the key. A request that reaches exactly the limit is accepted. error.details has period, resets_at, requested_micro_usd, limit_micro_usd, and used_micro_usd, and the same three amounts in USD. If both cases apply, the limit of the key is checked first.
Keep these points in mind:
- The reservation counts at once, so a request can be rejected even if its actual cost would fit under the limit. A smaller request, such as a chat request with a lower
max_tokens, reserves less. - Nothing is charged for a rejected request, and no job is created.
- A
402response has noRetry-Afterheader. For a key limit, readerror.details.resets_atto learn when the counter starts again. If it isnull, the limit is for the whole life of the key, and you need to use a key with a higher limit.
Low balance alerts
Section titled “Low balance alerts”You can get a balance.low webhook event when your available balance falls below a level that you choose. Set the level in the settings of the dashboard. See Webhooks.
Balance in the MCP server
Section titled “Balance in the MCP server”The MCP server has the tool get_balance, which reads the same data. See MCP server.
Errors
Section titled “Errors”| Code | HTTP status | Type | Description |
|---|---|---|---|
unauthorized | 401 | authentication_error | The API key is missing, malformed, unknown, revoked, or expired. The response does not say which. |
GET /v1/key returns the same codes. For the full list of codes, see
Errors.
All prices are in USD. Listed prices include our margin.