Sending data to SpeculaPro
In short
Section titled “In short”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.
Five ways in
Section titled “Five ways in”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.
Why SpeculaPro keeps its own copy
Section titled “Why SpeculaPro keeps its own copy”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.
What happens to what you send
Section titled “What happens to what you send”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
200response means every transaction markedacceptedis 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.
The order to send things in
Section titled “The order to send things in”For a clean start, send records in this order:
- 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.
- Customers, with everything you know about them: type, risk rating, KYC level, identifiers.
- Accounts, each linked to its customer.
- Transactions, continuously from then on.
- Account events, continuously, as they happen, if you use fraud rules. Send them no later than the transactions they come before.
Before you start
Section titled “Before you start”You need:
- the address of your SpeculaPro workspace, for example
https://speculapro.example.com; - an API key with the
ingest:writescope. 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.
A first request
Section titled “A first request”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.
Go-live checklist
Section titled “Go-live checklist”| 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 |