Back to documentation

Core concepts

A clear model for app-owned balances

XferAPI gives your product a durable balance API without taking ownership of your business rules. Learn which boundary owns what before you send the first transfer.

Workspace

The collaboration and commercial boundary. A workspace contains members, roles, ledger access, billing, traffic limits, transfer shape, and plan-based capacity for new ledgers and ledger configuration.

Ledger

The isolated accounting boundary for one product, environment, or balance domain. Accounts, vocabulary, platform accounts, transfers, records, and service keys belong to one ledger and do not cross it.

Ledger-scoped API key

A backend credential that selects exactly one ledger. The public /v1 API derives the ledger from the key, so integrations do not put an internal ledger ID in a URL or request body.

Plan capacity controls creation, not history

Additional means above the built-ins

The exact points asset and built-in -1 and -2 platform account definitions do not consume additional allowances. If a ledger does not contain points, it does not receive a hidden free asset slot. System account 0 is not a platform account.

Downgrades preserve existing resources

Existing workspaces, ledgers, assets, platform account definitions, and full ranges continue to work and remain editable after a downgrade. The new plan blocks only another creation while its relevant limit is reached; it does not rewrite balances, history, or transfer behavior.

Every ledger counts toward the workspace limit, including a disabled ledger. A platform account range contains 1–20 IDs and counts as one definition regardless of its length or status. A user can create at most two Free workspaces they own; workspaces joined as a member do not count, and existing downgraded workspaces are preserved.

The vocabulary

Six terms explain most integrations

Account

An application-owned balance owner identified inside a ledger. The application chooses the account ID format within XferAPI’s URL-safe ASCII rules.

Asset

The unit being moved, such as points, credits, coins, or gems. A ledger configures the asset codes it accepts.

Action

The business meaning attached to an account entry, such as top_up, spend, refund, or grant.

Scene

The business context of the whole transfer. Together with transfer_id, it forms the idempotency boundary.

Transfer

One caller-identified N-to-M movement. For every asset, source totals must equal destination totals.

Record

The durable account-level movement produced by one transfer item. Transfer history is the source of truth for accounting state.

The accounting rule

One transfer, balanced per asset

A transfer may debit several accounts and credit several accounts. For each asset, the total on the source side must equal the total on the destination side.

A 100-point split
{
  "transfer_id": "order_123",
  "scene": "spend",
  "from_accounts": [
    { "account_id": "member_42", "asset": "points", "action": "spend", "amount": "100" }
  ],
  "to_accounts": [
    { "account_id": "creator_7", "asset": "points", "action": "spend", "amount": "90" },
    { "account_id": "-2", "asset": "points", "action": "spend", "amount": "10" }
  ]
}

Idempotency decisions

Retry the same operation, or start a new attempt

The response category determines whether the original transfer_id and scene remain usable. Do not treat every non-ok code the same.

Uncertain result: retry directly

For a timeout, disconnect, invalid or missing envelope, internal_error, service_unavailable, or transfer_state_conflict, resend the original ID, scene, and exact payload. Do not create a new ID or require a read first.

Admission blocked: retry after it clears

rate_limit_exceeded, quota_exceeded, and billing_suspended do not start a new transfer. After backoff or remediation, resend the same original request.

Terminal transaction failure: use a new ID

For transfer creation, this category contains exactly insufficient_balance and execution-time amount_out_of_range. The pair can never later succeed. Fix the condition, then submit a new transfer_id.

Pair already owned or closed

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

Time-aware value

Ordinary destination entries can start in the future or expire at an exclusive Unix-second boundary. Balance reads and deductions evaluate availability at one server time captured for the Transfer. For each ordinary source account and asset, active batches with an expiration are consumed earliest-expiry-first, followed by permanent balance. Future and expired batches are not spendable; a source entry cannot select a specific batch, and equal-expiry ordering is not contractual. Expiration recycling is managed by XferAPI and is not a user-created transfer.

Whole-transfer reversal

A reversal applies the inverse balance changes to the complete transfer and moves its records to the reversal state. It is safe to repeat and does not create a second business transfer.

Keep the boundary explicit

Your application decides

  • When a user earns or spends value
  • What an asset, action, and scene mean
  • Which business operation owns each transfer ID
  • Whether an ordinary debit may use allow_overdraft

XferAPI provides

  • The isolated ledger and scoped service-key boundary
  • Balanced N-to-M movement and durable records
  • Idempotent direct retries for uncertain responses
  • Time-aware balances and whole-transfer reversal

Ready to implement? Start with a working request, then use the API reference for every field and status.