Developer documentation
Pull deliveries, bounces, deferrals, opens and clicks into your own warehouse, dashboards or scripts over a read-only JSON/CSV API — or connect Claude, ChatGPT and other LLM clients directly to BigStats over the Model Context Protocol.
Authentication
Create a token in the dashboard under API & MCP clients. The full value is shown once; only a SHA-256 hash is stored. Send it on every request as Authorization: Bearer <token>. Tokens are read-only and cannot modify data or reach any other workspace. Each token carries explicit scopes — reports for aggregate endpoints and events for raw rows with recipient addresses. The same screen shows last-used timestamps, lists every connected MCP/OAuth assistant, and revokes either instantly.
Base URL
https://bigstats.email/api/public/v1
Endpoints
GET /api/public/v1/overview
Delivery KPIs and a time series for the workspace: injected, delivered, deferred, bounced, opened, clicked, complaints, plus the previous period for comparison.
Query: range=24h|7d|30d|90d (default 7d), cluster=<cluster id>
GET /api/public/v1/domains
Per-receiver performance grouped by recipient domain or ISP group.
Query: range=…, group_by=domain|isp (default domain), cluster=<cluster id>
GET /api/public/v1/bounces
Bounce and deferral diagnostics: category mix, sending-IP pressure and top SMTP response codes.
Query: range=…, cluster=<cluster id>
GET /api/public/v1/clusters
List the MTA clusters defined on the workspace with the /24 sending blocks each one covers. Use an id as the cluster filter on the other endpoints.
Query: none
GET /api/public/v1/pools
Shared IP pools: sending IPs used by more than one sender, senders rotating across several IPs, volume mix and rotation flags.
Query: range=…, cluster=<cluster id>
GET /api/public/v1/pool-detail
Per-member delivery health for one shared IP (split by sender) or one sender (split by IP): delivery, bounce, hard-bounce, deferral and complaint rates, bounce categories and top SMTP codes.
Query: mode=ip|sender (default ip), key=<ip or sender> (required), range=…, cluster=…, limit (1–500, default 100), compare=true|false
GET /api/public/v1/pool-domains
Drill through from one IP or sender to the receiving side — performance grouped by recipient domain or ISP group, so you can see where issues concentrate.
Query: mode=ip|sender, key=<ip or sender> (required), group_by=domain|isp (default domain), range=…, from/to (ISO 8601, override range), cluster=…, limit (1–500, default 100)
GET /api/public/v1/pool-series
Time-bucketed trend for one IP or sender — events, delivered, delivery rate, bounce rate, complaint rate and hard bounces per hour or per day, with an optional previous-period overlay.
Query: mode=ip|sender, key=<ip or sender> (required), range=…, bucket=hour|day, cluster=…, compare=true|false
GET /api/public/v1/events
Row-level events with all configurable fields (opens, clicks, bounce detail, SMTP code, DSN, diagnostic, VMTA, sending IP). JSON or CSV.
Query: event_type, domain, campaign_id, cluster, from (ISO 8601), to (ISO 8601), limit (1–5000, default 500), format=json|csv
Examples
cURL — overview
curl -s "https://bigstats.email/api/public/v1/overview?range=7d" \ -H "Authorization: Bearer $BIGSTATS_TOKEN"
cURL — bounce CSV export
curl -s "https://bigstats.email/api/public/v1/events?event_type=bounced&range=7d&format=csv&limit=5000" \ -H "Authorization: Bearer $BIGSTATS_TOKEN" -o bounces.csv
cURL — shared pool diagnosis
# Which senders share 203.0.113.42, and how is each one doing? curl -s "https://bigstats.email/api/public/v1/pool-detail?mode=ip&key=203.0.113.42&range=30d&compare=true" \ -H "Authorization: Bearer $BIGSTATS_TOKEN" # Where is that IP struggling, by ISP group? curl -s "https://bigstats.email/api/public/v1/pool-domains?mode=ip&key=203.0.113.42&group_by=isp&range=30d" \ -H "Authorization: Bearer $BIGSTATS_TOKEN"
JavaScript / TypeScript
const res = await fetch(
"https://bigstats.email/api/public/v1/domains?range=30d&group_by=isp",
{ headers: { Authorization: `Bearer ${process.env.BIGSTATS_TOKEN}` } },
);
if (!res.ok) throw new Error(`BigStats API ${res.status}`);
const { rows } = await res.json();
for (const r of rows) {
console.log(r.bucket, r.delivered, r.bounced);
}Python
import os, requests
r = requests.get(
"https://bigstats.email/api/public/v1/events",
params={"event_type": "bounced", "limit": 1000},
headers={"Authorization": f"Bearer {os.environ['BIGSTATS_TOKEN']}"},
timeout=30,
)
r.raise_for_status()
for e in r.json()["events"]:
print(e["occurred_at"], e["smtp_code"], e["bounce_category"], e["diagnostic"])Example response — /overview
{
"range": "7d",
"from": "2026-07-28T19:00:00.000Z",
"to": "2026-08-04T19:00:00.000Z",
"totals": {
"injected": 41293811, "delivered": 40510442, "deferred": 612004,
"bounced": 402118, "opened": 9120344, "clicked": 1044219, "complained": 3122
},
"previous": { "delivered": 38911204, "bounced": 448190 },
"series": [{ "bucket": "2026-07-28", "delivered": 5712001, "bounced": 61224 }]
}Event fields
| occurred_at | ISO 8601 timestamp of the event |
| event_type | injected · delivered · deferred · bounced · opened · clicked · unsubscribed · complained · rejected |
| recipient / recipient_domain | Address and receiving domain |
| isp_group | gmail · microsoft · yahoo · apple · other |
| campaign_id / mailing_id / message_id | Identifiers passed through from your MTA |
| sending_ip / vmta / remote_mta | Which IP and binding sent it, and which server answered |
| ip_block | The /24 the sending IP belongs to — the unit clusters are built from |
| smtp_code / dsn / diagnostic | Raw SMTP reply, enhanced status code and diagnostic text |
| bounce_type / bounce_category | hard · soft · block, and the classified reason |
| url / user_agent | Click destination and the client that generated the open or click |
Raw event queries are bounded to a rolling window for performance; aggregate endpoints read from pre-computed rollups and stay fast at billion-per-day volume. Rate limit: 60 requests per minute per token.
KumoMTA webhooks
Point a KumoMTA HTTP log hook at the ingest endpoint below and post records in the new_json log format. BigStats normalizes every record type into a single event model, so a KumoMTA deployment reports side by side with any other supported MTA.
Request
POST /api/public/ingest/kumomta?key=ga_live_…
Content-Type: application/json
# the key may also be sent as the header: x-bigstats-key: ga_live_…
# a single record …
{ "type": "Delivery", "recipient": "jordan@gmail.com", "timestamp": 1785000012 }
# … or a batch: a JSON array, newline-delimited JSON,
# or an object wrapper using "events", "records" or "logs"
{ "records": [
{ "type": "Reception", "recipient": "jordan@gmail.com", "timestamp": 1785000000 },
{ "type": "Delivery", "recipient": "jordan@gmail.com", "timestamp": 1785000012 }
] }Response
HTTP/1.1 200 OK
{ "ok": true, "received": 2 }
# failures
401 { "error": "missing ingest key" } # no ?key= and no x-bigstats-key header
401 { "error": "invalid ingest key" } # unknown or deactivated key
400 { "error": "no events found in payload" }
413 { "error": "too many events in one batch" } # hard cap of 25,000 records per POST
500 { "error": "could not store events" } # safe to retry; duplicates are droppedReception → injected — Message accepted into a KumoMTA queue. Establishes the denominator for delivery and bounce rates.
KumoMTA record — Reception
{
"type": "Reception",
"id": "01HZX9K7Q2W8N4C6",
"sender": "news@brand.example.com",
"recipient": "jordan@gmail.com",
"queue": "gmail.com",
"site": "",
"size": 42311,
"timestamp": 1785000000,
"tenant": "brand-weekly",
"campaign": "weekly-digest-2026-08",
"egress_pool": "pool-transactional",
"egress_source": "203.0.113.42",
"source_address": { "address": "203.0.113.42", "port": 0 },
"headers": {
"Subject": "Your weekly delivery digest",
"Message-ID": "<a1b2c3@brand.example.com>",
"X-Campaign-Id": "weekly-digest-2026-08",
"X-Mailing-Id": "m-99231"
},
"meta": { "list_id": "weekly" }
}Batched transaction — one record, one event per recipient
{
"type": "Delivery",
"id": "01HZXA13S5U7W9Y1",
"sender": "news@brand.example.com",
"recipient": ["a@gmail.com", "b@gmail.com", "c@gmail.com"],
"timestamp": "2026-08-04T12:15:00Z",
"egress_pool": "pool-transactional",
"source_address": { "address": "203.0.113.42" },
"peer_address": { "name": "gmail-smtp-in.l.google.com" },
"response": { "code": 250, "content": "2.0.0 OK - gsmtp" }
}How each field on a KumoMTA record maps onto the BigStats event columns you query over the API, MCP or CSV export. Unknown fields are never dropped — the full record is stored on raw.
| KumoMTA field | BigStats field | Notes |
|---|---|---|
| type | event_type | reception→injected, delivery→delivered, bounce/expiration/adminbounce/oob→bounced, transientfailure/delayed→deferred, rejection→rejected, feedback→complained. Anything else lands as other. |
| timestamp | occurred_at | Unix seconds, milliseconds or an ISO 8601 string. reception_timestamp / created are accepted as fallbacks. Missing timestamps default to receipt time. |
| recipient | recipient / recipient_domain / isp_group | String, object with email/address, or an array for batched transactions — an array produces one BigStats event per recipient. Lowercased; the domain is split off and grouped into gmail · microsoft · yahoo · apple · other. |
| headers['Message-ID'] | message_id | Falls back to id, then queue_id. |
| sender | (dedupe key) | Envelope sender. Combined with type, message_id, recipient and timestamp into the dedupe hash, so replayed records never double-count. |
| source_address.address | sending_ip | Falls back to egress_source. The /24 is derived from it for clustering. |
| egress_pool | vmta | Falls back to egress_source, then queue. |
| peer_address.name / .addr | remote_mta | Falls back to site, then provider_name — the receiving MX that answered. |
| response.code | smtp_code | Validated to 200–599. If absent, the first 3-digit code inside response.content is used. |
| response.enhanced_code | dsn | class.subject.detail joined, e.g. 5.1.1. Falls back to an enhanced code parsed out of response.content. |
| response.content | diagnostic | Full remote reply text. Falls back to reason (used by admin bounces and expirations). |
| bounce_classification | bounce_type / bounce_category | Used as the declared hint; BigStats still classifies from the SMTP code and diagnostic text into hard · soft · block plus a reason category. |
| headers['X-Campaign-Id'] / campaign / tenant | campaign_id | First match wins; tenant is the usual KumoMTA multi-brand key. |
| headers['X-Mailing-Id'] / mailing_id / campaign_id | mailing_id | First match wins. |
| headers['Subject'] | subject | Header lookups are case-insensitive. |
| (whole record) | raw | The complete JSON record is retained on the event row for drill-down and export. |
KumoMTA does not emit open or click records; those streams stay empty unless your tracking layer posts them separately. Records are de-duplicated on event type, message id, recipient, timestamp and sender, so log hook retries and replays are safe. See the KumoMTA setup guide.
MCP server
BigStats exposes a Model Context Protocol server so an LLM can query your delivery reporting directly and answer questions like “why did Microsoft bounce rate double yesterday?”. The connection uses OAuth: you sign in to BigStats and approve the client, and every tool call then runs as your account — scoped to your workspaces, read-only, with recipient addresses never exposed.
MCP endpoint (Streamable HTTP)
https://bigstats.email/mcp
Client config (Claude Desktop / Cursor style)
{
"mcpServers": {
"bigstats": {
"url": "https://bigstats.email/mcp"
}
}
}Settings → Connectors → Add custom connector, paste https://bigstats.email/mcp, then complete the BigStats sign-in and approve access.
Add BigStats as a remote MCP server using the same URL. Clients that support dynamic registration configure themselves; no client ID or secret to create.
| list_workspaces | List the workspaces the signed-in account can report on. |
| delivery_overview | KPIs and time series for a workspace and range. |
| domain_breakdown | Per-domain or per-ISP performance. |
| bounce_analysis | Bounce categories, IP pressure and SMTP codes. |
| shared_pools | Shared and rotating sending IPs, with sender mix and rotation flags. |
| pool_detail | Per-member delivery health for one IP or sender, with optional previous-period comparison. |
| pool_domains | Where one IP or sender is struggling, by recipient domain or ISP group. |
| search_events | Row-level drill-down, without recipient addresses. |
Errors
| 401 | Missing or invalid bearer token — create a new token in API & MCP clients. |
| 403 | The token lacks the scope this endpoint needs (reports or events). Issue a token with that scope. |
| 400 | Invalid query parameter. The response includes a detail array naming the field. |
| 500 | Query failed. Retry with a smaller range or narrower filters. |