Skip to content

Sending data to SpeculaPro

SpeculaPro can only monitor what it receives. Your systems (core banking, mobile money platform, card processor, payment switch) send it five kinds of record over a simple HTTPS API:

Record What it is Endpoint
Customers The people and businesses you serve POST /api/v1/ingest/customers
Accounts Their wallets, bank accounts, cards, merchant and agent accounts POST /api/v1/ingest/accounts
Transactions Every movement of money POST /api/v1/ingest/transactions
FX rates Daily exchange rates into your base currency POST /api/v1/ingest/fx-rates
Account events Logins, failed OTPs and PINs, SIM swaps, profile changes, new payees: what happens on an account besides money moving POST /api/v1/ingest/events

Each request carries a batch of up to 500 records (by default). SpeculaPro answers with a status for every record, so you always know exactly which ones were taken and which need attention.

The API is the main route, but the same records can arrive four other ways. Whichever you use, every record gets the same checks, currency conversion, encryption, duplicate protection and per-record result.

Route How Suits Guide
REST API Your systems push batches over HTTPS Real-time or near-real-time feeds This section
File drops SpeculaPro collects end-of-day files (CSV, JSON Lines, ISO 20022 camt.053 and pain.001) from your SFTP server or S3-compatible bucket, and writes a results file back Banks and providers that already produce daily extracts or statements File drops
Kafka topics Your systems publish to your own topics; SpeculaPro reads them Institutions with their own streaming platform Kafka topics
Change data capture SpeculaPro follows your core-banking database’s changes (Debezium topics, or Postgres logical replication) and a mapping turns rows into records Institutions whose systems can’t push, and whose DBA can grant read access to a replication stream Change data capture
Data import An administrator uploads a CSV or JSON file under Settings → Data import Pilots, and backfilling history Importing a file

File drops, Kafka topics and change data capture are set up under Settings → Data sources. See Data sources.

SpeculaPro never queries your core systems while it monitors. It keeps its own copy of what you send, for three reasons:

  • Evidence. When a rule fires, SpeculaPro must show exactly what it saw, even if your source record changes later.
  • Speed and safety. Rules look back over months of history. Asking your core banking system for that on every transaction would be slow and would load your production systems.
  • Independence. If your systems are slow or offline, monitoring of what has already arrived carries on.

See Data ownership and ingestion for the design.

Your system ──HTTPS──▶ SpeculaPro ingest ──▶ checks, currency conversion, encryption
│
customers, accounts, FX rates ───┤──▶ written to the database straight away
│
transactions, account events ─────┴──▶ queued durably (Kafka) ──▶ written to the database
──▶ evaluated by your rules
  • Customers, accounts and FX rates are written to the database before you get the response.
  • Account events are counted for rules the moment they arrive, then queued and stored like transactions. See Account events.
  • Transactions are checked, converted to your base currency, stripped of readable identifiers and placed on a durable queue. A 200 response means every transaction marked accepted is safely stored on that queue; a separate process writes it to the database moments later. See Responses and statuses.
  • Personal identifiers (national ID numbers, phone numbers, emails, account and wallet numbers) are encrypted before they are stored. See Personal data.

For a clean start, send records in this order:

  1. FX rates, if any of your transactions are in a currency other than your workspace’s base currency (GHS for most Ghanaian institutions). Transactions in a foreign currency are rejected when no suitable rate exists. See Currencies and FX.
  2. Customers, with everything you know about them: type, risk rating, KYC level, identifiers.
  3. Accounts, each linked to its customer.
  4. Transactions, continuously from then on.
  5. Account events, continuously, as they happen, if you use fraud rules. Send them no later than the transactions they come before.

You need:

  • the address of your SpeculaPro workspace, for example https://speculapro.example.com;
  • an API key with the ingest:write scope. See Getting an API key;
  • your workspace’s base currency and country. These were set when the workspace was created; ask your administrator. The country decides how local phone numbers are read.
Terminal window
curl https://speculapro.example.com/api/v1/ingest/transactions \
-H "Authorization: Bearer $SPECULAPRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [{
"external_id": "MOMO-2026-10-09-000184223",
"occurred_at": "2026-10-09T08:14:03Z",
"customer_external_id": "CUST-77120",
"account_external_id": "WAL-0244123456",
"direction": "in",
"type": "cash_in",
"channel": "agent",
"amount_minor": 495000,
"currency": "GHS"
}]
}'
{
"accepted": 1,
"duplicate": 0,
"stale": 0,
"rejected": 0,
"retry": 0,
"results": [
{
"index": 0,
"external_id": "MOMO-2026-10-09-000184223",
"status": "accepted",
"id": "0199c8f2-41a7-7d02-b3e5-1f6a9c0e2b47"
}
]
}

That is a GHS 4,950.00 agent cash-in into a mobile money wallet. amount_minor is in pesewas: see Currencies and FX.

Check
☐ A separate API key per system and per environment, stored in a secret manager
☐ FX rates loaded daily for every foreign currency you process, before that day’s transactions
☐ All existing customers and accounts loaded (a one-off back-fill), then updates sent as they happen
☐ Every transaction has a stable, unique external_id that never changes and is never reused
☐ Your sender reads the per-record status, not only the HTTP status, and resends records marked retry
☐ Records marked rejected are logged and routed to someone who can fix the data
☐ Customer and account updates carry a version that only ever increases (or none at all, if they can never arrive out of order)
☐ Every customer and account update sends the full record: omitted fields are cleared
☐ Wallet accounts and counterparties carry a provider (or identifier_type), so phone numbers are matched in any format
☐ Bank account numbers and card tokens are the same characters across all feeds (spacing and case don’t matter)
☐ A test run against a non-production workspace with a day of real-shaped data, with zero unexplained rejections
☐ Monitoring on your side alerts when sending fails or the rejected count jumps