Wallets API
Manage the wallets under your account and move funds between them. All endpoints require API-key auth and are scoped to the authenticated account.
The Wallet object
{
"id": "wlt_3xQ8d2Kpath7",
"number": "47514771",
"name": "Operations",
"currency": "KES",
"is_default": false,
"status": "active",
"available": 12500.00,
"pending": 0.00,
"created_at": "2026-06-05T10:00:00Z"
}| Field | Type | Notes |
|---|---|---|
id | string | Wallet id, prefixed wlt_. Accepted as wallet_id anywhere |
number | string | 8-digit account number. A human-friendly alias — accepted as wallet_id anywhere the id is, and the two never collide |
name | string | Human label, unique per account |
currency | string | ISO-4217. Fixed at creation |
is_default | bool | The wallet used when a request omits wallet_id |
status | enum | active / closed |
available | number | Spendable balance, decimal major units |
pending | number | Reserved (in-flight payouts), decimal major units |
created_at | timestamp |
POST /v1/wallets
Create a wallet.
Request
{
"name": "Operations",
"currency": "KES"
}| Field | Type | Required | Notes |
|---|---|---|---|
name | string | ✓ | 1–64 chars, unique per account |
currency | string | ✗ | ISO-4217; defaults to the account's default_currency |
curl -X POST https://api.teronapay.com/v1/wallets \
-u "$KEY:$SECRET" \
-H "Content-Type: application/json" \
-d '{ "name": "Operations", "currency": "KES" }'Response 201: a Wallet object (available and pending start at 0).
Errors
400 invalid_request— missing/oversized name, unsupported currency409 conflict— a wallet with that name already exists on the account422 precondition_failed— per-account wallet limit reached
GET /v1/wallets
List the account's wallets. Returns the default wallet first, then by
created_at.
{
"data": [ /* Wallet objects, balances included */ ]
}Query parameters: currency (filter), status (active / closed).
GET /v1/wallets/{id}
Retrieve one wallet, including live available / pending balances.
Errors
404 not_found— unknown id or not on this account
GET /v1/wallets/{id}/balance
Balance-only shortcut (cheaper than the full object when you poll). The {id}
path segment accepts the wlt_… id or the 8-digit number.
{ "wallet_id": "wlt_3xQ8d2Kpath7", "number": "47514771", "currency": "KES", "available": 12500.00, "pending": 0.00 }PATCH /v1/wallets/{id}
Update mutable fields. Currency is immutable.
Request
{ "name": "Operations (KE)", "is_default": true }| Field | Type | Notes |
|---|---|---|
name | string | Rename |
is_default | bool | Promote to default. The previous default is demoted. Must match the account's default_currency |
DELETE /v1/wallets/{id}
Close a wallet. Response 204.
Errors
422 precondition_failed— wallet is the default, or holds a non-zeroavailable/pendingbalance. Move funds out first (transfer or payout).
Transfers
Move funds between two wallets of the same account. Ledger-only and synchronous — no external rails, no fee, no callback. A transfer either succeeds atomically or fails inline.
The Transfer object
{
"id": "trf_7Yk2mZpQ9aLr",
"from_wallet_id": "wlt_DEFAULT00001",
"to_wallet_id": "wlt_3xQ8d2Kpath7",
"amount": 500.00,
"currency": "KES",
"status": "succeeded",
"reference": "float-topup-may",
"created_at": "2026-06-05T10:05:00Z"
}| Field | Type | Notes |
|---|---|---|
id | string | Transfer id, prefixed trf_ |
from_wallet_id | string | Debited wallet |
to_wallet_id | string | Credited wallet |
amount | number | Decimal major units, positive |
currency | string | Must match both wallets |
status | enum | Always succeeded (transfers resolve synchronously) |
reference | string | Optional merchant note |
POST /v1/transfers
Request
{
"from_wallet_id": "wlt_DEFAULT00001",
"to_wallet_id": "wlt_3xQ8d2Kpath7",
"amount": 500.00,
"currency": "KES",
"reference": "float-topup-may"
}| Field | Type | Required | Notes |
|---|---|---|---|
from_wallet_id | string | ✓ | Source wallet — wlt_… id or 8-digit number |
to_wallet_id | string | ✓ | Destination wallet (must differ from source) — wlt_… id or 8-digit number |
amount | number | ✓ | Decimal major units, positive |
currency | string | ✗ | Defaults to the wallets' currency; rejected if it differs |
reference | string | ✗ | Free-text note |
Idempotency: send Idempotency-Key: <key> to dedupe retries.
Response 201: a Transfer object with status: "succeeded".
Errors
400 invalid_request— same source and destination, non-positive amount404 not_found— a wallet id doesn't exist on this account422 precondition_failed— insufficientavailablein the source wallet, or the two wallets have different currencies
GET /v1/transfers
List transfers. Query parameters: wallet_id (either side), since,
before, limit. Same paging shape as GET /v1/payments.
GET /v1/transfers/{id}
Retrieve one transfer.
Status codes summary
| Code | Meaning |
|---|---|
200 | OK — GET succeeded |
201 | Created — wallet or transfer created |
204 | No content — wallet closed |
400 | Bad request |
404 | Wallet/transfer not found |
409 | Duplicate wallet name |
422 | Precondition failed (limit reached, non-empty wallet, insufficient balance, currency mismatch) |
429 | Rate limit exceeded |
