---
title: "Campaign results"
method: GET
path: "/campaigns/{id}/metrics"
tags: ["Campaigns"]
---

# Campaign results

`GET /campaigns/{id}/metrics`

## Request

- Base URL: `https://sending.dev/api/v1`
- URL: `https://sending.dev/api/v1/campaigns/{id}/metrics`
- Auth: HTTP bearer

Deliveries, unique opens and clicks, unsubscribes, hard and soft bounces, complaints and derived rates. Opens count only events classified as human (Apple MPP and proxies excluded): compare campaigns on clicks, and on CONTENT clicks (`contentClicks`): a click rate made of logo clicks is not engagement. Optional blocks: `?timeseries=1|day|hour` (event series), `?links=1` (clicks per link, with role), `?conversions=1|last|first` (attributed conversions, with the list and the link that converted). `revenue` is always there: what the campaign sold, as orders attributed to its messages by the last click on the content within the workspace window (never a total across currencies). Requires authentication only (any valid API key).

## Path parameters

- `id` string, required: Campaign ID.

## Query parameters

- `timeseries` string: `1` or `day` for the daily series, `hour` for the hourly one.
- `links` string: `1` to include `links`.
- `conversions` string: `1` or `last` for last touch, `first` for first touch.

## Response `200`

Campaign metrics.

- CampaignMetrics
  - `id` string, uuid, required
  - `recipients` integer, required
  - `queued` integer, required
  - `delivered` integer, required
  - `sent` integer, required
  - `failed` integer, required
  - `uniqueOpens` integer, required: Distinct messages with at least one human open.
  - `rawOpens` integer, required: Open events of every class (human, proxy, scanner): never a KPI.
  - `openClassification` object, required
    - `human` integer, required
    - `machine` integer, required
    - `prefetch` integer, required
    - `bot` integer, required
  - `uniqueClicks` integer, required
  - `contentClicks` integer, required: Messages with a human click on content: excludes logo, images, chrome and social links.
  - `unsubscribes` integer, required
  - `hardBounces` integer, required
  - `softBounces` integer, required
  - `complaints` integer, required
  - `openRate` number, nullable, required: Percentage 0-100 of delivered; null without deliveries.
  - `clickRate` number, nullable, required
  - `contentClickRate` number, nullable, required
  - `clickToOpenRate` number, nullable, required: Unique clicks / human opens, capped at 100.
  - `unsubscribeRate` number, nullable, required
  - `bounceRate` number, nullable, required: Hard + soft, over recipients.
  - `complaintRate` number, nullable, required
  - `timeseries` object[]: Only buckets with events: gaps are filled by whoever draws the chart.
    - `date` string, required: `YYYY-MM-DD` for a daily bucket, `YYYY-MM-DDTHH:00:00Z` for an hourly one.
    - `bucket` 'hour' | 'day', required
    - `opens` integer, required
    - `machineOpens` integer, required
    - `clicks` integer, required
    - `unsubscribes` integer, required
    - `bounces` integer, required
  - `links` LinkStat[]: With `?links=1`: clicks per link, most clicked first.
    - `url` string, required: The most clicked raw variant, the recognizable one.
    - `canonicalUrl` string, required
    - `role` string, nullable, required: logo | image | cta | text | social | video | chrome, or null if not classified.
    - `isContent` boolean, required
    - `uniqueClicks` integer, required: Distinct messages with a human click.
    - `totalClicks` integer, required
    - `machineClicks` integer, required: Scanner and proxy clicks: reported, not counted.
    - `firstClickAt` string, date-time, nullable, required
    - `lastClickAt` string, date-time, nullable, required
    - `share` number, nullable, required: Share 0-100 of the scope's unique human clicks.
  - `conversions` object: With `?conversions=1|last|first`.
    - `attribution` 'first' | 'last', required
    - `lookbackDays` integer, required
    - `total` integer, required
    - `contacts` integer, required
    - `revenueCents` integer, required
    - `currency` string, nullable, required: `null` with mixed currencies: the total cannot be summed.
    - `byName` object[], required
      - `name` string, required
      - `conversions` integer, required
      - `revenueCents` integer, required
    - `byLink` object[], required
      - `url` string, required
      - `conversions` integer, required
      - `contacts` integer, required
      - `revenueCents` integer, required
    - `recent` ConversionRow[], required: The latest 50.
      - `id` string, uuid, required
      - `name` string, required
      - `source` string, required
      - `valueCents` integer, required
      - `currency` string, required
      - `occurredAt` string, date-time, required
      - `contactId` string, uuid, nullable, required
      - `contactEmail` string, nullable, required
      - `touchUrl` string, nullable, required: Clicked link (canonical) it is attributed to.
      - `touchAt` string, date-time, nullable, required
      - `nodeId` string, nullable, required
      - `secondsFromTouch` integer, nullable, required
  - `revenue` RevenueReport, required: Store and API orders attributed to the messages: the last click on the content (not the logo, never an open) within `attribution.windowDays` days before the order. Test runs and cancelled orders excluded.
    - `orders` integer, required: Attributed orders, cancelled excluded. A count across all currencies.
    - `revenue` number, nullable, required: Gross total of the attributed orders; `null` with more than one currency (never summed).
    - `refunded` number, nullable, required: Refunds on those orders, declared apart and never subtracted from `revenue`.
    - `currency` string, nullable, required: The only currency involved; `null` with zero orders or more than one currency.
    - `averageOrderValue` number, nullable, required
    - `revenuePerRecipient` number, nullable, required: `revenue` / `delivered`; `null` with no orders, no deliveries or more than one currency.
    - `byCurrency` object[], required: One row per currency, highest revenue first: the numbers to read when there is more than one.
      - `currency` string, required
      - `revenue` number, required
      - `refunded` number, required
      - `orders` integer, required
      - `averageOrderValue` number, nullable, required
    - `attribution` object, required
      - `model` 'last_content_click', required
      - `windowDays` integer, required: Workspace attribution window: the last content click within these days before the order.

## Other responses

- `401`: Missing or invalid token.
- `403`: Insufficient scope.
- `404`: Resource not found.

---

[API](https://skmtc.dev/sending/apis/sending-dev-api.md) · [All operations](https://skmtc.dev/sending/apis/sending-dev-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sending/apis/sending-dev-api/revisions/9fe78d2918a1?raw)
