WarmblyDocs
Endpoint reference

Analytics and audit

Read dashboard, deliverability, warmup, campaign, account, and usage analytics, plus your organization's audit trail.

The analytics endpoints expose the same rollups that power the dashboard: an org-wide overview, deliverability posture, warmup progress, per-campaign performance (with daily and hourly breakdowns and side-by-side comparison), mailbox health, and account usage. The audit endpoint returns your organization's activity trail ("who did what, when, from where"). All of these are read-only.

Every analytics route shares one auth gate: Scope READ_ANALYTICS · Org permission view_analytics. The audit route uses the same org permission with a dedicated scope. See permissions for the scope reference and authentication for how to present credentials.

Dates are parsed as YYYY-MM-DD unless noted. Errors follow the standard {error, message, code, request_id} envelope documented in error codes.

Get dashboard analytics

GET /analytics/dashboard

Returns the main dashboard overview for the active organization: aggregate stats, recent activity, top campaigns, account health, and a daily trend series. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
periodquerystringOne of 7d, 30d, 90d. Defaults to 7d; any other value falls back to 7d.

Response

{
  "period": "7d",
  "overall_stats": {
    "total_emails_sent": 1240,
    "total_opens": 612,
    "machine_opens": 88,
    "total_clicks": 143,
    "total_replies": 57,
    "total_bounces": 9,
    "open_rate": 49.35,
    "click_rate": 11.53,
    "reply_rate": 4.6,
    "bounce_rate": 0.73,
    "active_campaigns": 4,
    "active_accounts": 12
  },
  "recent_activity": [
    {
      "type": "replied",
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "campaign_name": "Q2 outbound",
      "contact_email": "[email protected]",
      "contact_id": "c0ffee00-0000-0000-0000-000000000002",
      "timestamp": "2026-06-11T14:02:11Z"
    }
  ],
  "top_campaigns": [
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "name": "Q2 outbound",
      "status": "active",
      "emails_sent": 820,
      "open_rate": 51.2,
      "click_rate": 12.1,
      "reply_rate": 5.0
    }
  ],
  "account_health": {
    "total_accounts": 12,
    "healthy_accounts": 10,
    "warning_accounts": 1,
    "error_accounts": 1
  },
  "daily_trend": [
    { "date": "2026-06-05", "sent": 160, "opens": 79, "clicks": 18, "replies": 7 }
  ]
}

Get deliverability dashboard

GET /analytics/deliverability

Returns the organization's deliverability posture for a time window: bounce, complaint, open, click, and reply counts and rates, suppression and dead-letter pressure, reply-intent breakdown, seed inbox-placement, an overall health band (from the documented thresholds), a daily timeseries, and per-mailbox and per-campaign breakdowns. Requires an organization context. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
fromquerystringWindow start as an RFC 3339 timestamp. Defaults to 7 days ago (UTC).
toquerystringWindow end as an RFC 3339 timestamp. Defaults to now (UTC).

Response

{
  "from": "2026-06-05T00:00:00Z",
  "to": "2026-06-12T00:00:00Z",
  "events_total": 1380,
  "bounce_count": 9,
  "complaint_count": 1,
  "unsubscribe_count": 4,
  "reply_count": 57,
  "open_count": 612,
  "click_count": 143,
  "suppressed_recipients": 21,
  "dlq_pending": 0,
  "intent_positive": 18,
  "intent_negative": 6,
  "intent_out_of_office": 11,
  "intent_question": 9,
  "intent_neutral": 13,
  "emails_sent": 1240,
  "bounce_rate": 0.73,
  "complaint_rate": 0.08,
  "open_rate": 49.35,
  "click_rate": 11.53,
  "reply_rate": 4.6,
  "spam_placement_rate": 6.5,
  "inbox_placement_rate": 93.5,
  "placement_samples": 40,
  "band": "healthy",
  "timeseries": [
    {
      "date": "2026-06-05",
      "sent": 160,
      "bounces": 1,
      "complaints": 0,
      "opens": 79,
      "clicks": 18,
      "replies": 7,
      "unsubscribes": 1
    }
  ],
  "by_mailbox": [
    {
      "email_account_id": "a0a1a2a3-0000-0000-0000-000000000003",
      "email": "[email protected]",
      "sent": 420,
      "bounces": 2,
      "complaints": 0,
      "bounce_rate": 0.48,
      "complaint_rate": 0.0,
      "band": "healthy"
    }
  ],
  "by_campaign": [
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "name": "Q2 outbound",
      "sent": 820,
      "bounces": 5,
      "complaints": 1,
      "bounce_rate": 0.61,
      "complaint_rate": 0.12,
      "band": "watch"
    }
  ]
}

spam_placement_rate and inbox_placement_rate are omitted when there are no seed samples in the window.

Get warmup analytics

GET /analytics/warmup

Returns warmup send and reply statistics over a date range, with a summary and per-day series. Optionally scoped to a single mailbox. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
fromquerystringRequired. Range start (YYYY-MM-DD).
toquerystringRequired. Range end (YYYY-MM-DD).
email_idquerystring (uuid)Optional. Limit to one email account. Invalid UUIDs are ignored.

Response

{
  "email_account_id": "a0a1a2a3-0000-0000-0000-000000000003",
  "email": "",
  "date_range": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-12T00:00:00Z"
  },
  "summary": {
    "total_sent": 210,
    "total_replied": 84,
    "average_daily": 17.5,
    "reply_rate": 40.0,
    "target_progress": 0,
    "days_active": 12
  },
  "daily_stats": [
    { "date": "2026-06-01", "emails_sent": 12, "emails_replied": 5, "target_volume": 12 }
  ]
}

email_account_id is the zero UUID when no email_id filter is supplied.

Get campaign analytics

GET /analytics/campaigns/:id

Returns a single campaign's performance summary plus per-sequence-step stats. The campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Campaign id.

Response

{
  "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
  "name": "Q2 outbound",
  "status": "active",
  "date_range": { "from": "0001-01-01T00:00:00Z", "to": "0001-01-01T00:00:00Z" },
  "summary": {
    "total_contacts": 500,
    "emails_sent": 820,
    "emails_pending": 60,
    "unique_opens": 410,
    "machine_opens": 52,
    "unique_clicks": 99,
    "replies": 41,
    "bounces": 5,
    "unsubscribes": 3,
    "open_rate": 50.0,
    "click_rate": 12.07,
    "reply_rate": 5.0,
    "bounce_rate": 0.61
  },
  "steps": [
    {
      "step_id": "5e9e0001-0000-0000-0000-000000000004",
      "name": "Intro",
      "position": 1,
      "emails_sent": 500,
      "opens": 260,
      "clicks": 61,
      "replies": 28,
      "bounces": 3
    }
  ]
}

machine_opens is the subset of unique_opens from automated fetchers (Apple MPP prefetch, UA-less clients); human opens are unique_opens minus machine_opens.

Get campaign daily stats

GET /analytics/campaigns/:id/daily

Returns per-day send, open, click, and reply counts for one campaign over a date range. The campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Campaign id.
fromquerystringRequired. Range start (YYYY-MM-DD).
toquerystringRequired. Range end (YYYY-MM-DD).

Response

The series is returned under a data envelope.

{
  "data": [
    { "date": "2026-06-05", "sent": 120, "opens": 61, "clicks": 14, "replies": 6 },
    { "date": "2026-06-06", "sent": 110, "opens": 58, "clicks": 12, "replies": 5 }
  ]
}

Get campaign hourly stats

GET /analytics/campaigns/:id/hourly

Returns per-hour stats for one campaign on a single day. The campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Campaign id.
datequerystringDay to report (YYYY-MM-DD). Defaults to today.

Response

The series is returned under a data envelope, with the resolved date echoed back. Each item's hour is 0-23.

{
  "data": [
    { "hour": 9, "sent": 22, "opens": 11, "clicks": 3, "replies": 1 },
    { "hour": 10, "sent": 30, "opens": 16, "clicks": 4, "replies": 2 }
  ],
  "date": "2026-06-11"
}

Compare campaigns

GET /analytics/campaigns/compare

Returns side-by-side performance for up to 10 campaigns over a date range. Every requested campaign must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idsquerystringRequired. Comma-separated campaign UUIDs. Invalid entries are dropped; the list is capped at 10. At least one valid id is required.
fromquerystringRequired. Range start (YYYY-MM-DD).
toquerystringRequired. Range end (YYYY-MM-DD).

Response

{
  "campaigns": [
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "name": "Q2 outbound",
      "status": "active",
      "emails_sent": 820,
      "open_rate": 50.0,
      "click_rate": 12.07,
      "reply_rate": 5.0,
      "bounce_rate": 0.61
    },
    {
      "campaign_id": "b1f2c3d4-0000-0000-0000-000000000005",
      "name": "Reactivation",
      "status": "paused",
      "emails_sent": 410,
      "open_rate": 44.1,
      "click_rate": 9.8,
      "reply_rate": 3.4,
      "bounce_rate": 1.2
    }
  ],
  "period": {
    "from": "2026-06-01T00:00:00Z",
    "to": "2026-06-12T00:00:00Z"
  }
}

List account statuses

GET /analytics/accounts

Returns the health and usage status of every email account the caller owns. Accounts whose status fails to build are skipped. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

Response

The list is returned under a data envelope (no cursor; all of the caller's accounts are included). Each item has the same shape as get account status.

{
  "data": [
    {
      "id": "a0a1a2a3-0000-0000-0000-000000000003",
      "email": "[email protected]",
      "provider": "google",
      "status": "active",
      "last_synced_at": "2026-06-12T08:00:00Z",
      "health": { "status": "healthy", "score": 100, "issues": [] },
      "errors": [],
      "daily_usage": {
        "date": "2026-06-12",
        "campaign_sent": 18,
        "campaign_limit": 50,
        "warmup_sent": 22,
        "warmup_limit": 40
      },
      "in_campaign": true
    }
  ]
}

Get account status

GET /analytics/accounts/:id

Returns the detailed status for one email account: a combined health score (folding in warmup-pool reputation), active errors, today's usage, warmup status, and warmup-pool health. The account must belong to the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
idpathstring (uuid)Email account id.

Response

{
  "id": "a0a1a2a3-0000-0000-0000-000000000003",
  "email": "[email protected]",
  "provider": "google",
  "status": "active",
  "last_synced_at": "2026-06-12T08:00:00Z",
  "health": {
    "status": "warning",
    "score": 90,
    "issues": ["Warmup reputation needs watching"]
  },
  "errors": [
    {
      "id": "e1e1e1e1-0000-0000-0000-000000000006",
      "error_code": "IMAP_AUTH",
      "severity": "WARNING",
      "title": "Mailbox reconnect recommended",
      "message": "Token nearing expiry",
      "created_at": "2026-06-11T22:14:00Z"
    }
  ],
  "daily_usage": {
    "date": "2026-06-12",
    "campaign_sent": 18,
    "campaign_limit": 50,
    "warmup_sent": 22,
    "warmup_limit": 40
  },
  "warmup_status": {
    "enabled": true,
    "paused": false,
    "started_at": "2026-05-20T00:00:00Z",
    "current_volume": 22,
    "target_volume": 33,
    "max_volume": 40,
    "reply_rate": 35,
    "days_active": 23
  },
  "warmup_health": {
    "state": "watch",
    "score": 78,
    "spam_score": 6,
    "evaluated_at": "2026-06-12T06:00:00Z"
  },
  "in_campaign": true
}

warmup_status is present only when warmup has ever been enabled; warmup_health is present only when the mailbox is in a warmup pool. in_campaign reports whether the mailbox currently backs a live campaign.

Get usage overview

GET /analytics/usage

Returns account, campaign, contact, and API usage counters for the caller. Auth: Scope READ_ANALYTICS · Org permission view_analytics.

ParameterInTypeDescription
periodquerystringOne of day, week, month. Defaults to day; any other value falls back to day.

Response

{
  "user_id": "11111111-0000-0000-0000-000000000007",
  "period": "day",
  "email_accounts": { "total": 12, "active": 11, "in_warmup": 8, "with_errors": 1 },
  "campaigns": { "total": 9, "active": 4, "paused": 2, "draft": 3, "emails_sent": 12400 },
  "contacts": { "total": 8200, "subscribed": 8050, "added_today": 120 },
  "api": { "total_calls": 0, "daily_limit": 50000, "top_endpoints": [] }
}

List audit logs

GET /audit-logs

Returns the organization-wide activity trail for the caller's current organization ("who did what, when, from where"). The organization is always taken from the session and never from a client parameter, so one organization can never read another's trail. Auth: Scope READ_AUDIT_LOGS · Org permission view_analytics.

ParameterInTypeDescription
limitqueryintPage size. Defaults to 50; must be between 10 and 200 or a 400 is returned.
cursorquerystring (uuid)Opaque cursor from pagination.next_cursor. Invalid cursors return 400.
actor_idquerystring (uuid)Filter to a single acting member.
entity_idquerystring (uuid)Filter to a single entity.
entity_typequerystringFilter by entity type (for example campaign, contact, email_account, api_key, webhook).
actionquerystringFilter by action (for example create, update, delete, send, revoke).
datequerystringSingle-day filter (YYYY-MM-DD), expanded to that whole UTC day.
start_datequerystringRange start. RFC 3339 or YYYY-MM-DD. Overrides date.
end_datequerystringRange end. RFC 3339 or YYYY-MM-DD. Overrides date.

Response

A data plus pagination envelope with an opaque cursor. actor is null when the acting user has since been deleted; entity_id, changes, and metadata are omitted when empty.

{
  "data": [
    {
      "id": "9c9c9c9c-0000-0000-0000-000000000008",
      "org_id": "0a0a0a0a-0000-0000-0000-000000000009",
      "user_id": "11111111-0000-0000-0000-000000000007",
      "actor": {
        "id": "11111111-0000-0000-0000-000000000007",
        "first_name": "Ada",
        "last_name": "Lovelace",
        "email": "[email protected]"
      },
      "action_date": "2026-06-12T08:14:00Z",
      "action": "update",
      "entity_type": "campaign",
      "entity_id": "b1f2c3d4-0000-0000-0000-000000000001",
      "ip_address": "203.0.113.10",
      "user_agent": "Mozilla/5.0",
      "changes": { "status": "paused" },
      "timestamp": "2026-06-12T08:14:00Z"
    }
  ],
  "pagination": {
    "next_cursor": "c1_b3BhcXVlLWN1cnNvcg",
    "has_more": true
  }
}

Secret values (API key material, webhook secrets, passwords) are never recorded in changes or metadata; the trail records only that a field changed.

On this page