Metrics

Send funnel

GET
/api/v1/metrics

Totals plus per-template and per-day breakdowns with rates and deliverability warnings. Rates are null (not 0) when the denominator is zero, so "no data" stays distinguishable from "0%". Opens and clicks are rated against DELIVERED, not sent. Defaults to the last 7 days.

Authorization

bearerAuth write
headerAuthorizationBearer <token>

An API key from the dashboard under Settings → API keys, sent as Authorization: Bearer aem_….

Authorization has two independent axes.

The scope is ranked — a key satisfies any requirement at or below its own tier:

  • read — see messages, contacts and metrics. Changes nothing, and cannot send.
  • write — everything read does, plus managing templates, contacts, automations, segments and suppressions. This is what editing a template needs.
  • admin — everything write does, plus sending configuration: domains, senders, webhook registration, kill switch, daily cap, brand.

There is no approve scope. It was a rung once; it is not one now, and a key requested with it is rejected.

The approval grant (can_approve) is a separate boolean, not a rung. Delivering mail to a real inbox needs write and the grant. Keeping them on separate axes is what makes the review gate a control rather than a convention: a key that may propose is not automatically a key that may approve its own proposal.

Give your application the lowest tier that works. Most need write and the grant — the dashboard mints that combination as Send + manage; Manage only is the same rung with the grant withheld.

Scope: write

Query Parameters

since?string

ISO 8601 start (default: 7 days ago).

Formatdate-time
until?string

ISO 8601 end (default: now).

Formatdate-time

Response Body

Metrics.

application/json
  1. response
period?
totals?
by_template?

Keyed by template slug.

by_day?

Keyed by YYYY-MM-DD.

warnings?array<string>

Deliverability tripwires, phrased as actions.

truncated?boolean

True when the window hit the query cap — narrow it for exact numbers. Always false when source is daily_rollup, which is pre-aggregated and has no cap to hit.

source?string

Which engine answered. raw reads the message log directly and is exact to the second. daily_rollup is used once since reaches past the 30-day log retention window, past which the individual rows no longer exist — it is aggregated to whole UTC days, so period reports the snapped window rather than the one you asked for, and a warning says so. One engine always answers the whole range; the two are never mixed.

Value in"raw""daily_rollup"
Example Request
curl -X GET 'https://sendandretain.com/api/v1/metrics'
Example Response
{  "period": {    "since": "2019-08-24T14:15:22Z",    "until": "2019-08-24T14:15:22Z"  },  "totals": {},  "by_template": {},  "by_day": {},  "warnings": [    "Complaint rate above 0.08% — stop and investigate before sending more."  ],  "truncated": true,  "source": "raw"}