Wallets API
HTTP API reference for the F2.M4 Programmable Wallets — create wallets, post ledger entries, bind funding sources, execute mandate-gated payments, triage reconciliation anomalies.
Wallets API
REST API for the Wallets concept. Per-agent fund pools with mandate-gated debits, multi-rail funding, and automatic reconciliation.
Base URL: https://api.codespar.dev
All endpoints require authentication. See Authentication. Endpoints noted as admin require either a bearer api key OR service auth with an x-codespar-user header for an org admin/owner.
Wallet object
| Field | Type | Description |
|---|---|---|
id | string | Wallet ID, wlt_<16chars> |
org_id | string | Owning organization |
project_id | string | Owning project — wallets are project-scoped |
agent_id | string | null | Optional agent binding; null for org-level wallets |
display_name | string | Free-form, max 120 chars |
status | "active" | "frozen" | "closed" | Frozen wallets reject new ops; closed is terminal |
created_at | string | ISO 8601 |
closed_at | string | null | Set when status flips to closed |
metadata | object | Free-form operator-supplied JSON |
When fetched via GET /v1/wallets/:id, the response also embeds a balances array (one entry per currency).
Currency whitelist
BRL, USD, MXN, COP, ARS, USDC, BRLA. Fiat-only currencies route through the gateway (Stripe / Mercado Pago / Asaas / Pix). Stablecoin currencies (USDC, BRLA) currently support funding only; the offramp execute path lands in M4.4.
Wallets
Create a wallet
POST /v1/wallets
Body
{
"display_name": "Customer Service Agent",
"currency": "BRL",
"agent_id": "agt_optional",
"metadata": {}
}Seeds a zero-balance row in the requested currency. Other currencies get rows lazily as funding events for those currencies post.
Response — 201 Created with the wallet object.
List wallets
GET /v1/wallets
Project-scoped. Optional query params:
status=active|frozen|closedagent_id=<id>limit=1..100(default 25)
Response
{ "wallets": [/* Wallet */] }Get a wallet (with balances)
GET /v1/wallets/:id
Response — Wallet object with embedded balances:
{
"id": "wlt_…",
"balances": [
{
"wallet_id": "wlt_…",
"currency": "BRL",
"balance_minor": "10000",
"available_minor": "8500",
"updated_at": "2026-04-26T12:00:00Z"
}
]
}balance_minor and available_minor are bigint strings (centavos / cents). available <= balance is enforced at the DB layer.
Ledger
Post a ledger entry
POST /v1/wallets/:id/ledger — admin
Direct ledger writes. The gateway's processPayment is the primary caller for hold/release/debit; webhook adapters use this for fund. Hand-rolled writes are typically operator-driven corrections.
Body
{
"currency": "BRL",
"amount_minor": "5000",
"kind": "fund",
"mandate_id": null,
"attempt_id": "demo-fund-001",
"external_ref": "E12345…",
"metadata": { "description": "Pix incoming" }
}kind enum: fund | hold | release | debit | reconcile | reverse | fee. Sign refinements:
| Kind | Sign | Mandate required |
|---|---|---|
fund | positive | no |
release | positive | no |
hold | negative | yes |
debit | negative | yes |
fee | negative | no |
reconcile | zero | no |
reverse | mirrors original | no |
Idempotency — (wallet_id, attempt_id, kind) and (wallet_id, kind, external_ref) are partial unique indexes. A retried call with the same attempt_id returns the prior row with HTTP 200, no double-post; a fresh insert returns 201.
Errors — 400 invalid_body, 409 wallet_not_active, 409 balance_constraint_violation (CHECK trip).
List ledger entries
GET /v1/wallets/:id/ledger
Query params:
limit=1..200(default 50)before_id=<bigint>— cursor pagination, descending by idkind=<one of the kinds above>
Response
{
"entries": [/* LedgerEntry */],
"next_before": "12345"
}Funding sources
A funding source binds a connection to a wallet for a specific currency. The funding bridge converts that connection's commerce.payment.* events into kind=fund ledger entries.
Constraint — at most one binding per (connection_id, currency) while enabled=true. Prevents the recon engine from double-applying the same receipt.
Bind a funding source
POST /v1/wallets/:id/funding-sources — admin
{
"connection_id": "ca_…",
"currency": "BRL",
"metadata": {}
}Errors — 404 connection_not_found (not in caller's project), 409 connection_not_active, 409 funding_source_conflict.
List funding sources
GET /v1/wallets/:id/funding-sources
{ "funding_sources": [
{
"wallet_id": "wlt_…",
"connection_id": "ca_…",
"currency": "BRL",
"enabled": true,
"created_at": "…",
"metadata": {}
}
] }Unbind a funding source
DELETE /v1/wallets/:id/funding-sources/:connection_id/:currency — admin
Returns 204 No Content on success, 404 funding_source_not_found otherwise.
Execute
Run a payment through the gateway
POST /v1/wallets/:id/execute — admin
Drives the full F2.M4 lifecycle: policy → mandate → wallet hold → route → execute → wallet settle → budget record.
Body
{
"amount": 19.99,
"currency": "BRL",
"recipient": "Acme Supplier Co.",
"description": "Pix supplier payout",
"mandate_id": "mnd_abc123",
"preferred_method": "pix",
"attempt_id": "exec-2026-04-26-001",
"metadata": {}
}amount is in major units (e.g. 19.99 = R$ 19,99). The gateway converts internally.
Status mapping
| Gateway status | HTTP | When |
|---|---|---|
completed | 200 | All gates passed; provider returned settled |
requires-approval | 402 | InsufficientFundsError — top up the wallet to retry |
denied | 403 | Policy or mandate gate failed |
failed | 422 | Route or execute gate failed |
Response body — Full GatewayPaymentResult always (even on non-200):
{
"requestId": "gw-…",
"status": "completed",
"policy": { "allowed": true, "reason": "" },
"mandate": { "valid": true, "mandateId": "mnd_…" },
"route": { "method": "pix", "provider": "asaas" },
"payment": { "transactionId": "tx_…", "amountSent": 19.99 },
"wallet": {
"holdId": "100",
"debitId": "102",
"releaseId": "101",
"insufficientFunds": false
},
"audit": [
{ "timestamp": "…", "step": "policy_check", "status": "pass", "detail": "…" }
]
}Stablecoin currencies (USDC, BRLA) return 400 currency_not_routable — the fiat gateway has no on-chain rails today.
Reconciliation anomalies
The recon engine flags two failure modes:
debit_without_receipt— a debit older than the grace window has no matching provider eventreceipt_without_debit— acommerce.payment.*event with no matching wallet ledger row
List anomalies
GET /v1/wallets/:id/recon-anomalies
Optional query: status=open|resolved|dismissed (default open).
{ "anomalies": [
{
"id": "1",
"wallet_id": "wlt_…",
"kind": "debit_without_receipt",
"ledger_entry_id": "100",
"external_ref": "tx_…",
"amount_minor": "-1000",
"currency": "BRL",
"detected_at": "…",
"status": "open",
"resolved_at": null,
"resolution_note": null,
"metadata": {}
}
] }Resolve / dismiss
POST /v1/wallets/:id/recon-anomalies/:aid — admin
{
"status": "resolved",
"note": "Reconciled manually against bank statement"
}Or {"status": "dismissed", "note": "False positive — webhook arrived 90s late"}.
Errors — 404 anomaly_not_found (already resolved or wrong wallet).
Idempotency at a glance
| Path | Idempotency key | Behavior on retry |
|---|---|---|
| Ledger POST | (wallet_id, attempt_id, kind) OR (wallet_id, kind, external_ref) | HTTP 200 + prior row |
| Funding-source POST | (connection_id, currency) partial unique | HTTP 409 funding_source_conflict |
| Execute POST | walletAttemptId (defaults to requestId) | Forwarded to wallet ops; same prior-row semantics |
| Recon-anomaly resolve | WHERE status='open' predicate | HTTP 404 if already resolved |
Errors
All non-2xx responses follow the shared error envelope:
{
"error": {
"code": "balance_constraint_violation",
"message": "ledger entry would violate a wallet balance invariant",
"details": { "wallet_id": "…", "currency": "BRL", "kind": "hold" }
},
"request_id": "req_…"
}Specific codes used by this surface:
| Code | HTTP | Meaning |
|---|---|---|
wallet_not_active | 409 | Wallet is frozen or closed |
balance_constraint_violation | 409 | DB CHECK trip — overdraw or negative balance |
ledger_conflict | 409 | Different unique index than the partials (rare) |
funding_source_conflict | 409 | Connection already bound for this currency |
funding_source_not_found | 404 | Binding doesn't exist |
connection_not_found | 404 | Connection not in caller's project |
connection_not_active | 409 | Connection status ≠ connected |
currency_not_routable | 400 | Stablecoin execute (M4.4) |
anomaly_not_found | 404 | Already resolved/dismissed or wrong wallet |
not_found | 404 | Wallet doesn't exist or cross-tenant |
Next
- Build an agent with a wallet — Asaas sandbox walkthrough
- Wallets concept — design + invariants
- Errors — full error envelope reference
Last updated on
Projects API
HTTP API reference for creating, listing, updating, and deleting projects -- the second level of CodeSpar's org -> project tenancy model.
@codespar/api-types
Zod schemas + TypeScript types for every /v1/* REST shape on api.codespar.dev. Used internally by the SDK and dashboard; published for callers building HTTP clients directly.