Accounts
Penny models money with three kinds of account. Capital Accounts hold real balances. Control Accounts hold no balance of their own; they scope how much can be spent against a Capital Account, and how fast. Linked Accounts are external bank accounts you register as sources of incoming funds. All three share the account_ ID prefix, and every account response carries a type field (capital, control, or linked) that tells you which kind it is.
Capital Accounts
A Capital Account holds an actual settled balance: this is your real money. It reports two balance fields: available_balance (what’s free to move right now) and total_balance (including funds held against pending activity). Capital Accounts can be:
- Nested: every Capital Account you create sits under a
parent_account_id, so one funding source can be split across departments or products. Your business’s top-level Capital Account hasroot: true. - Auto-topped-up: configure
auto_top_upwith asource_account_id(another Capital Account), athreshold_amount, and atop_up_amount, and Penny movestop_up_amountfrom the source whenever the balance drops below the threshold. - Alerted: set
low_balance_notification.threshold_amount(viaPATCH /accounts/capital/{account_id}/low_balance_alert) and receive anaccount.low_balance_alertwebhook when the balance drops below it.
Keys whose value is null are omitted from responses. This account has no spend_control_id, so the key is absent.
Control Accounts
A Control Account holds no balance. It is tied to one Capital Account (capital_account_id), and spend through it is drawn from that Capital Account’s balance. It exists to answer one question: how much is allowed to move through here, and how fast? Control Accounts can nest under a parent_account_id, and each one can carry its own Spend Control via spend_control_id.
This is the layer you attach to cards, cardholders, or business units when you want a shared limit across many of them. A card and its cardholder can point at the same Control Account and share one budget, backed by one Capital Account’s balance.
When you create a Control Account (POST /accounts/control/), the request names the Capital Account as source_capital_account_id and an optional parent as parent_control_account_id. The response reports them as capital_account_id and parent_account_id:
This Control Account sits directly under its Capital Account, so parent_account_id is null and omitted.
Reading balances and history
Every Capital and Control Account exposes its own ledger and transaction history:
See Transactions for what a transaction represents, and Reading Transactions & Ledger Entries for a worked example.
Moving money between accounts
POST /accounts/capital/transfer moves funds from one Capital Account to another.
Send an Idempotency-Key header with every transfer. It is optional, but strongly advised: if a request times out, retrying with the same key returns the original result instead of moving the money twice. See Retries & Idempotency.
The 201 Created response contains both sides of the transfer: source_transaction (the outbound transaction on from_account_id) and destination_transaction (the inbound transaction on to_account_id). Both are transfer-type Transactions with an internal channel and a transfer counterparty naming the other account. Abridged response:
Account status and lifecycle
Every Capital and Control Account carries a status:
The same three operations exist under /accounts/control/{account_id} for Control Accounts. Linked Accounts have their own statuses; see Linking an Account.
Activating and deactivating an account both apply immediately and return the updated account. Deleting an account is asynchronous: the request returns 202 Accepted with the account (item) and the Task that completes the deletion (task). Deleting an account whose deletion has already started is rejected. Abridged response:
A task’s status is one of received, scheduled, pending, pending_subtasks, running, completed, failed, or cancelled. The task also carries start_time once it starts running and finished_time once it finishes. To check on the deletion, poll GET /tasks/{task_id}, or re-fetch the account with GET /accounts/capital/{account_id} (or GET /accounts/control/{account_id}) and check its status.