---
title: "Get outbound SMS statistics by error code"
method: GET
path: "/v1/sms/stats/error-codes"
tags: ["sms-stats"]
---

# Get outbound SMS statistics by error code

`GET /v1/sms/stats/error-codes`

## Request

- Base URL: `https://us1.platform.bird.com`
- URL: `https://us1.platform.bird.com/v1/sms/stats/error-codes`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `bird_session`

Returns aggregate delivery and latency statistics grouped by normalized failure reason for the requested period. The grouping key matches the `error_code` filter on the message list, so each row maps directly to the affected messages rather than a raw carrier code. Rows are ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, hard maximum 200).

Rows use send-time attribution. A delivery confirmed during the period for a message accepted earlier counts against the earlier period. A recent period therefore under-reports `delivered` while delivery reports are still arriving, and its counts grow as reports arrive. The maximum window is 365 days; requesting a longer range returns 422.

## Query parameters

- `from` string, date
- `to` string, date
- `timezone` string
- `sort` 'accepted' | 'sent' | 'delivered' | 'undelivered' | 'failed' | 'rejected' | 'expired': Metric to rank breakdown rows by, applied descending. Shared by the breakdowns whose rows carry lifecycle counts only, with no derived rates to sort on.
- `limit` integer
- `include_trend` boolean
- `trend_grain` 'daily' | 'hourly': Bucket grain for a stats trend series.

## Headers

- `X-Workspace-Id` string
- `X-Organization-Id` string

## Response `200`

Per-error-code breakdown for the requested period.

- SMSStatsByErrorCodeResponse: Per-error-code breakdown for the requested period, ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, max 200).
  - `period` SMSStatsSummaryPeriod, required: The window the server actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request's bounds.
    - `from` string, required: Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.
    - `to` string, required: Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.
    - `data_as_of` string, date-time, nullable: Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.
  - `data` SMSErrorCodeStatsPoint[], required: Error-code breakdown rows, ranked by the `sort` metric (default `failed`) descending. Empty when no delivery failures occurred in the period.
    - `error_code` string, required: Standardized failure reason: - `invalid_destination`: The number is unassigned, ported out, or malformed. - `unreachable`: The handset is off or outside coverage. - `blocked_by_carrier`: The carrier filtered the message. - `blocked_by_fraud_protection`: Bird fraud protection blocked suspected SMS pumping. - `blocked_by_recipient`: The recipient device blocked the sender. - `landline_unreachable`: The destination is a landline that does not accept SMS. - `content_rejected`: The carrier rejected the content. - `sender_unregistered`: The sender is not registered for the destination. - `recipient_opted_out`: The recipient is on a suppression list. - `provider_unavailable`: The provider remained unavailable after retries. - `insufficient_balance`: The workspace wallet could not fund the send. - `unknown`: The failure could not be classified. This is an open enum. Accept unrecognized values.
    - `delivery` SMSDeliveryStats, required: SMS lifecycle counts and rates for a whole period or breakdown. Counts use the message send time, so a later delivery stays attributed to the bucket in which the message was sent. Rates are null when their denominator is zero.
      - `accepted` integer, required: Distinct messages accepted for sending after admission checks. This is the denominator for `delivery_rate` and `failure_rate`.
      - `sent` integer, required: Distinct messages handed off to the carrier for delivery.
      - `delivered` integer, required: Distinct messages the carrier confirmed as delivered to the handset.
      - `undelivered` integer, required: Distinct messages the carrier reported as not delivered.
      - `failed` integer, required: Distinct messages that failed during sending.
      - `rejected` integer, required: Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure.
      - `expired` integer, required: Distinct messages that could not be delivered within their validity window and expired.
      - `delivery_rate` number, nullable, required: Share of accepted messages that were delivered, computed as `delivered / accepted`. Null when no messages were accepted in scope.
      - `failure_rate` number, nullable, required: Share of accepted messages that ultimately failed, computed as `(undelivered + failed + expired) / accepted`. Null when no messages were accepted in scope.
    - `latency` SMSLatencyStats, required: Latency percentiles in milliseconds for the requested scope: - `processing`: From acceptance to carrier handoff. - `delivery`: From carrier handoff to delivery confirmation. - `total`: From acceptance to delivery confirmation. Each family is omitted when no qualifying event contributes a measurement. Individual percentiles can also be null.
      - `processing` SMSLatencyQuantiles: Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.
        - `p50_ms` integer, nullable, required: Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.
        - `p95_ms` integer, nullable, required: 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
        - `p99_ms` integer, nullable, required: 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
      - `delivery` SMSLatencyQuantiles: Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.
        - `p50_ms` integer, nullable, required: Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.
        - `p95_ms` integer, nullable, required: 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
        - `p99_ms` integer, nullable, required: 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
      - `total` SMSLatencyQuantiles: Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.
        - `p50_ms` integer, nullable, required: Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.
        - `p95_ms` integer, nullable, required: 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
        - `p99_ms` integer, nullable, required: 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
    - `trend` SMSStatsPoint[]: Per-bucket lifecycle counts for this error code, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.
      - `bucket` string, required: The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.
      - `delivery` SMSDeliveryCounts, required: SMS lifecycle counts for a time bucket. Counts use the message send time, so a message accepted on Monday and delivered on Tuesday counts in Monday's bucket. Rates are available only for whole periods and breakdowns.
        - `accepted` integer, required: Distinct messages accepted for sending after admission checks.
        - `sent` integer, required: Distinct messages handed off to the carrier for delivery.
        - `delivered` integer, required: Distinct messages the carrier confirmed as delivered to the handset.
        - `undelivered` integer, required: Distinct messages the carrier reported as not delivered.
        - `failed` integer, required: Distinct messages that failed during sending.
        - `rejected` integer, required: Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure.
        - `expired` integer, required: Distinct messages that could not be delivered within their validity window and expired.
  - `total` integer, required: Total number of distinct error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more.

## Other responses

- `400`: Bad request
- `401`: Authentication required
- `403`: Insufficient permissions
- `404`: Resource not found
- `422`: The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.
- `429`: Rate limit exceeded
- `500`: Internal server error
- `503`: The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation.

---

[API](https://skmtc.dev/messagebird/apis/bird-api.md) · [All operations](https://skmtc.dev/messagebird/apis/bird-api/llms.txt) · [OpenAPI document](https://skmtc.dev/messagebird/apis/bird-api/revisions/3e2a50466454?raw)
