Skip to main content
A budget key is a short-lived child of an ordinary Sunra API key, with a USD spending cap and its own call receipts. Use one per agent turn or run to give your agent limited LLM access and account for each end user’s usage separately. The cap controls admission using estimated cost: actual settled spend can exceed it, so keep a margin in your own authorization hold. Sunra continues to bill the parent key’s organization; a budget key is a cap plus attribution, not a wallet.

Lifecycle at a glance

  1. Mint with an ordinary key. Save the child id, your business ref, and the secret returned once.
  2. Use the child secret for one or more LLM calls before expiry, with an explicit output limit and a call tag.
  3. Inspect the key’s live spend and call receipts with an ordinary key.
  4. Close when the turn ends. Poll until status: closed, then settle your user’s authorization hold using the final spent_usd.
All management requests use https://api.sunra.ai and Authorization: Key $SUNRA_KEY, where SUNRA_KEY is an active ordinary key. A budget key cannot call management endpoints. See Authentication.

Mint a budget key

Send POST /v1/budget-keys with a JSON body: Omit optional fields instead of sending null. Unknown top-level fields are rejected. If models is omitted, the child inherits the parent’s model restrictions. If supplied, calls must satisfy both that list and the parent’s current allowlist. This example uses jq to set expiry one hour from now and mint a $0.05 key:
A 201 response is the resource itself, with no data wrapper. Example values:
Save secret_key immediately: mint returns it only once. GET, list, receipts, and close never return it. Mint is not idempotent: repeating a request, even with the same ref, creates a different child. If the response is lost, use the recovery endpoint below to find and close the orphaned key before deciding whether to mint another.

Call the LLM API with the child key

Use https://api-llm.sunra.ai with Authorization: Bearer <child secret>. These are the only supported endpoints: Other API operations, including budget-key management and native/media embeddings, refuse the child with 403 budget_key_route_forbidden. An expired or revoked credential can fail authentication first; an unrecognized URL can still return 404. For Chat Completions, send max_tokens or max_completion_tokens on every child request, using the field supported by your model. Missing both returns 400 max_tokens_required. The value must be a positive safe integer; if both fields are supplied, they must be equal. For Messages use max_tokens; for Responses use max_output_tokens. Those two APIs can fill a missing limit from the model’s configured output limit; sending an explicit limit makes the intended allocation clear. n may be omitted, but when present it must be the number 1. best_of is rejected even when it is 1. A child request may carry only the portable generation parameters listed below, plus the output ceiling for the endpoint you are calling. Anything else — provider extension bags and alternative ceiling spellings such as extra_body, generation_config, generationConfig, max_new_tokens — returns 400 invalid_input, because they can raise the real output limit after translation and invalidate the reservation. Accepted top-level fields: model, provider, stream, stream_options, messages, input, system, instructions, tools, tool_choice, parallel_tool_calls, response_format, text, temperature, top_p, top_k, stop, stop_sequences, seed, presence_penalty, frequency_penalty, logit_bias, logprobs, top_logprobs, user, metadata, store, previous_response_id, include, truncation, reasoning, reasoning_effort, thinking, service_tier, safety_identifier, prompt_cache_key, prompt_cache_retention, verbosity, n, encoding_format, dimensions. Requests made with an ordinary key are not restricted this way. Label a call with x-sunra-call-tag, at most 128 printable ASCII characters. It appears as tag in the receipt. If the key has metadata.allowed_tags, a missing tag or a tag outside that set returns 403 budget_call_tag_forbidden. Otherwise, the header is optional. Ordinary keys ignore it entirely. Set CHILD to the returned secret. -i includes response headers:
Save x-sunra-prediction-id to join this call to its receipt. The receipt’s id is byte-identical to that header; do not substitute a provider response ID.

Track spend

Read the key

With ID set to the minted child id:
The response has the same resource fields as mint, without secret_key: Admission reserves the estimated sell cost before a call. Real settled cost can exceed the cap by the estimation error of calls in flight. Keep a margin in your own authorization hold; do not release it based on open-key spend or on an LLM response finishing. An unknown live value is not zero.

Read per-call receipts

limit defaults to 100 and accepts 1–500. While has_more is true, request the next page with after set to next_cursor, which is the last row’s id. Pass it unchanged and URL-encode it. An invalid cursor or one belonging to another child returns 400 invalid_input. An illustrative page after closing a key with one paid call:
Rows include PAID, VOID, PENDING, and UNCOLLECTIBLE attempts. Only PAID rows have a settled cost; every other status has cost_usd: "0.00000000". A refusal before a call record is created is not counted. completed_at and tag can be null; usage can include cache token counts, and wire_id is optional. calls_count counts all attempts in the receipt across all pages, not just the current page. complete is true only for a closed key; open and finalizing receipts can change. For final reconciliation, close the key, fetch every receipt page from the beginning, and sum cost_usd for status: "PAID" using decimal arithmetic. That sum equals the closed spent_usd, and the total number of rows equals the closed calls and receipt calls_count. has_more: false alone does not mean the key is closed.

Close the key

Close takes no body and stops new calls. Calls with the closed child fail with 401 budget_key_revoked. Already admitted calls are not canceled by close and can still settle. Closing is idempotent. Repeating it returns the same final snapshot once closed, including a zero-spend snapshot for a key with no calls. You can also poll GET by ID, but GET returns HTTP 200 even while status is finalizing: always check the body. There is no fixed guarantee for how quickly finalization completes.

End-to-end example

Run this in Bash with curl (supporting --fail-with-body) and jq. Set SUNRA_KEY to your ordinary key as described in Authentication. The example uses google/gemini-2.5-flash; your parent key must allow that model. It mints a $0.05 key for one hour, makes one call, reads its receipt, closes the key, and prints final spend. Receipt values read before close may still be pending. The exit handler also attempts to close the key if the call or receipt read fails. Save the printed ref and child ID in your application so interrupted work can be recovered.
Do not automatically retry mint after an ambiguous failure. For your application’s final per-call accounting, re-read all receipt pages after closed, as described above. Repeating an LLM request is not deduplicated and can incur another charge.

Recovering a lost ticket

Use your saved ref to find child IDs when a mint response or your local ticket record was lost:
The response is { "data": [...], "has_more": false, "next_cursor": null }, with budget-key resources in data and no secrets. ref is required, nonempty, and at most 256 characters. limit defaults to 100 and accepts 1–100. If has_more is true, pass the opaque next_cursor as URL-encoded after to get the next page. Multiple keys can share the same ref; inspect all matches and close orphaned keys. List is for recovery. Use GET by ID or close to obtain the final accounting state; do not settle your authorization hold from an open list result. The secret is never recoverable. Any active ordinary key of the same organization can get, list, close, and read the calls of a budget key. It need not be the key that minted it. After rotating or revoking the parent key, use another active ordinary key in that organization to finish outstanding tickets. Revoking the original parent stops new child calls with 401 budget_key_revoked, but does not finish the child’s final accounting for you.

Errors

Read the HTTP status and error.details.reason; the following table covers budget-specific handling. Existing model-access, parameter-validation, organization-balance, and upstream errors can retain their usual response shapes. Example 402 budget_reserved body:
budget_exhausted and budget_reserved include all four USD strings from the admission decision. budget_state_unknown includes only cap_usd among these amounts; missing spend and reservation values must not be treated as zero.

Limits and guarantees

  • Lifetime: at most 24 hours from mint validation. Expiry stops new calls; you still need to obtain a closed snapshot for final accounting.
  • Best-effort cap: admission reserves estimated sell cost. Actual spend can exceed cap_usd by estimation error across calls in flight. Keep a margin in your own authorization hold.
  • Live versus final: open and finalizing figures can change. Only status: closed provides the authoritative, immutable snapshot; complete: true marks its receipt. The snapshot covers recorded settlement, so settlement that is never recorded or arrives after the snapshot can be absent from the final amount.
  • Billing: the organization that owns the parent key is billed exactly as before, with its existing balance and access restrictions. A budget key does not fund a wallet or transfer your end user’s money into Sunra; your application manages end-user authorization and accounting.