Back to documentation

Add your first app balance

Create a balance boundary, issue a scoped key, and send your first points or credits transfer.

App boundary

The workspace owns people; the ledger owns accounts, balance labels, and transfer configuration.

Scoped service key

Create a key for one ledger with the read or write scope your backend needs.

Balanced movement

Debits and credits must balance for every asset in the request.

1

Create your app boundary

Sign in to the console, create a workspace if you do not have one, then create a ledger for your app. Assets, actions, scenes, accounts, and platform accounts all belong to that ledger. Add any custom vocabulary in ledger settings before sending it: transfers never create unknown codes automatically.

Open console
2

Create a scoped service key

Open API keys in the Console and create a key with both read and write for this walkthrough. The current ledger determines the key's accounting boundary. In production, grant only the scopes each backend service needs. Copy the full secret immediately; only its hash is retained after creation.

Store server-side
export XFERAPI_KEY='xfer_sk_<key_id>_<secret>'
3

Issue the first app balance

The API key already selects its ledger. Every new ledger includes a built-in -1 top-up platform account plus the points asset and top_up scene and action, so this first request works without seeding a user balance. Platform accounts always allow overdraft; ordinary source accounts require sufficient available balance unless their request item explicitly sets allow_overdraft: true. Persist the business transfer ID, scene, and complete payload before sending.

Issue 100 points
curl -X POST https://api.xferapi.com/v1/transfers \
  -H "Authorization: Bearer $XFERAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transfer_id": "grant_123",
    "scene": "top_up",
    "background_completion": true,
    "from_accounts": [{
      "account_id": "-1",
      "asset": "points",
      "action": "top_up",
      "amount": "100"
    }],
    "to_accounts": [{
      "account_id": "member_42",
      "asset": "points",
      "action": "top_up",
      "amount": "100"
    }]
  }'

Business outcomes return HTTP 200, while authentication, malformed HTTP, payload limits, or infrastructure failures may use a non-2xx status. In every valid envelope, confirm code: "ok"; other string codes are application errors and carry data: null. Amounts are decimal JSON strings, including in balance and record responses.

Retry the original request: after a timeout, disconnect, invalid or missing envelope, internal_error, service_unavailable, or transfer_state_conflict, resend the same ID, scene, and exact payload directly. Do not create a new ID or make a read request first. The same request can also be retried after rate_limit_exceeded, quota_exceeded, or billing_suspended once that condition clears.

Start a new attempt: insufficient_balance and execution-time amount_out_of_range are the terminal transaction failure codes for transfer creation. That ID-and-scene pair can never later succeed; fix the condition and use a new transfer_id.

transfer_already_reversed separately means the pair is already closed. idempotency_conflict means it belongs to another payload. Use a new ID for the request you are trying to submit.

4

Reverse deliberately

A reversal applies the inverse balance changes and moves the existing records to their reversal state. It requires write scope. Repeating the same reversal is safe.

Reversal request
curl -X POST https://api.xferapi.com/v1/reversals \
  -H "Authorization: Bearer $XFERAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "transfer_id": "grant_123", "scene": "top_up" }'
5

Inspect status or balance when needed

A successful Transfer does not require a follow-up read. Use these endpoints only when your product needs to display a balance, an operations or support workflow needs durable state, or you intentionally want to inspect background completion. An uncertain create response should still be handled by directly retrying the exact original Transfer.

succeeded

Every source and destination entry is durable.

completing

The source side is durable. Inspection continues the destination side without debiting the source again.

Optional transfer and balance reads
curl "https://api.xferapi.com/v1/transfers/grant_123?scene=top_up" \
  -H "Authorization: Bearer $XFERAPI_KEY"

curl "https://api.xferapi.com/v1/accounts/member_42" \
  -H "Authorization: Bearer $XFERAPI_KEY"

Continue from here

Read the API reference for time-aware balances, transfer status, retry behavior, and the complete request model.