Skip to content

Analytics API

The Analytics API returns the month-by-month figures behind the Analytics page: transaction volume, rule hits by typology, rule performance, alert outcomes and real-time decision outcomes. Use it to feed a board pack, a management dashboard or your own data warehouse.

For today’s figures, use the Dashboard API instead.

Who can call it A signed-in person with alerts.view (by default every compliance role; not the Administrator). API keys can’t call it
How to sign in with curl As described in Rules API: before you start
Months Calendar months in your workspace’s time zone, written YYYY-MM, exact in every time zone (half-hour and quarter-hour offsets included). The current month is always included and is incomplete until it ends
Money Whole numbers of minor units in your base currency (pesewas for GHS): 48200000 means GHS 482,000.00
Freshness From the analytics store, normally seconds behind live and never more than 15 minutes: beyond that the answer comes from the main database. From the main database, live
Changes nothing All five are reads. Nothing is written or audited
Endpoint Returns
GET /api/v1/analytics/volume Completed transactions by month, channel and type
GET /api/v1/analytics/typologies Rule hits by month and rule category
GET /api/v1/analytics/rules The ten rules with the most hits, by month, with their closed and confirmed alerts
GET /api/v1/analytics/alerts Alerts by the month they were raised, kind and outcome
GET /api/v1/analytics/decisions Real-time decisions by month and outcome

All five take the same query parameter and return the same window fields.

Parameter Default Meaning
months 12 How many months to cover, the current one included. 1 to 24; anything else is refused with 422

The answer never covers more than can be shown for your workspace right now: up to 24 months from the analytics store, 3 from the main database. Asking for more isn’t an error. You get the most available, months in the answer tells you how many that was, and limit tells you why it is fewer than 24.

Terminal window
curl -s -b ~/.sp-cookies "$SP/api/v1/analytics/volume?months=6"

Every answer starts with the same fields, saying what it covers and where it was read:

{
"source": "clickhouse",
"months": 6,
"from": "2026-05-01T00:00:00Z",
"max_months": 24,
"timezone": "Africa/Accra",
"base_currency": "GHS"
}
Field Meaning
source clickhouse: read from the analytics store. postgres: read from the main database; limit says why
months Months actually covered, the current one included
from The first instant covered: midnight on the first day of the earliest month, in the workspace’s time zone
max_months The most that can be shown for this workspace right now. 24 when the analytics store holds the workspace’s whole history; 3 from the main database; a number in between when the analytics store holds the workspace’s history from a later date only
limit Why max_months is below 24. Absent when 24 months are available. See the table below
timezone The workspace’s time zone, which decides where each month starts and ends
base_currency The currency of every amount

limit is one of:

limit Meaning
not_configured The installation has no analytics store
not_copied The analytics store is connected, but this workspace’s history hasn’t been copied to it
copying A copy of this workspace’s history into the analytics store is running, or was interrupted
partial The analytics store holds this workspace’s history from a later date only. With more than 3 whole months, source is clickhouse and max_months is what it holds; with 3 or fewer, source is postgres
behind The analytics store is more than 15 minutes behind live
unavailable The analytics store couldn’t be reached for this request

Months with nothing to count are left out of points. Build the full list of months from from and months if you need the gaps.

{
"source": "clickhouse", "months": 6, "from": "2026-05-01T00:00:00Z",
"max_months": 24, "timezone": "Africa/Accra", "base_currency": "GHS",
"points": [
{ "month": "2026-05", "channel": "bank_transfer", "type": "transfer", "count": 1840, "amount_minor": 912400000 },
{ "month": "2026-05", "channel": "mobile_money", "type": "deposit", "count": 22310, "amount_minor": 446200000 }
]
}

Figures are illustrative.

Field Meaning
month YYYY-MM
channel, type The transaction’s channel and type, as you sent them
count Transactions with status completed that happened in that month
amount_minor Their total value in the base currency, minor units

Pending, failed, reversed and blocked transactions aren’t counted. Points are ordered by month, then channel, then type.

{
"source": "clickhouse", "months": 6, "from": "2026-05-01T00:00:00Z",
"max_months": 24, "timezone": "Africa/Accra", "base_currency": "GHS",
"points": [
{ "month": "2026-05", "category": "structuring", "hits": 64, "suppressed": 3 },
{ "month": "2026-05", "category": "velocity", "hits": 121, "suppressed": 0 }
]
}
Field Meaning
category The rule’s category. Empty for a rule without one
hits Every hit in that month by a live rule, including hits that an approved suppression kept from raising an alert
suppressed How many of hits were suppressed. It is a part of hits, not an addition to it

Hits by rules in shadow mode are in neither figure. hits is counted the same way here, in rule performance and in the dashboard’s rule figures, and is the same from either database. A hit is placed in the month it was recorded. With the analytics store, the category is the rule’s category when the hit was copied, so re-categorising a rule doesn’t move its old hits; from the main database it is the rule’s category today.

{
"source": "clickhouse", "months": 6, "from": "2026-05-01T00:00:00Z",
"max_months": 24, "timezone": "Africa/Accra", "base_currency": "GHS",
"rules": [
{
"key": "core.structuring",
"name": "Repeated deposits just below a threshold",
"hits": 212,
"suppressed": 9,
"closed_alerts": 90,
"confirmed_alerts": 31,
"months": [
{ "month": "2026-05", "hits": 40, "suppressed": 2, "closed_alerts": 12, "confirmed_alerts": 5 },
{ "month": "2026-06", "hits": 33, "suppressed": 0, "closed_alerts": 18, "confirmed_alerts": 4 }
]
}
]
}
Field Meaning
rules At most ten rules: those with the most hits over the window, most first
key, name The rule’s key, such as core.structuring (not its library letter, such as B1), and its name
hits Hits by the live rule, including suppressed ones; shadow hits are excluded
suppressed How many of hits an approved suppression kept from raising an alert
closed_alerts Alerts involving this rule that were closed in that month
confirmed_alerts Of those, the alerts closed with any disposition other than false_positive
months The same figures month by month. Months with none of them are left out

The Analytics page’s “N% confirmed” is confirmed_alerts ÷ closed_alerts over the window. An alert raised by several rules counts once for each. Note the two different months: a hit is counted in the month it was recorded, an alert in the month it was closed.

{
"source": "clickhouse", "months": 6, "from": "2026-05-01T00:00:00Z",
"max_months": 24, "timezone": "Africa/Accra", "base_currency": "GHS",
"points": [
{ "month": "2026-05", "kind": "aml", "outcome": "false_positive", "count": 48 },
{ "month": "2026-05", "kind": "aml", "outcome": "escalated_to_case", "count": 17 },
{ "month": "2026-05", "kind": "fraud", "outcome": "open", "count": 2 }
]
}
Field Meaning
month The month the alert was raised
kind aml, fraud, sanctions or other
outcome Where the alert stands now. For a closed alert, its disposition: false_positive, no_action, duplicate or escalated_to_case, or other if it has none. Otherwise its status: open or in_case
count Alerts

Because outcome is the alert’s current state, the figures for a past month change as its alerts are worked.

{
"source": "clickhouse", "months": 6, "from": "2026-05-01T00:00:00Z",
"max_months": 24, "timezone": "Africa/Accra", "base_currency": "GHS",
"points": [
{ "month": "2026-05", "outcome": "allow", "count": 181204, "degraded": 12, "avg_latency_ms": 6.4 },
{ "month": "2026-05", "outcome": "review", "count": 310, "degraded": 0, "avg_latency_ms": 7.9 }
]
}
Field Meaning
outcome allow, step_up, review or block
count Real-time decisions made in that month with that outcome
degraded Of those, the decisions answered by the workspace’s failure policy because SpeculaPro couldn’t finish in time
avg_latency_ms The average time SpeculaPro took to decide, in milliseconds, as measured inside SpeculaPro. It doesn’t include the network between your system and SpeculaPro
Status When
401 Not signed in, or called with an API key
403 Signed in without alerts.view
404 The process you reached doesn’t run the api role
422 months is below 1 or above 24
500 Neither database could answer. The body carries a reference to give your administrator

Errors use the same problem format as the rest of the API.