Skip to main content
Use the freeze -> consume / unfreeze flow when cost is unknown upfront. This is the right choice for LLM inference, async tasks, and flows where final cost is only known after execution. transaction_id is the unique ID for this charge transaction and stays the same across freeze, consume, and unfreeze.
1

Freeze

Reserve a maximum budget before starting work. After freezing, credits are no longer available to spend, but they have not been consumed yet.
curl -X POST https://api.velobase.io/v1/billing/freeze \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "user_987",
    "transaction_id": "llm_chat_001",
    "amount": 100
  }'
Freeze Parameters
body.customer_id
string
required
The unique identifier for this customer. The customer must already exist and have enough available credits to cover the freeze amount.
body.transaction_id
string
required
Unique ID for this charge transaction. Use the same value for the subsequent consume or unfreeze call.
body.amount
number
required
Maximum credits to reserve. Must be greater than 0. Set this to the upper bound of expected cost.
body.credit_types
string[]
Restrict the freeze to these wallet categories only. If omitted, the system may use any active credits. The same categories are used automatically during consume.
body.business_type
string
Transaction category for reporting. Examples: TASK, ORDER, TOKEN_USAGE.
body.description
string
A human-readable note for this freeze.
Freeze Response
{
  "transaction_id": "llm_chat_001",
  "frozen_amount": 100,
  "freeze_details": [
    {
      "account_id": "...",
      "credit_type": "default",
      "amount": 100
    }
  ],
  "is_idempotent_replay": false
}
transaction_id
string
The transaction ID passed in the request.
frozen_amount
number
Total credits reserved by this freeze.
freeze_details
array
Breakdown of which credit accounts were frozen. The system may freeze across multiple wallet categories.
is_idempotent_replay
boolean
true when the response was returned from a previous request with the same transaction_id.
Returns 400 with insufficient balance if the user does not have enough available credits.If credit_types is provided during freeze, and the selected wallet categories do not have enough credits, the error message becomes insufficient balance in selected credit_types.
2

Consume

Settle the actual cost after the task finishes. Any unused credits are returned automatically.
curl -X POST https://api.velobase.io/v1/billing/consume \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "llm_chat_001",
    "actual_amount": 73
  }'
Example: freeze 100, finish with an actual cost of 73, and the remaining 27 is automatically returned.Consume Parameters
body.transaction_id
string
required
The same transaction ID used in the freeze step.
body.actual_amount
number
Actual credits to consume. If less than the frozen amount, the remainder is returned. Defaults to the full frozen amount if omitted.
Consume Response
{
  "transaction_id": "llm_chat_001",
  "consumed_amount": 73,
  "returned_amount": 27,
  "consume_details": [
    {
      "account_id": "...",
      "credit_type": "default",
      "amount": 73
    }
  ],
  "consumed_at": "2026-04-07T12:00:00.000Z",
  "is_idempotent_replay": false
}
consumed_amount
number
Credits permanently consumed by this operation.
returned_amount
number
Unused frozen credits automatically returned to the available balance (frozen_amount - actual_amount).
consume_details
array
Breakdown of which credit accounts were consumed.
consumed_at
string
ISO 8601 timestamp when consumption completed.
is_idempotent_replay
boolean
true when the response was returned from a previous request with the same transaction_id.
3

Unfreeze (Cancel)

Release frozen credits without consuming. Use when the task is cancelled.
curl -X POST https://api.velobase.io/v1/billing/unfreeze \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "llm_chat_001"
  }'
Unfreeze Response
{
  "transaction_id": "llm_chat_001",
  "unfrozen_amount": 100,
  "unfreeze_details": [
    {
      "account_id": "...",
      "credit_type": "default",
      "amount": 100
    }
  ],
  "unfrozen_at": "2026-04-07T12:01:00.000Z",
  "is_idempotent_replay": false
}
unfrozen_amount
number
Total credits returned to the available balance.
unfreeze_details
array
Breakdown of which credit accounts were unfrozen.
unfrozen_at
string
ISO 8601 timestamp when the unfreeze completed.
is_idempotent_replay
boolean
true when the response was returned from a previous request with the same transaction_id.

Wallet Categories and Consumption Order

  • The system automatically freezes and consumes from all available credits.
  • If credit_types is provided during freeze, only those wallet categories are eligible for this transaction.
  • Callers do not choose which wallet category to use.
  • Credits that expire sooner are handled first; if expiry is the same, older accounts are handled first.
  • Only credits that are currently active participate in freeze and consume. Credits that have not started yet or have already expired are ignored.
  • Use the customer balance API to inspect remaining credits by credit_type.

Idempotency

All three operations are idempotent on transaction_id. Duplicate calls return the original result with is_idempotent_replay: true.

Verify the Result

curl https://api.velobase.io/v1/customers/user_987 \
  -H "Authorization: Bearer your_api_key_here"
After each step, check:
  • whether balance.frozen increases after freeze
  • whether balance.used increases after consume
  • whether balance.available changes as expected
  • which accounts[].credit_type entries were used

Error Format

All API errors return an HTTP error status and a structured error object:
{
  "error": {
    "message": "freeze record not found",
    "type": "not_found",
    "code": "freeze_record_not_found"
  }
}
error.message
string
Human-readable description of the error.
error.type
string
High-level error category.
error.code
string
Stable machine-readable error code for programmatic handling.