Skip to content

Accounts

An account is where a customer’s money sits or moves through: a mobile money wallet, a bank account, a card, a merchant or agent account, a loan. Every account belongs to one customer. Transactions are tied to accounts, so many rules (velocity, pass-through, dormancy) work per account.

POST /api/v1/ingest/accounts
Authorization: Bearer spk_…

The body is {"items": [ … ]} with up to 500 accounts.

Field Type Required What it is Example
external_id string, 1–128 Yes Your reference for the account. Never changes, never reused "WAL-0244123456"
customer_external_id string, 1–128 Yes The external_id of the customer who owns it "CUST-77120"
type see below Yes What kind of account "wallet"
currency 3-letter currency code Yes The account’s currency "GHS"
version integer ≥ 0 No, but strongly advised Increases whenever the record changes. Works exactly as for customers: higher is applied, equal or lower is stale, none is always applied 1760000000000
provider string, ≤ 64 No Who runs the account. Stored in lower case "mtn_momo", "telecel_cash", "at_money", a bank code
identifier string, ≤ 64 No The account number, wallet number (MSISDN) or card token. Encrypted "0244123456"
identifier_type msisdn · account_number · card_token · iban · other No (see defaults) What kind of number identifier is. Decides how it is normalised "msisdn"
status active · dormant · frozen · closed No (default active) "active"
opened_at date-time No When it was opened. Rules for new accounts need it "2023-06-12T10:05:00Z"
closed_at date-time No When it was closed
attributes object, ≤ 64 keys No Your own extra fields {"tier": "gold"}

type is one of bank, wallet, card, merchant, agent, loan, virtual, other.

Terminal window
curl https://speculapro.example.com/api/v1/ingest/accounts \
-H "Authorization: Bearer $SPECULAPRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"external_id": "WAL-0244123456",
"customer_external_id": "CUST-77120",
"version": 1760000000000,
"type": "wallet",
"provider": "mtn_momo",
"identifier": "0244123456",
"currency": "GHS",
"opened_at": "2023-06-12T10:05:00Z"
},
{
"external_id": "AGT-0417",
"customer_external_id": "CUST-90311",
"version": 1760000000000,
"type": "agent",
"provider": "mtn_momo",
"currency": "GHS",
"attributes": {"region": "Ashanti", "agent_tier": "B"}
}
]
}'
{
"accepted": 2, "duplicate": 0, "stale": 0, "rejected": 0, "retry": 0,
"results": [
{"index": 0, "external_id": "WAL-0244123456", "status": "accepted", "id": "0199c8f1-2c40-7b8d-9e13-3f5a6b7c8d90"},
{"index": 1, "external_id": "AGT-0417", "status": "accepted", "id": "0199c8f1-2c40-7b8d-9e13-40ab5c6d7e81"}
]
}

Send it again with the same external_id. Versions work exactly as for customers: a higher version is applied, an equal or lower one returns stale and changes nothing, and a record with no version is always applied without raising the stored version. See Versions.

If customer_external_id names a customer SpeculaPro hasn’t received, the account is still accepted. SpeculaPro creates a placeholder customer (a “stub”) which the real customer record replaces when it arrives. See Placeholder customers.

The same works the other way: if a transaction names an account SpeculaPro hasn’t received, a placeholder account is created with the transaction’s currency and account type (other if none was given). Your real account record replaces it.

identifier is the number that identifies the account outside your institution: the wallet’s phone number, the bank account number, the card token.

  • It is normalised (see below), then encrypted with your workspace’s key. It is never stored in readable form.
  • A masked copy of the normalised number is kept for display. Phone numbers keep the + and the whole country calling code plus the last two digits: wallet 0244123456 becomes +233244123456 and is shown as +233•••••••56; a UK number +447700900123 is shown as +44••••••••23. Other identifiers keep their first three and last two characters, so bank account GH0123456778 is shown as GH0•••••••78.
  • A keyed fingerprint lets SpeculaPro recognise the same number elsewhere, in particular as the counterparty of another customer’s transaction. That’s how it can see that money sent “to 0244123456” went to a wallet you monitor.

identifier_type tells SpeculaPro how to read the number. You can usually leave it out:

If you don’t send identifier_type and… SpeculaPro treats the identifier as
type is wallet msisdn (a phone number)
provider is a mobile-money provider (list below) msisdn
anything else account_number

Mobile-money providers recognised automatically (matched in lower case): mtn_momo, telecel_cash, vodafone_cash, at_money, airteltigo_money, airtel_money, orange_money, moov_money, wave, free_money, mpesa, momo.

Send identifier_type explicitly when the default would be wrong, for example "card_token" for a card account, or "msisdn" for a wallet from a provider not on the list. Any value other than the five allowed fails the whole batch with 422.

Type Normalisation These all match
msisdn Converted to international (E.164) format using your workspace’s country, exactly like customer phone numbers 0241234567, 233241234567, +233 24 123 4567, 00233241234567 → +233241234567
account_number, card_token, iban, other Spaces, dashes, dots and slashes removed; letters upper-cased gb82 west-1234, GB82WEST1234 → GB82WEST1234

So you don’t need to format wallet numbers the same way in every feed: a wallet sent as 0241234567 in your account feed and as +233 24 123 4567 in a transaction’s counterparty is recognised as the same number.

Message Cause Fix
currency must be an ISO 4217 code currency isn’t three capital letters Use codes such as GHS, USD, XOF
invalid data: violates … The record broke a database rule Check the values against the table above