Skip to content

KPI Module

The SAFE Billing Platform KPI (Key Performance Indicators) module provides real-time business intelligence data about your billing operations. Access comprehensive metrics about customers, numbers, features, invoices, payments, Direct Debit collections and outstanding debt through a simple REST API.

The KPI module is designed to integrate seamlessly with your existing business intelligence tools and workflows:

Dashboard Integration

  • Fetch JSON data to populate real-time dashboards (Grafana, PowerBI, Tableau)
  • Set up automated polling to keep metrics current
  • Combine multiple KPIs to create comprehensive business views

Automated Reporting

  • Schedule daily/weekly CSV exports for spreadsheet analysis
  • Track performance trends over time with automated scripts
  • Generate alerts when metrics exceed thresholds

Quick Browser Checks

  • Use HTML output to quickly view KPIs in your browser
  • Share read-only KPI links with team members
  • Bookmark specific KPI views for regular monitoring

Daily Revenue Report Script

#!/bin/bash
# Download yesterday's invoice breakdown as CSV
curl -X GET "https://companyname.callstats.net/backend/kpi/invoices/breakdown/?outputMode=csv" \
-H "Authorization: Bearer YOUR_KPI_KEY" \
> "revenue_$(date +%Y%m%d).csv"

Dashboard JSON Feed

// Fetch customer metrics every 5 minutes for dashboard
setInterval(async () => {
const response = await fetch('https://companyname.callstats.net/backend/kpi/customers/', {
headers: { 'Authorization': 'Bearer YOUR_KPI_KEY' }
});
const data = await response.json();
updateDashboard(data);
}, 300000);

Browser Bookmark

https://companyname.callstats.net/backend/kpi/invoices/byMonth/?key=YOUR_KPI_KEY

Save this URL to quickly check monthly billing trends in your browser.

The KPI module offers aggregated data and insights across seven categories:

  • Customers - Monitor customer counts, status distributions, and dealer performance
  • Numbers - Track telephone number allocations, types, and utilisation
  • Features - Analyse feature adoption, revenue generation, and charge summaries
  • Invoices - Review billing performance, revenue trends, and payment status
  • Payments - Report amounts actually received, by method, dealer and period
  • Collections - Measure Direct Debit and card collection performance, including failures and retries
  • Debt Ageing - Bucket outstanding invoice balances by how overdue they are

Customers, numbers and features can also produce growth and churn series, showing additions and losses over time. See Available KPIs for the full list.

Where the CRM is in use, six further categories answer sales and credit control questions: crmDeals, crmProposals, crmCases, crmChaseCases, crmSequences and crmEmailQueue. They reuse each object’s own access rules, so a query never totals anything the user could not list. See Machine access.

All KPI endpoints require authentication using a valid KPI access key. You can authenticate using either method:

Terminal window
curl -X GET https://companyname.callstats.net/backend/kpi/customers/ \
-H "Authorization: Bearer YOUR_KPI_KEY"
Terminal window
curl -X GET "https://companyname.callstats.net/backend/kpi/customers/?key=YOUR_KPI_KEY"

The KPI module is accessed through your platform’s domain:

https://companyname.callstats.net/backend/kpi/

Replace companyname with your actual platform subdomain.

The same endpoint is also served at /crm/kpi/, which returns identical data under identical permissions. It exists so a CRM-only user has an obvious address to point a dashboard tool at. See Machine access.

KPI endpoints use a RESTful URL structure:

/backend/kpi/{object}/
/backend/kpi/{object}/{kpi}/

Where:

  • {object} is the KPI category: customers, numbers, features, invoices, payments, directDebitCollections, cardCollections, or arAgeing
  • {kpi} is the specific KPI type (optional, defaults to summary)

The endpoint answers GET requests only. Any other method returns 405 Method Not Allowed with an Allow: GET header.

ParameterTypeRequiredDescription
outputModestringNoResponse format: json, csv, or html
keystringConditionalKPI key if not using Bearer authentication
groupBystringNoComma-separated dimensions to group by, instead of a {kpi} type
intervalstringNoPeriod bucket: day, week, month, quarter, or year
measuresstringNoNamed measure set to return (defaults to default)

Every KPI category can be grouped by any of its dimensions using the groupBy parameter, as an alternative to the named KPI types. Combine it with interval to bucket results by time period:

Terminal window
# Payment totals by quarter and payment method
curl -X GET "https://companyname.callstats.net/backend/kpi/payments/?groupBy=period,method&interval=quarter" \
-H "Authorization: Bearer YOUR_KPI_KEY"
  • groupBy accepts a comma-separated list of the category’s dimensions. Each category page lists its dimensions.
  • interval controls the size of the period dimension’s buckets: day, week, month, quarter, or year. If you group by period without an interval, it defaults to month.
  • interval also works with the named KPI types, so invoices/byMonth/?interval=quarter re-buckets the familiar monthly report by quarter.
  • measures selects a named set of result columns where a category offers more than one (for example the invoices breakdown set).

Grouped period columns are named {prefix}period in JSON output (for example invoice_period), except in the legacy byMonth KPI types, which keep their original {prefix}month names.

Summary KPI

Terminal window
curl -X GET "https://companyname.callstats.net/backend/kpi/customers/" \
-H "Authorization: Bearer YOUR_KPI_KEY"

Specific KPI

Terminal window
curl -X GET "https://companyname.callstats.net/backend/kpi/customers/byStatus/" \
-H "Authorization: Bearer YOUR_KPI_KEY"

JSON responses return an array of objects with field names prefixed by the KPI type:

[
{
"customer_count": 2847
}
]

For multi-row results:

[
{
"customer_status": "Active",
"customer_count": 2650
},
{
"customer_status": "Suspended",
"customer_count": 47
}
]

CSV output includes headers and is suitable for spreadsheet import:

Customer Status,Count
Active,2650
Suspended,47

HTML output displays results in a formatted table, ideal for browser viewing:

Customer StatusCount
Active2650
Suspended47

KPI results are aggregate. The endpoint reports on the platform as a whole or on a group of customers, never on a single named customer - to report on one customer, use the record list endpoints, for example /invoices/?f[customerID]=1234.

Filtering comes in three forms: the named filters each category declares, generic field filters on a permitted set of fields, and customer cohort scoping.

These filters apply to every category:

FilterTypeDescriptionExample
createdSincedate/timeEntries created since this datecreatedSince=2026-01-01
updatedSincedate/timeEntries modified since this dateupdatedSince=2026-01-01
droppedSincedate/timeEntries dropped since this date, by activity stamp rather than effective drop datedroppedSince=2026-01-01

Each category declares its own filters on top of these; see the category pages for the full list.

Three rules apply throughout:

  • A boolean filter set to false is dropped, not inverted. active=0 means “no filter”, not “inactive”. Use dropped=true for the opposite of active=true.
  • Name and ID forms cannot be combined. Filters come in pairs - status and statusID, dealer and dealerCode, numberType and numberTypeID, featureType and featureTypeID, paymentMethod and paymentMethodID. Pass one or the other; passing both returns a 400. The same applies to a range whose minimum falls after its maximum.
  • An unrecognised name returns no rows rather than an error. A misspelt status or dealer value produces an empty result, so check the spelling when a figure comes back as zero. Setting dealer= with no value matches records with no dealer.

The API’s generic field filters (f[field]=value and f[field][operator]=value) apply to a permitted set of fields for each category:

CategoryFilterable fields
customersstatusID, currencyID, dealerCode, customerClass, billingCycle, paymentMethod, accountManagerID, commissionHolderID, soldByID, enteredDate, updatedDate, contractStartDate, contractEndDate
numbersstatusID, numberTypeID, soldDate, soldByID, enteredDate, updatedDate, lines
featuresstatusID, startDate, endDate, soldDate, enteredDate
invoicesinvoiceDate, dueDate, invoiceAmount, invoiceVAT, paidAmount
paymentspaymentDate, paymentReversedDate, paymentMethod, paymentAmount
directDebitCollectionscollectionDate, directDebitPaymentAmount, statusID, retryCount, directDebitPaymentFailedStamp
cardCollectionspaymentCardPaymentAmount, statusID, paymentCardPaymentTakenStamp, paymentCardPaymentSetupStamp
arAgeinginvoiceDate, dueDate, invoiceAmount, invoiceVAT, paidAmount

Any other field returns a 400 (error code 400027) listing the fields that category accepts. A field that identifies a customer returns 400026, since the endpoint reports aggregates only.

Send either the bare form of a field or an operated form, never both. A request carrying f[invoiceAmount]=100 alongside f[invoiceAmount][min]=50 does not mean what it looks like: whichever appears later in the query string replaces the other outright, so the filter you meant may be discarded without a word.

The operators available depend on the field:

Field typeOperatorsFields
Lookupeq, in, nullstatusID, currencyID, dealerCode, customerClass, billingCycle, paymentMethod, accountManagerID, commissionHolderID, soldByID, numberTypeID
Dateeq, min, max, nullenteredDate, updatedDate, contractStartDate, contractEndDate, soldDate, startDate, endDate, invoiceDate, dueDate, paymentDate, paymentReversedDate, collectionDate, and the *Stamp fields
Amount or counteq, min, max, in, nulllines, retryCount, invoiceAmount, invoiceVAT, paidAmount, paymentAmount, directDebitPaymentAmount, paymentCardPaymentAmount

f[field]=value with no operator means eq. min is “on or after”, max is “on or before”, null=1 matches empty values and null=0 matches populated ones. See Filtering in the API Reference for the full syntax.

Every category except customers accepts f[customer][...], which narrows results to a group of customers rather than to one:

FilterDescription
f[customer][createdSince]Customers added since this date
f[customer][updatedSince]Customers modified since this date
f[customer][droppedSince]Customers dropped since this date
f[customer][f][field]Any field on the customers list above
Terminal window
# Numbers dropped by month, for customers added since August 2025
curl -X GET "https://companyname.callstats.net/backend/kpi/numbers/droppedByPeriod/?f[customer][createdSince]=2025-08-01" \
-H "Authorization: Bearer YOUR_KPI_KEY"

Anything else inside f[customer] returns a 400 listing what is available.

The limit, offset and orderBy list parameters do not apply to KPI results. Row ordering is fixed by the requested dimensions, and a grouped query returns every group.

An AI assistant reaches the same KPI data with the same filters, including the generic field filters above. Two things differ, because an assistant acts as a named user rather than through a platform-wide key:

  • It can scope a KPI question to a single customer, which this endpoint cannot.
  • Its totals cover business customers only unless the connection holds Personal Customers access. This endpoint always reports platform-wide, so a figure here can legitimately exceed the same figure from an assistant.

See Summaries and rollups.

Terminal window
curl -X GET "https://companyname.callstats.net/backend/kpi/customers/byDealer/?active=true" \
-H "Authorization: Bearer YOUR_KPI_KEY"

The KPI module returns standard HTTP status codes:

Status CodeDescription
200Success - Request processed successfully
400Bad Request - Invalid parameters or request format
401Unauthorised - Missing or invalid KPI key
403Forbidden - Valid key but insufficient permissions
404Not Found - Invalid object or KPI type
405Method Not Allowed - The endpoint answers GET only
500Server Error - Internal processing error

JSON Format

{
"errors": {
"error": "Invalid KPI type",
"error_code": 400001,
"hint": "Valid KPI types for customers are: null, byStatus, byDealer"
}
}

HTML Format

Error: Invalid KPI type
Error Code: 400001
Hint: Valid KPI types for customers are: null, byStatus, byDealer

KPI endpoints are subject to rate limiting to ensure platform stability. Monitor response headers for current limits and adjust request frequency accordingly.

KPI data is updated in real-time as changes occur in the platform:

  • Customer/Number/Feature KPIs: Real-time updates
  • Invoice KPIs: Updated as invoices are generated, sent, or paid
  • Financial summaries: Calculated on-demand for accuracy
  1. Cache responses appropriately - KPI data changes less frequently than transactional data
  2. Use specific KPIs - Request only the data you need rather than parsing general summaries
  3. Implement retry logic - Handle rate limits and temporary errors gracefully
  4. Monitor rate limit headers - Adjust request frequency based on remaining allowance
  5. Use filters - Reduce data transfer and processing by filtering at the source

Explore the detailed documentation for each KPI category: