Skip to main content
Use POST /v1/billing/deduct to deduct credits from a user’s available balance in a single request. This is the right choice for fixed-price image generation, fixed-cost API calls, and other one-time operations where cost is known upfront. If cost is unknown in advance, or you need to reserve credits before settlement, use staged deduction instead.

Minimal Example

curl -X POST https://api.velobase.io/v1/billing/deduct \
  -H "Authorization: Bearer your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "user_987",
    "transaction_id": "img_gen_001",
    "amount": 5
  }'

Parameters

body.customer_id
string
required
The unique identifier for this customer. The customer must already exist and have enough available credits.
body.transaction_id
string
required
Unique ID for this charge transaction. Also serves as the idempotency key — repeating a request with the same transaction_id returns the original result without double-charging.
body.amount
number
required
Credits to deduct. Must be greater than 0.
body.credit_types
string[]
Restrict deduction to these wallet categories only. If omitted, the system may draw from any active credits. If provided and the selected categories have insufficient credits, the request fails.
body.business_type
string
Transaction category for reporting. Examples: TASK, ORDER, TOKEN_USAGE.
body.description
string
A human-readable note for this transaction.

Response

{
  "transaction_id": "img_gen_001",
  "deducted_amount": 5,
  "deduct_details": [
    {
      "account_id": "...",
      "credit_type": "default",
      "amount": 5
    }
  ],
  "deducted_at": "2026-04-07T12:00:00.000Z",
  "is_idempotent_replay": false
}
transaction_id
string
The unique transaction ID passed in the request.
deducted_amount
number
Total credits actually deducted.
deduct_details
array
Deduction breakdown. Credits may be consumed from multiple wallet categories when the amount spans more than one account.
deducted_at
string
ISO 8601 timestamp when the deduction completed.
is_idempotent_replay
boolean
true when the response was returned from a previous request with the same transaction_id.

Deduction Rules and Wallet Categories

  • The system automatically deducts from all available credits.
  • If credit_types is provided, deduction is limited to those wallet categories only.
  • Callers do not choose which wallet category to consume from.
  • Credits that expire sooner are consumed first; if expiry is the same, older accounts are consumed first.
  • Only credits that are currently active are eligible for deduction. Credits that have not started yet or have already expired are ignored.
  • Use the customer balance API to inspect remaining credits by credit_type.

Insufficient Balance

Returns 400 if the customer doesn’t have enough available credits:
{
  "error": {
    "message": "insufficient balance",
    "type": "bad_request"
  }
}
If credit_types is provided, the error message becomes insufficient balance in selected credit_types when the selected wallet categories do not have enough credits. Failed requests do not produce a partial deduction and do not leave behind a half-completed state.

Idempotency

Same transaction_id -> same result, is_idempotent_replay: true, no double charge.

Verify the Result

curl https://api.velobase.io/v1/customers/user_987 \
  -H "Authorization: Bearer your_api_key_here"
After deduction, check:
  • whether balance.available decreased
  • which accounts[].credit_type entries were consumed
  • whether each accounts[].available value changed as expected

Error Format

All API errors return an HTTP error status and a structured error object:
{
  "error": {
    "message": "insufficient balance in selected credit_types",
    "type": "bad_request",
    "code": "insufficient_balance_in_selected_credit_types"
  }
}
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.