Appearance
Build Compliance Dashboards with the Metrics API
The Metrics API gives your own dashboards the numbers behind CryptoComply: onboarding volumes and turnaround, screening hits, alert and RFI queues, and open workload. Individuals and entities are covered by the same metrics.
| Endpoint | What it does |
|---|---|
GET /api/orgs/{org_id}/analytics/metrics/ | The catalog: the metrics and dimensions your organization can report on, and the report limits. |
POST /api/orgs/{org_id}/analytics/report/ | Runs a report over a date range, bucketed by day, week or month, grouped and filtered. |
Paths are relative to https://api.cryptocomply.xyz. Use current as {org_id} to mean the organization of your API key.
Authenticate
Ask the CryptoComply team for an API key with the analytics.read permission. It reads metrics and nothing else. Send it on every request, and keep it in a secret manager. See Authentication & API keys.
bash
curl https://api.cryptocomply.xyz/api/orgs/current/analytics/metrics/ \
-H "Authorization: Api-Key $CRYPTOCOMPLY_API_KEY"Discover the metrics
The catalog is the source of truth: read it instead of hard-coding. Each metric has an id to send in reports, a label for chart titles, a unit (count, ratio from 0 to 1, or seconds), the granularities it takes (empty for a current count), and the dimensions it can be grouped and filtered by. Each dimension lists its values. Metrics for optional features appear only if your organization uses them.
A sample of what is available:
| Area | Metrics |
|---|---|
| Profiles | profiles.created, profiles.decided, profiles.approval_rate, profiles.time_to_decision_p50, profiles.time_to_decision_p90, profiles.pending_review |
| Cohorts (by arrival week or month) | cohort.completion_rate, cohort.turnaround_p50, cohort.turnaround_p90 |
| Screening | screening.hits, screening.profiles_hit, screening.hits_resolved, screening.address_runs, screening.address_failure_rate |
| Blockchain addresses | blockchain_addresses.added, blockchain_addresses.by_risk, blockchain_addresses.screened, blockchain_addresses.cannot_be_screened, blockchain_addresses.risk_rose, blockchain_addresses.time_to_first_screening_p50 |
| Intake forms | intake.submissions, intake.invitations_sent, intake.completion_rate, intake.time_to_submit_p50, intake.held_duplicates |
| Transaction alerts | transaction_alerts.received, transaction_alerts.closed, transaction_alerts.time_to_decide_p50, transaction_alerts.override_rate |
| Alerts | alerts.created, alerts.closed, alerts.time_to_close_p50, alerts.open |
| RFIs | rfis.sent, rfis.answered, rfis.response_rate, rfis.response_time_p50, rfis.open, rfis.overdue |
| Workload and SLAs | alerts.unassigned, reviews.overdue |
Common dimensions: profile.type (individual or entity), profile.group, profile.review_status, profile.risk, alert.type (including blockchain_address for blockchain address alerts), alert.status, hit.kind, hit.resolution and review.age.
Run a report
Only metrics is required.
bash
curl -X POST https://api.cryptocomply.xyz/api/orgs/current/analytics/report/ \
-H "Authorization: Api-Key $CRYPTOCOMPLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metrics": ["profiles.created", "profiles.decided"],
"dimensions": ["profile.type"],
"filters": [{ "dimension": "profile.risk", "op": "in", "values": ["high", "severe"] }],
"time": {
"from": "2026-07-06",
"to": "2026-09-27",
"granularity": "week",
"timezone": "America/New_York",
"compare": "previous_period"
},
"include": ["totals"]
}'| Field | What it does |
|---|---|
metrics | Up to 20 metric IDs. Each becomes a column. |
dimensions | Up to 2 dimensions to group by. Every metric must take them. |
filters | dimension, op (eq, in, not_in) and values. All must pass. |
time | from and to (both included), granularity (day, week, month), timezone (IANA, default UTC) and compare (previous_period). Without a range, the last 30 days. |
order, limit | Sort by a metric, dimension or bucket; cap the rows (up to 5000). |
include | ["totals"] recomputes each metric over the whole range. |
The response has meta (range, timezone, granularity, rows_count, is_truncated, computed_at, is_cached), columns (labels and units for headers and formatting), rows and totals.
- Empty buckets are filled with zeros; a rate or median with nothing behind it is
null. bucketis the first day of the bucket: a Monday for weeks, the 1st for months.compareon each row holds the matching bucket of the previous period.partialistruefor the bucket still running; style it differently.- Use
totalsfor headline numbers, especially rates and percentiles, which can't be summed from the buckets. - Durations are in seconds.
- Reports are cached for up to an hour, so refreshing every few minutes is plenty.
Recipes
Onboarding funnel by profile type
Applications received and decided each week, split by profile type, with the previous period for comparison:
json
{
"metrics": ["profiles.created", "profiles.decided"],
"dimensions": ["profile.type"],
"time": { "from": "2026-07-06", "to": "2026-09-27", "granularity": "week", "compare": "previous_period" },
"include": ["totals"]
}Chart one line per profile.type. Add a filter on profile.type to build a view for individuals or entities only. For turnaround, run profiles.time_to_decision_p50 and profiles.time_to_decision_p90 by month and divide by 86,400 for days.
Screening and alert workload
Hits found and alerts raised per week, then what is still open now:
json
{
"metrics": ["screening.hits", "alerts.created", "alerts.closed"],
"time": { "from": "2026-07-06", "to": "2026-09-27", "granularity": "week" }
}json
{
"metrics": ["alerts.open", "alerts.unassigned"],
"dimensions": ["alert.type", "alert.status"]
}Current counts such as alerts.open take no granularity. Group by alert.type to show watchlist, blockchain address, adverse media and duplicate alerts side by side, and use hit.kind on screening.hits to split sanctions, PEP and adverse media.
Open reviews by status and age (SLAs)
json
{
"metrics": ["profiles.pending_review"],
"dimensions": ["profile.review_status", "review.age"],
"include": ["totals"]
}review.age buckets profiles by days in their current status: 0_2, 3_7, 8_14 and 15_plus. Show it as a stacked bar or heat map, next to reviews.overdue and rfis.overdue as SLA indicators.
Limits and errors
A report covers at most 366 days, 2 dimensions, 20 metrics and 5000 rows; the catalog's limits gives the current values. An API key can call the analytics endpoints 30 times a minute; past that the API answers 429 with a Retry-After header.
Errors share one body with a detail, a stable code and a correlation_id. A refused report answers 422 with errors[] pointing at the field to fix.
| Status | code | Fix |
|---|---|---|
401 | auth.invalid_credentials, auth.token_expired | Check the Authorization header and the key. |
403 | auth.permission_denied | The key needs analytics.read for this organization. |
422 | analytics.unknown_metric, analytics.incompatible_dimension, analytics.invalid_filter | Use IDs and values from the catalog; drop granularity for current counts. |
422 | analytics.range_too_large, analytics.cardinality_exceeded | Shorten the range or use fewer dimensions. |
429 | rate.throttled | Wait the seconds in Retry-After. |
See Errors for the codes every endpoint shares.
What's next?
- REST API reference — every field of both endpoints, under Analytics.
- Exports & audit trail — export the records behind a number.