Read-only API

Calibri public API

REST basehttps://api.getcalibri.com/api/public/v1
MCPhttps://api.getcalibri.com/mcp

What this is

An HTTP API and an MCP endpoint for the deliverability data Calibri measures for your workspace: per-mailbox scores, inbox placement per provider (how much lands in spam), domain health, alerts and the weekly report. Your scripts, n8n, Make, Sheets and Slack read it over HTTP; Claude, Claude Code and Cursor read it over MCP. Over MCP, an assistant can also switch weekly inbox placement tests on or off and connect Slack, if the workspace owner allowed changes for its key.

  • REST base: https://api.getcalibri.com/api/public/v1
  • MCP: https://api.getcalibri.com/mcp
  • Every response is JSON, except this page (markdown).
  • The REST API never writes. A key reads one workspace and nothing else.
  • Three MCP tools make changes (set_placement_tests, connect_slack, disconnect_slack). They work only with a key the workspace owner allowed to make changes; every other key stays read-only.
  • Scores are cached, not recomputed on request: mailbox scores refresh twice a day, domain health hourly. Every score carries computedAt, so you can see how fresh it is. Polling more often than that returns the same numbers. The exception is the weekly report: it is rebuilt on a read when the cached copy no longer matches the workspace (for example a mailbox or domain was added), so two reads can differ.

GET /api/public/v1 (no key) lists the endpoints. GET /api/public/v1/docs (no key) returns this page.

Authentication

Send your key in the Authorization header on every request:

Authorization: Bearer cal_live_<48 hex characters>
  • Create, view and revoke keys in Calibri under Connectors > API & MCP (workspace owners). A key is shown once, at creation. Store it like a password.
  • A key belongs to one workspace. Keys start with cal_live_.
  • Every key is read-only unless the owner turns on "Allow changes" for it (at creation or later, under Connectors > API & MCP). Only the three MCP write tools use that permission. A key with changes allowed can switch placement tests and point the workspace's Slack alerts at any Slack channel, so give it only to tools you trust. Turning it off takes effect on the key's next request. Every change made with a key is logged with the key's id and label.
  • Send the key only in the header. Never put it in a URL or query string: URLs end up in server logs. Calibri does not accept keys in URLs.
  • Keys for the Calibri dashboard do not work here, and public keys do not work on the dashboard API.
  • The workspace needs an active subscription, otherwise requests return 402.

A missing, invalid or revoked key returns 401 with a WWW-Authenticate: Bearer realm="calibri" header:

json
{ "error": "unauthorized", "message": "This key is invalid or was revoked." }

A request with no usable Authorization header returns the message Send Authorization: Bearer cal_live_... Create a key in Calibri under Connectors > API & MCP.

Rate limits

  • 120 requests per minute per key. Over the limit: 429 with a retry-after header (seconds).
  • 600 requests per minute per IP address, on top of the per-key limit.
  • GET /reports/weekly (and the get_weekly_report MCP tool) is also limited to 200 reads per workspace per day. Over it: 429 rate_limited.
  • The connect_slack MCP tool is limited to 20 attempts per workspace per day (each attempt posts a test message to Slack). Over it: a tool error.

Because data refreshes at most hourly, one call every few minutes is plenty.

Endpoints

All paths are relative to https://api.getcalibri.com/api/public/v1. Email addresses and domain names in paths are trimmed and case-insensitive. Put @ in a path as %40 if your client does not do it for you.

GET/workspace

The workspace this key belongs to, and the key in use.

bash
curl -s https://api.getcalibri.com/api/public/v1/workspace \
  -H "Authorization: Bearer $CALIBRI_KEY"

Fields: id, name, timezone (string or null), plan, key: { id, label, keyPrefix, canWrite }. canWrite is true when the owner allowed changes for this key.

GET/mailboxes

All mailboxes, ordered by email.

bash
curl -s "https://api.getcalibri.com/api/public/v1/mailboxes?domain=example.com" \
  -H "Authorization: Bearer $CALIBRI_KEY"

Query parameters:

  • domain (optional): only mailboxes on that domain.
  • includeRevoked (optional, true or false, default false): revoked mailboxes are hidden from the list unless you pass true. A revoked mailbox can still be read by address.

Returns { mailboxes: Mailbox[], count }. A Mailbox has:

  • id, email, domain, provider, status.
  • ageBucket: lt_1m, 1_3m, 4_6m, gt_6m or null.
  • score: see the glossary. A mailbox with no cached score yet has score.status = "collecting".
  • trend: { delta7d, delta30d, direction } where direction is improving, declining, flat or insufficient_history. Null while collecting.
  • warmup: { status, progressPct }, status not_warmed_up, warming_up or warmed_up. Null while collecting.
  • metrics (null while collecting): windowDays (always 14), sends24h, sends7d, sendsAll, sendsCold, sendsWarmup, bounces, bounceRatePct, hardBounceRatePct, coldBounceRatePct, warmupBounceRatePct, blocklistListings, senderAuth (pass, fail, unknown). Bounce figures cover the last 14 days and every bounce counts, hard and soft.
  • quotaToday (or null): { newToday, totalToday, status }, status ok, warn, at_cap or over_cap.
  • placement: array of placement rows (see the glossary), one per provider tested.

GET/mailboxes/:email

One mailbox, same shape as above. 404 not_found if the address is not in this workspace.

GET/mailboxes/:email/history

Daily facts for one mailbox.

bash
curl -s "https://api.getcalibri.com/api/public/v1/mailboxes/ana%40example.com/history?days=30" \
  -H "Authorization: Bearer $CALIBRI_KEY"

days: integer from 1 to 90, default 30. Returns { entity: { type, id, label }, days, rows }, with rows ascending by day, each { day, version, facts }. facts is an object of that day's measurements; its keys can grow over time, so read the ones you need and ignore the rest.

GET/domains

All monitored domains, ordered by name. Returns { domains: Domain[], count }.

bash
curl -s https://api.getcalibri.com/api/public/v1/domains \
  -H "Authorization: Bearer $CALIBRI_KEY"

A Domain has:

  • id, fqdn, status, provider (google, microsoft, mixed or null), ageBucket.
  • placementTestsEnabled: true when weekly inbox placement tests are on for this domain. Read live, not from the score cache.
  • score: as for mailboxes, plus collectingReason (warming, no_data or null). A domain's score.status is the band of its score, the colour the Domains page shows. One weak inbox shows on that mailbox's own score.status, not on the domain.
  • mailboxes: { total, active, warmedUp } (or null).
  • sends24h (or null).
  • bounces (or null): windowDays (14), coldRatePct, warmupRatePct, combinedRatePct, coldSends, warmupSends, coldSampleMeaningful, warmupSampleMeaningful. A rate is null when its sample is too small to judge (fewer than 50 sends in the window and fewer than 10 bounces), the same gate the Domains page uses; the sends count is always there.
  • auth (or null): auditedAt, score, spfPresent, spfLookupCount, dkimState, dmarcPresent, dmarcPolicy, dmarcPct, mxPresent, customTrackingState, websiteRedirectVerdict.
  • dmarc7d (or null): directMessages, directSpfPct, directDkimPct, forwardedMessages.
  • blocklist (or null): activeListings, listings: [{ list, listedAt }].
  • postmaster (or null): statsDate, reputation (HIGH, MEDIUM, LOW, BAD or null), userReportedSpamRatio. Google Postmaster data, when available.
  • placement: placement rows.
  • recommendations: [{ severity: high|medium|low, signal, message }].

GET/domains/:fqdn

One domain, same shape. 404 not_found if it is not in this workspace.

GET/domains/:fqdn/placement

Recent completed inbox placement tests for a domain, newest first.

bash
curl -s "https://api.getcalibri.com/api/public/v1/domains/example.com/placement?limit=10" \
  -H "Authorization: Bearer $CALIBRI_KEY"

limit: integer from 1 to 50, default 10. Returns { fqdn, tests }. Each test: id, completedAt, trigger, senderEmail (or null) and results, an object keyed by provider (google, ms365; a provider is absent if it was not tested). Each result has total, inbox, spam, tab, filed, missing, band, inboxPct, spamPct.

GET/domains/:fqdn/history

Daily facts for one domain. Same parameters and shape as the mailbox history (days 1 to 90, default 30).

GET/alerts

Alerts fired in a window, newest first.

bash
curl -s "https://api.getcalibri.com/api/public/v1/alerts?since=7d&severity=critical,high" \
  -H "Authorization: Bearer $CALIBRI_KEY"

Query parameters:

  • since (default 7d): 24h, 7d (any number of hours or days) or an ISO date or timestamp. At most 30 days back.
  • severity: comma-separated list of critical, high, medium, low, info.
  • kind: comma-separated list of alert kinds, at most 10.
  • domain: only alerts for that domain.
  • acknowledged: true or false.
  • limit: integer from 1 to 200, default 50.

Returns { alerts, count, window: { since, now } }. Each alert: id, kind, severity, title, body (or null), domain (or null), firedAt, acknowledgedAt (or null). body is always null for bounce_classified alerts, whose text quotes raw bounce messages; the title still says what happened.

GET/reports/weekly

The workspace report the Calibri app renders: score, domain and mailbox totals, volume, movers and findings.

bash
curl -s "https://api.getcalibri.com/api/public/v1/reports/weekly?period=week" \
  -H "Authorization: Bearer $CALIBRI_KEY"

period: week or month, default month. The response is the report object, including tenant (id, name), period (start, end, days, label), score, domains, mailboxes, infrastructure and volume. Its summary field is the AI summary object (the same as aiSummary), not a string. Limited to 200 reads per workspace per day.

Field glossary

score: { composite, grade, status, computedAt }

  • composite: 0 to 100, or null when there is no score.
  • grade: A (85 and up), B (70 to 84), C (55 to 69), D (40 to 54), F (below 40), or unknown.
  • status: healthy, at_risk, action_required, burnt, insufficient_data or collecting.
    • collecting: Calibri has not cached a score for this mailbox or domain yet (new, or its first cycle has not run). It is returned, not omitted. composite is null and grade is unknown. Check again later.
    • insufficient_data: scored, but there is too little sending evidence to judge.
    • For a domain, status follows the score band: healthy at 85 and up, at_risk from 70 to 84, action_required below 70. A mailbox's status is its own evidence, so a single weak inbox can sit under a healthy domain.
  • computedAt: ISO timestamp of the cached score, or null while collecting. Mailbox scores refresh twice a day; domain health refreshes hourly.

placement row: { provider, band, testedAt, ageDays, stale, counts, inboxPct, spamPct }

  • provider: google (Gmail seed inboxes) or ms365 (Outlook seed inboxes).
  • band: inboxing, mixed, spam_heavy or inconclusive.
  • counts: total, inbox, spam, filed, missing, and tab when present (Gmail promotions or updates tabs).
  • inboxPct and spamPct: share of total that landed in the inbox or in spam, with one decimal. Null when total is 0.
  • ageDays: age of the test. stale is true when the test is older than 14 days.

Bounce metrics cover the last 14 days (windowDays: 14). Every bounce counts, hard and soft. bounceRatePct is bounces over sends in that window. On a domain, coldRatePct and warmupRatePct are null when the sample is too small (fewer than 50 sends and fewer than 10 bounces); coldSampleMeaningful and warmupSampleMeaningful say which.

Weekly report bands: domains.critical in /reports/weekly is the same band as action_required on a domain's score.status.

Timestamps are ISO 8601 in UTC. Days (day, statsDate) are YYYY-MM-DD.

MCP

Connect an AI assistant to the same data.

  • URL: https://api.getcalibri.com/mcp
  • Transport: Streamable HTTP, stateless (POST only; GET and DELETE return 405).
  • Auth: the same Authorization: Bearer cal_live_... header. Same rate limits as the REST API.
  • One JSON-RPC message per POST: batches (a JSON array) get 400 with error code -32600. Request bodies over 1 MiB get 413.
  • Server name: calibri. Ten tools read; three make changes and need a key with changes allowed.

Tools:

ToolArguments
get_workspacenone
list_mailboxesdomain?, includeRevoked?
get_mailboxemail
list_domainsnone
get_domainfqdn
get_placement_historyfqdn, limit? (1 to 50, default 10)
list_alertssince?, severity? (array), kind? (array), domain?, acknowledged?, limit? (1 to 200, default 50)
get_weekly_reportperiod? (week or month, default month)
get_historyentity (mailbox or domain), id (email or domain name), days? (1 to 90, default 30)
get_slack_setupnone
set_placement_tests (changes)domain, enabled (boolean)
connect_slack (changes)webhookUrl
disconnect_slack (changes)none

Each read tool returns the REST shape as structuredContent and as JSON text. Not found and bad input come back as tool errors (isError: true).

Tools that make changes or set up Slack:

  • get_slack_setup (read-only, any key): { connected, muted, alertsToSlack: { enabled, minSeverity, categories }, weeklyReportToSlack, settingsUrl, steps }. steps explains how to connect Slack: from the Slack card on the Connectors page, or by creating a Slack incoming webhook (api.slack.com/apps > Create New App > From scratch > Incoming Webhooks > On > Add New Webhook to Workspace > pick a channel > copy the URL) and giving it to connect_slack.
  • set_placement_tests: switches weekly inbox placement tests on or off for one domain, the same as the toggle on the Domains page. While on, Calibri sends real probe mail every week to Gmail and Outlook seed inboxes. Paid plans only: turning tests on in a Free workspace returns the error Inbox placement tests are on paid plans. Turning them off always works; a test already running still finishes. Returns { domain, placementTestsEnabled, changed }. Safe to repeat.
  • connect_slack: takes a Slack incoming webhook URL (https://hooks.slack.com/..., no other host is accepted). Calibri posts Calibri is connected. Deliverability alerts will post here. to the channel and saves the webhook only if Slack accepts it. It replaces any existing Slack connection; the previous channel gets a short notice that alerts moved, naming the key. Which alerts and reports go to Slack is still chosen in Calibri's Alerts settings; the result shows the current choice (alertsToSlack, weeklyReportToSlack). The URL is stored encrypted and never returned.
  • disconnect_slack: posts a short goodbye in the channel, then removes the Slack connection, the same as Disconnect on the Connectors page. Returns { connected: false, wasConnected }. Safe to repeat.

With a read-only key the three write tools return the error This key is read-only. The workspace owner can allow changes for this key in Connectors > API & MCP.

Connect:

  • Claude (claude.ai and desktop): Settings > Connectors > Add custom connector. URL https://api.getcalibri.com/mcp. Under Request headers add Authorization with the value Bearer <your key>.
  • Claude Code:
    bash
    claude mcp add --transport http calibri https://api.getcalibri.com/mcp --header "Authorization: Bearer <your key>"
  • Cursor: add to mcp.json:
    json
    {
      "mcpServers": {
        "calibri": {
          "url": "https://api.getcalibri.com/mcp",
          "headers": { "Authorization": "Bearer <your key>" }
        }
      }
    }

ChatGPT connectors need OAuth, which Calibri does not offer yet.

Errors

Errors are JSON: { "error": "<code>", "message": "<what to do>" }.

StatuserrorWhen
400bad_requestA parameter is invalid. The message names it, for example limit must be an integer from 1 to 50.
401unauthorizedMissing, invalid or revoked key. Includes WWW-Authenticate.
402subscription_requiredThe workspace has no active subscription. The body is exactly { error, message }.
404not_foundNo such mailbox or domain in this workspace.
429rate_limitedOver 120 requests per minute per key, 600 per minute per IP, or 200 weekly report reads per day. Honour retry-after.

Changelog

  • v1, 2026-11: first release. Ten read-only endpoints and nine MCP tools.
  • 2026-11-11: four MCP tools: get_slack_setup, and three that make changes with a key the owner allowed to (set_placement_tests, connect_slack, disconnect_slack). Domains gain placementTestsEnabled; the workspace key gains canWrite. The REST API stays read-only.
  • 2026-11-02: a domain's score.status is the band of its score, the same as the Domains page. Before, it was the worst mailbox's status, so an A-graded domain could read at_risk.
  • 2026-11-02: domain bounce rates are null when the sample is too small, with coldSampleMeaningful and warmupSampleMeaningful added, the same gate as the Domains page. Before, a domain with 13 cold sends could show a 277% cold bounce rate.