# SMS traffic metrics

Read the volume, delivery outcomes and cost of the SMS sent with your API tokens, per UTC day or month, with a breakdown by country, network and token, in a single call to GET /sms/metrics.

**Language:** en
**Audience:** developer
**TLDR:** GET /transactional/v1/sms/metrics?from=YYYY-MM-DD&to=YYYY-MM-DD returns totals, a zero-filled series per UTC day (up to 92 days) or month (granularity=month, up to 1100 days) and a breakdown by country, network and token, for all your API tokens or one (tokenId). Figures are aggregated, not real time: only charged messages, by charge date, test traffic excluded, cost in EUR. dataUpTo says how fresh they are; buckets from finalBefore on may still change. 10 requests per minute.
**Search keywords:** metrics, statistics, stats, usage, analytics, report, reporting, delivery rate, delivery stats, cost, spend, volume, consumption
**Related pages:** /a2p-messaging-api/http/dlrs, /a2p-messaging-api/http/rate-limits
**Docs index (every page):** https://staging-instasent-docs-nextjs.oscar-284.workers.dev/llms.txt
**This zone's index:** https://staging-instasent-docs-nextjs.oscar-284.workers.dev/a2p-messaging-api/llms-full.txt
**This page:** https://staging-instasent-docs-nextjs.oscar-284.workers.dev/a2p-messaging-api/http/metrics/ (HTML) · https://staging-instasent-docs-nextjs.oscar-284.workers.dev/a2p-messaging-api/http/metrics.md (Markdown)

[Delivery reports](/a2p-messaging-api/http/dlrs) tell you what happened to each message, one webhook call at a time. Metrics answer the aggregate questions in one request: how many messages you sent last week, how many were delivered, to which countries and networks, through which token, and what they cost. You get the figures without storing and counting every DLR yourself, which makes this the endpoint for usage dashboards, delivery-rate monitoring and cost reconciliation.

## Request

[`GET /transactional/v1/sms/metrics` - SMS traffic metrics of your API tokens over a window of UTC days or months.](/a2p-messaging-api/http/reference)

Authenticate with an `api_sms` token, the same one you send with (see [Authentication](/a2p-messaging-api/http/authentication)). The token you call with does not narrow the figures: any of your tokens reads the traffic of **all** your API tokens, deleted ones included, unless you pass `tokenId`.

```bash
# September 2026, one row per day
curl "https://api.instasent.com/transactional/v1/sms/metrics?from=2026-09-01&to=2026-09-30" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"

# January to September 2026, one row per month, one token only
curl "https://api.instasent.com/transactional/v1/sms/metrics?granularity=month&from=2026-01&to=2026-09&tokenId=66f1a2b3c4d5e6f7a8b9c0d1" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

### Query parameters

- `from` — `string`, required
  First day of the window, included, as `YYYY-MM-DD` in UTC. With `granularity=month`, `YYYY-MM` is accepted too, and a full date is taken as its whole month.
- `to` — `string`, required
  Last day of the window, included, in the same format as `from`. It must not be earlier than `from`.
- `granularity` — `string`, default: `day`
  Size of each bucket: `day` or `month`. With `day` the window can span at most **92 days**; with `month`, at most **1100 days** (about three years).
- `tokenId` — `string`
  Narrows the figures to one token. Use the `tokenId` returned in `breakdown.byToken`. A token that is unknown or sent nothing in the window returns zeros, not an error.

## What the figures count

- **Only charged messages.** A message that was not charged does not appear here, so these figures are your billed traffic, not every request you made. See [What is billed](/a2p-messaging-api/channels/sms/senders#what-is-billed) for the cases where a message is blocked and not charged.
- **By charge date, in UTC.** Each message lands in the UTC day (or month) it was charged, whatever your local time zone. A message charged at 00:30 on 1 October in Madrid (UTC+2) counts on 30 September.
- **Test traffic is left out.**
- **`cost` is what you were charged**, in EUR, rounded to four decimals.
- **Each message is counted once, under its current status**: `delivered`, `failed`, `error`, `sent`, `enqueued`, `expired` or `rejected`. They mean the same as in the [DLR status list](/a2p-messaging-api/http/dlrs#status-list); `enqueued` is a message still waiting to be handed to the carrier. Statuses without a counter of their own (such as `buffered` or `accepted`) only add to `messages`, so the status counters need not add up to `messages`.
- **`noDR` counts messages that have not received any delivery report.** It overlaps the status counters (a `sent` message with no report is in both), so never add it to them.

To compute a delivery rate, divide `delivered` by `messages`. On the most recent buckets that ratio can still rise as late reports arrive (see below).

## Freshness: `dataUpTo` and `finalBefore`

The figures are aggregated periodically, not computed in real time: a message you sent a minute ago is not in them yet. Two fields of every response tell you how far to trust each bucket.

- **`dataUpTo`** is the instant (ISO 8601, UTC) up to which charged traffic is included. It is `null` when that instant is not known at the time of the call; the figures are returned anyway.
- **`finalBefore`** is the first bucket that may still change, in the same format as the buckets (`YYYY-MM-DD` by day, `YYYY-MM` by month). Every bucket before it is final. Buckets from it on are provisional, because carriers keep sending delivery reports after the send, sometimes days later, and each one can move a message from `sent` to `delivered` or `failed`.

> **Tip**: If you copy the figures into your own store, keep the buckets before `finalBefore` for good and read the ones from `finalBefore` on again in a later call. Read `finalBefore` on every response instead of assuming a fixed number of days, and use `dataUpTo` to decide whether there is anything new to fetch.

## Response

A `200` returns the metrics in `entity`:

```json
{
  "entity": {
    "channelType": "sms",
    "granularity": "day",
    "from": "2026-09-28",
    "to": "2026-09-30",
    "timezone": "UTC",
    "dataUpTo": "2026-09-30T22:00:00+00:00",
    "finalBefore": "2026-09-29",
    "totals": { "messages": 2000, "parts": 2170, "delivered": 1881, "failed": 27, "error": 3, "sent": 73, "enqueued": 3, "expired": 5, "rejected": 5, "noDR": 79, "cost": 86.8 },
    "series": [
      { "date": "2026-09-28", "messages": 1200, "parts": 1310, "delivered": 1150, "failed": 18, "error": 2, "sent": 21, "enqueued": 0, "expired": 4, "rejected": 3, "noDR": 23, "cost": 52.4 },
      { "date": "2026-09-29", "messages": 0, "parts": 0, "delivered": 0, "failed": 0, "error": 0, "sent": 0, "enqueued": 0, "expired": 0, "rejected": 0, "noDR": 0, "cost": 0 },
      { "date": "2026-09-30", "messages": 800, "parts": 860, "delivered": 731, "failed": 9, "error": 1, "sent": 52, "enqueued": 3, "expired": 1, "rejected": 2, "noDR": 56, "cost": 34.4 }
    ],
    "breakdown": {
      "byCountry": [
        { "country": "ES", "messages": 1500, "parts": 1630, "delivered": 1412, "failed": 20, "error": 2, "sent": 55, "enqueued": 2, "expired": 4, "rejected": 3, "noDR": 59, "cost": 65.2 },
        { "country": "PT", "messages": 500, "parts": 540, "delivered": 469, "failed": 7, "error": 1, "sent": 18, "enqueued": 1, "expired": 1, "rejected": 2, "noDR": 20, "cost": 21.6 }
      ],
      "byNetwork": [
        { "network": "21407", "messages": 900, "parts": 980, "delivered": 848, "failed": 12, "error": 1, "sent": 33, "enqueued": 1, "expired": 3, "rejected": 2, "noDR": 35, "cost": 39.2 },
        { "network": "21401", "messages": 600, "parts": 650, "delivered": 564, "failed": 8, "error": 1, "sent": 22, "enqueued": 1, "expired": 1, "rejected": 1, "noDR": 24, "cost": 26 },
        { "network": "26806", "messages": 500, "parts": 540, "delivered": 469, "failed": 7, "error": 1, "sent": 18, "enqueued": 1, "expired": 1, "rejected": 2, "noDR": 20, "cost": 21.6 }
      ],
      "byToken": [
        { "tokenId": "66f1a2b3c4d5e6f7a8b9c0d1", "name": "Production", "deleted": false, "messages": 1800, "parts": 1950, "delivered": 1690, "failed": 25, "error": 3, "sent": 69, "enqueued": 3, "expired": 5, "rejected": 4, "noDR": 75, "cost": 78 },
        { "tokenId": "65a0b1c2d3e4f5a6b7c8d9e0", "name": "Old integration", "deleted": true, "messages": 200, "parts": 220, "delivered": 191, "failed": 2, "error": 0, "sent": 4, "enqueued": 0, "expired": 0, "rejected": 1, "noDR": 4, "cost": 8.8 }
      ]
    }
  }
}
```

In this example the 28th is final, while the 29th and 30th may still change. The 29th had no traffic and still has its row.

- `channelType` — `string`
  Always `sms`.
- `granularity` — `string`
  `day` or `month`, as requested.
- `from` — `string`
  First bucket of the window: `YYYY-MM-DD` by day, `YYYY-MM` by month (even if you passed a full date).
- `to` — `string`
  Last bucket of the window, in the same format.
- `timezone` — `string`
  Always `UTC`: the zone every bucket is cut in.
- `dataUpTo` — `string | null`
  The instant up to which charged traffic is included, ISO 8601. `null` when unknown.
- `finalBefore` — `string`
  The first bucket that may still change, in the bucket format; every earlier bucket is final.
- `totals` — `object`
  The [counters](#counters) of the whole window.
- `series` — `object[]`
  Every bucket of the window in time order, including those without traffic, which come at zero. Each item is a `date` (bucket format) plus the [counters](#counters).
- `breakdown` — `object`
  The whole window split three ways. Rows are sorted by `messages`, busiest first, and each row carries its label plus the [counters](#counters).
  
  - `byCountry` — `object[]`
    `country`: ISO 3166-1 alpha-2 code of the destination (`ES`), or `null` when unknown.
  - `byNetwork` — `object[]`
    `network`: the destination mobile network as its MCC-MNC code in one string (`21407`), or `null` when unknown.
  - `byToken` — `object[]`
    `tokenId` (the value to pass as `tokenId`; it can be `null` for traffic not tied to a token), `name` (the token's name) and `deleted` (`true` when the token no longer exists; its past traffic still counts).

### Counters

The same eleven counters appear in `totals`, in every `series` item and in every breakdown row.

- `messages` — `integer`
  Charged messages.
- `parts` — `integer`
  Charged message parts. A long message is split into several parts, each charged, and Unicode characters lower the length at which that happens.
- `delivered` — `integer`
  Messages whose current status is `delivered`.
- `failed` — `integer`
  Messages whose current status is `failed`.
- `error` — `integer`
  Messages whose current status is `error`.
- `sent` — `integer`
  Messages whose current status is `sent`: sent, with no final delivery report yet.
- `enqueued` — `integer`
  Messages whose current status is `enqueued`: not yet handed to the carrier.
- `expired` — `integer`
  Messages whose current status is `expired`.
- `rejected` — `integer`
  Messages whose current status is `rejected`.
- `noDR` — `integer`
  Messages that have not received any delivery report. Overlaps the status counters.
- `cost` — `number`
  Amount charged, in EUR, rounded to four decimals.

## Errors and limits

- **`400 Bad Request`** when `from` or `to` is missing or is not a valid date in the expected format (an impossible date such as `2026-02-30` included), when `from` is after `to`, when the window is wider than the granularity allows, or when `granularity` or `tokenId` is not a valid value. Fix the parameters; retrying the same request does not help.
- **`404 Not Found`** when your account has no A2P Messaging API project. An unknown `tokenId` is not a `404`: it returns zeros.
- **`401`, `403` and `429`** behave as everywhere else in the API; see [Errors](/a2p-messaging-api/http/errors).
- **Rate limit: 10 requests per 60 seconds**, counted like the rest of the API's limits (see [Rate limits](/a2p-messaging-api/http/rate-limits)). Since the figures only refresh periodically, calling more often returns nothing new: cache the response and check `dataUpTo` before fetching again.

## What's next

- **[Receiving DLRs](/a2p-messaging-api/http/dlrs)** — the per-message status, pushed to your webhook as it changes.
- **[SMS senders](/a2p-messaging-api/channels/sms/senders)** — what is billed when a message is blocked.
- **[Rate limits](/a2p-messaging-api/http/rate-limits)** — per-endpoint limits and the `X-RateLimit-*` headers.
- **[API Reference](/a2p-messaging-api/http/reference)** — the full schema of `GET /sms/metrics`.

---

This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://staging-instasent-docs-nextjs.oscar-284.workers.dev/llms.txt — start there for full context.
