APIs / Bank
Retail banking: customers, accounts, cards, eight thousand transactions, transfers and FX rates.
Accounts carry real running balances. POST a transfer between two accounts and the hub debits one, credits the other and writes both transactions — in the answer; the data itself never moves. FX rates tick over the live stream. 10,047 records in all.
In Sonda: Import → From a URL with the OpenAPI address and the whole API lands as a project, one request per operation with example bodies. No keys, no headers to add. More on each protocol.
_note and X-Sondahub-Write: simulated. A GET afterwards will not find what you wrote.curl https://api.sondahub.com/v1/bank
Every list answers { "data": [...], "meta": { "page", "limit", "total", "pages" } } with X-Total-Count and Link headers (next, prev, first, last). These options work on every collection and every nested route:
| Option | Meaning | Example |
|---|---|---|
page, limit | Paging, 1-based; limit 1–200, default 20. offset works too. | ?page=3&limit=50 |
sort | Comma list of fields, - for descending. Default id here. | ?sort=-risk_score,id |
field=value | Equals. Booleans as true/false, null for missing. | ?segment=retail |
_ne _gt _gte _lt _lte | Not equal and comparisons, on numbers, dates and strings. | ?risk_score_gte=10&risk_score_lt=100 |
_like | Contains, case-insensitive. | ?name_like=an |
_in | Any of a comma list. | ?id_in=1,2,3 |
_null | true: missing; false: present. | ?phone_null=true |
a.b=value | Inside a JSON field, dotted. | ?address.line1=… |
q | Search across the text fields. | ?q=alpine |
fields | Only these fields back. | ?fields=id,name |
expand | Embed related records. | ?expand=accounts,cards |
A name that is not a field answers 400 and lists the fields. Writes answer 422 with one line per problem, 404 for a missing id, 405 with an Allow header for a verb a route does not take.
Account holders. 400 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
namerequired | string | |
emailrequired | string | |
phone | string | |
date_of_birth | date | |
address | json { line1, line2?, city, region, postal_code, country } | |
segment | enum | retail premium business student |
kyc_status | enum | pending verified rejected |
risk_score | int | min 0, max 100 |
Relations: accounts → the accounts whose customer_id is this customer; cards → the cards whose customer_id is this customer. Use ?expand=accounts,cards to embed them, or the routes below.
curl "https://api.sondahub.com/v1/bank/customers?segment=premium&risk_score_gte=1&limit=3"
curl https://api.sondahub.com/v1/bank/customers/1
curl "https://api.sondahub.com/v1/bank/customers/1/accounts?limit=5"
curl -X POST https://api.sondahub.com/v1/bank/customers \
-H "Content-Type: application/json" \
-d '{"name":"A name","email":"A email","segment":"retail","kyc_status":"pending"}'
curl -X PATCH https://api.sondahub.com/v1/bank/customers/1 \
-H "Content-Type: application/json" \
-d '{"segment":"premium"}'
curl -X DELETE https://api.sondahub.com/v1/bank/customers/1
Checking, savings, credit and loan accounts. balance is the current balance after every transaction. 640 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
numberread-only | string | Unique account number. |
ibanread-only | string | |
customer_idrequired | int → customers | |
typerequired | enum | checking savings credit loan |
nickname | string | |
currency | string | |
balance | float | Current balance. Negative on credit and loan accounts means money owed. |
available | float | Balance minus holds, or credit left. |
credit_limit | float | Credit accounts only. |
interest_rate | float | Annual, percent. |
status | enum | active frozen closed |
opened_at | date |
Relations: customer → one customer through customer_id; transactions → the transactions whose account_id is this account; cards → the cards whose account_id is this account. Use ?expand=customer,transactions,cards to embed them, or the routes below.
curl "https://api.sondahub.com/v1/bank/accounts?type=savings&balance_gte=10&expand=customer&limit=3"
curl https://api.sondahub.com/v1/bank/accounts/1?expand=customer
curl "https://api.sondahub.com/v1/bank/accounts/1/transactions?limit=5"
curl -X POST https://api.sondahub.com/v1/bank/accounts \
-H "Content-Type: application/json" \
-d '{"customer_id":1,"type":"checking","nickname":"Everyday","currency":"USD","status":"active"}'
curl -X PATCH https://api.sondahub.com/v1/bank/accounts/1 \
-H "Content-Type: application/json" \
-d '{"type":"savings"}'
curl -X DELETE https://api.sondahub.com/v1/bank/accounts/1
Debit and credit cards on an account. Numbers are masked; the last four are real digits of the seed. 505 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
account_idrequired | int → accounts | |
customer_idrequired | int → customers | |
brand | enum | visa mastercard amex |
type | enum | debit credit virtual |
masked_numberread-only | string | |
last4read-only | string | |
holder_name | string | |
expires | string | |
status | enum | active blocked expired lost |
contactless | bool | |
daily_limit | float | min 0 |
Relations: account → one account through account_id; customer → one customer through customer_id. Use ?expand=account,customer to embed them, or the routes below.
curl "https://api.sondahub.com/v1/bank/cards?brand=mastercard&daily_limit_gte=10&expand=account&limit=3"
curl https://api.sondahub.com/v1/bank/cards/1?expand=account
curl -X POST https://api.sondahub.com/v1/bank/cards \
-H "Content-Type: application/json" \
-d '{"account_id":1,"customer_id":1,"brand":"visa","type":"debit","expires":"09/28","status":"active"}'
curl -X PATCH https://api.sondahub.com/v1/bank/cards/1 \
-H "Content-Type: application/json" \
-d '{"brand":"mastercard"}'
curl -X DELETE https://api.sondahub.com/v1/bank/cards/1
Every movement on an account. amount is signed: negative leaves the account. balance_after is the running balance. 7,674 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
account_idrequired | int → accounts | |
referenceread-only | string | Unique. |
type | enum | card transfer deposit withdrawal fee interest payment refund |
amountrequired | float | |
currency | string | |
balance_afterread-only | float | |
description | string | |
merchant | string | |
category | enum | groceries dining transport fuel shopping utilities entertainment health travel subscriptions transfer income fees other |
status | enum | pending posted reversed |
card_id | int → cards | |
counterparty | json { name, account_number? } | |
booked_atrequired | datetime | |
value_date | date |
Relations: account → one account through account_id; card → one card through card_id. Use ?expand=account,card to embed them, or the routes below.
curl "https://api.sondahub.com/v1/bank/transactions?type=transfer&amount_gte=10&expand=account&limit=3"
curl https://api.sondahub.com/v1/bank/transactions/1?expand=account
curl -X POST https://api.sondahub.com/v1/bank/transactions \
-H "Content-Type: application/json" \
-d '{"account_id":1,"type":"card","amount":9.99,"category":"groceries","status":"pending","booked_at":"2026-09-30T12:00:00Z"}'
curl -X PATCH https://api.sondahub.com/v1/bank/transactions/1 \
-H "Content-Type: application/json" \
-d '{"type":"transfer"}'
curl -X DELETE https://api.sondahub.com/v1/bank/transactions/1
Money moving between two accounts. POST {"from_account_id","to_account_id","amount","description"} and the hub checks the funds, debits, credits, and writes both transactions. Insufficient funds answers 422. 800 records — the file.
debit_transaction_id and credit_transaction_id. Anything wrong answers 422 with the reason.| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
from_account_idrequired | int → accounts | |
to_account_idrequired | int → accounts | |
amountrequired | float | min 0.01 |
currency | string | |
description | string | |
statusread-only | enum | completed pending failed reversed |
debit_transaction_idread-only | int → transactions | |
credit_transaction_idread-only | int → transactions | |
scheduled_for | date | |
executed_atread-only | datetime |
Relations: from_account → one account through from_account_id; to_account → one account through to_account_id. Use ?expand=from_account,to_account to embed them, or the routes below.
curl "https://api.sondahub.com/v1/bank/transfers?status=pending&amount_gte=10&expand=from_account&limit=3"
curl https://api.sondahub.com/v1/bank/transfers/1?expand=from_account
curl -X POST https://api.sondahub.com/v1/bank/transfers \
-H "Content-Type: application/json" \
-d '{"from_account_id":1,"to_account_id":3,"amount":100,"description":"Rent"}'
curl -X PATCH https://api.sondahub.com/v1/bank/transfers/1 \
-H "Content-Type: application/json" \
-d '{"amount":42.5}'
curl -X DELETE https://api.sondahub.com/v1/bank/transfers/1
Exchange rates against USD and the main crosses. The live stream ticks them. 28 records — the file.
| Field | Type | Notes |
|---|---|---|
idread-only | int | Assigned by the server. Seed records keep their ids across restarts; records you create continue after the seed. |
created_atread-only | datetime | When the record was created (ISO 8601, UTC). |
updated_atread-only | datetime | When the record last changed. |
pairrequired | string | |
baserequired | string | |
quoterequired | string | |
raterequired | float | |
bid | float | |
ask | float | |
change_24h_pct | float | |
as_of | datetime |
curl "https://api.sondahub.com/v1/bank/fx_rates?rate_gte=10&limit=3"
curl https://api.sondahub.com/v1/bank/fx_rates/1
curl -X POST https://api.sondahub.com/v1/bank/fx_rates \
-H "Content-Type: application/json" \
-d '{"pair":"USD/ARS","base":"USD","quote":"ARS","rate":9.99}'
curl -X PATCH https://api.sondahub.com/v1/bank/fx_rates/1 \
-H "Content-Type: application/json" \
-d '{"rate":42.5}'
curl -X DELETE https://api.sondahub.com/v1/bank/fx_rates/1
The same stream two ways: the world's own activity, one tick a second, generated for your connection alone. Both push JSON text messages; SSE names each one with event: and numbers it with id:. ?topics=a,b narrows either.
| Topic | What arrives | How often |
|---|---|---|
fx | A rate ticking: pair, rate, bid, ask. | 1 s |
transactions | A card payment posting on one of the seed accounts. | 3 s |
wss://api.sondahub.com/v1/bank/ws?topics=fx
> {"type":"hello","api":"bank","topics":[…],"subscribed":[…]}
> {"type":"event","topic":"fx","api":"bank","ts":"…","data":{…}}
< {"type":"subscribe","topics":["fx"]} # narrow to some topics
< {"type":"ping"} # → {"type":"pong"}
< anything else # → echoed back as {"type":"echo"}
curl -N "https://api.sondahub.com/v1/bank/events?topics=fx"
retry: 3000
id: 1
event: fx
data: {"type":"event","topic":"fx",…}
One endpoint, https://api.sondahub.com/v1/bank/graphql: POST {"query", "variables"} or GET ?query=. Introspection is on, so Sonda's GraphQL mode loads the schema; the SDL is a click away. Every collection is a paged query with the same filter, sort and q options as REST (operators as suffixes: price_lt), a by-id query, relation fields both ways, and create, update, replace and delete mutations — simulated like every write, with the note in extensions.
curl https://api.sondahub.com/v1/bank/graphql -H "Content-Type: application/json" -d '{"query": "{ accounts(limit: 3, sort: \"-id\", filter: { type: checking }) { total data { id number iban customer_id customer { name } transactions(limit: 2) { id } } } }"}'
{
accounts(limit: 3, sort: "-id", filter: { type: checking }) {
total
data {
id number iban customer_id
customer { name }
transactions(limit: 2) { id }
}
}
}