Transfers
Move money between accounts — single, split, or batch.
All transfers go through a single endpoint:
POST /v1/transfersThe request body determines which mode is used.
Single Transfer
Move funds from one account to another.
{
"ledger_id": "...",
"debit_account_id": "...",
"credit_account_id": "...",
"amount": 10000,
"code": 1
}Returns 201 Created with the transfer object.
Using Labels Instead of IDs
You can reference ledgers and accounts by their human-readable label instead of UUIDs. Labels are case-insensitive and mutually exclusive with their _id counterparts.
{
"ledger_label": "main-ledger",
"debit_account_label": "revenue",
"credit_account_label": "expenses",
"amount": 10000,
"code": 1
}You cannot send both ledger_id and ledger_label in the same request — pick one. Same for account fields.
Two-Phase (Pending)
Add pending: true to create a hold. Optionally set timeout (seconds) or settlement_date.
{
"ledger_id": "...",
"debit_account_id": "...",
"credit_account_id": "...",
"amount": 10000,
"code": 1,
"pending": true,
"timeout": 3600
}Then confirm or void:
POST /v1/transfers/{id}/confirm
POST /v1/transfers/{id}/voidSee Settlement for details on settlement cycles and scheduled dates.
Split Transfer
Distribute funds from one source to multiple destinations in a single request.
{
"ledger_id": "...",
"debit_account_id": "...",
"amount": 10000,
"code": 1,
"split": [
{ "account_id": "acct-merchant", "amount": 9500 },
{ "account_id": "acct-platform-fee", "amount": 500 }
]
}Split amounts must sum exactly to the transfer amount.
How It Works
Under the hood, the API constructs linked transfer pairs through a control account. All legs succeed or all fail atomically.
Property Inheritance
When pending, timeout, settlement_date, or code is set on a split transfer, every leg inherits those properties automatically.
{
"ledger_id": "...",
"debit_account_id": "...",
"amount": 10000,
"code": 1,
"settlement_date": "2025-03-01",
"split": [
{ "account_id": "acct-merchant", "amount": 9500 },
{ "account_id": "acct-platform-fee", "amount": 500 }
]
}Each leg becomes a pending transfer settled on March 1st. You can confirm or void each leg independently.
Batch Transfer
Send multiple transfers in a single request. Processed asynchronously.
{
"transfers": [
{
"ledger_id": "...",
"debit_account_id": "...",
"credit_account_id": "...",
"amount": 50000,
"code": 1,
"timestamp": "2024-01-15T10:00:00-03:00",
"metadata": { "source": "legacy", "original_id": "TXN-001" }
},
{
"ledger_id": "...",
"debit_account_id": "...",
"credit_account_id": "...",
"amount": 15000,
"code": 2,
"timestamp": "2024-01-15T10:05:00-03:00"
}
]
}Returns 202 Accepted with a workflow_id for tracking.
Labels in Batch Mode
Batch entries also support ledger_label, debit_account_label, and credit_account_label:
{
"transfers": [
{
"ledger_label": "main-ledger",
"debit_account_label": "funding",
"credit_account_label": "revenue",
"amount": 50000,
"code": 1,
"timestamp": "2024-01-15T10:00:00-03:00"
}
]
}Rules
- Timestamps must be RFC 3339 with timezone offset.
- Maximum 1,000 transfers per request.
- Each transfer is written atomically to both the ledger engine and the database.
- The
timestampis stored ascreated_atfor ordering — the ledger engine assigns its own internal timestamps. This is the same behavior astimestampon single transfers.
Batch transfers cannot use pending, split, or settlement_date. Use separate single or split transfers for those features.
Auto-Create Mode
When importing data from external systems, you may not want to pre-create every ledger and account. Use the top-level auto-create flags to let the API provision them on the fly:
| Flag | Effect |
|---|---|
allow_ledger_create | If a ledger_label doesn't exist, auto-creates the ledger (with an associated @world account). |
allow_accounts_create | If an account label doesn't exist, auto-creates the account in the resolved ledger. |
fund_from_world | Enables overdraft on auto-created debit accounts so @world can fund them. |
{
"allow_ledger_create": true,
"allow_accounts_create": true,
"fund_from_world": true,
"transfers": [
{
"ledger_label": "usd-treasury",
"debit_account_label": "funding",
"credit_account_label": "revenue",
"amount": 50000,
"code": 1,
"timestamp": "2024-01-15T10:00:00-03:00"
}
]
}The API dispatches provisioning workflows for any missing resources. The transfer workflow waits for all provisioning to complete (via signals) before executing the transfers. Returns 202 Accepted with a workflow_id.
Auto-create flags are top-level and apply to all entries in the request. They work with single, split, and batch modes.
Idempotency
Include an idempotency_key (string) in your JSON payload to prevent duplicate transfers on retries. The key is scoped per workspace and resource type, and never expires.
{
"ledger_label": "main",
"debit_account_label": "revenue",
"credit_account_label": "expenses",
"amount": 10000,
"code": 1,
"idempotency_key": "user-signup-bonus-12345"
}Reference
Every transfer can include a reference field (must be a valid UUID) for external correlation, and a metadata object for arbitrary key-value pairs.