---
title: "Get outbound SMS statistics by cost center"
canonical: "https://bird.com/docs/api/reference/get-sms-stats-by-cost-center"
---

# Get outbound SMS statistics by cost center

`GET /v1/sms/stats/cost-centers`

Returns aggregate delivery and latency stats grouped by the `cost_center` messages were sent with, for the requested period. Messages sent without a cost center are not counted, so rows do not sum to the period total. Rows are ranked by the `sort` metric (default `accepted`) 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.

## Servers

- `https://{region}.platform.bird.com`: Regional API endpoint. Use the host for the region your organization is hosted in. Official Bird SDKs and the CLI select it automatically from your API key, so you rarely need to set it by hand.
  - `region`: `us1`, `eu1`. Default: `us1`. The region your organization's data is hosted in.
- `https://platform.bird.com`: Region-independent endpoint for authentication and account administration.
- `http://localhost:8080`: Local development.

## Authentication

Use one authentication option:

- `BearerAuth`: HTTP bearer authentication. Pass the API key as a bearer token in the `Authorization` header. Workspace
keys use `bk_{region}_*`; organization master keys use `bm_{region}_*`.
The prefix identifies the region and selects the
API endpoint. Official Bird SDKs and the CLI derive the region from the key.

- `CookieAuth`: Send `bird_session` in cookie. Session cookie set after signing in to the Bird dashboard. The cookie
value is an opaque session token; no session data is stored in the cookie
itself.

## Code samples

**TypeScript**

```ts
const stats = await bird.sms.stats.byCostCenter({ from: "2026-05-01", to: "2026-05-31" });
for (const row of stats.data) {
  // Messages sent without a cost center are not counted, so rows do not sum to
  // the period total.
  console.log(row.cost_center, row.delivery);
}
```

Examples: [TypeScript](/docs/api/reference/get-sms-stats-by-cost-center.ts.md) · [Python](/docs/api/reference/get-sms-stats-by-cost-center.py.md) · [Go](/docs/api/reference/get-sms-stats-by-cost-center.go.md) · [PHP](/docs/api/reference/get-sms-stats-by-cost-center.php.md) · [CLI](/docs/api/reference/get-sms-stats-by-cost-center.cli.md) · [MCP](/docs/api/reference/get-sms-stats-by-cost-center.mcp.md) · [cURL](/docs/api/reference/get-sms-stats-by-cost-center.curl.md)

## Example response `200`

```json
{
  "period": {
    "from": "2026-05-01",
    "to": "2026-05-25",
    "data_as_of": "2026-05-25T14:03:10Z"
  },
  "data": [
    {
      "cost_center": "CC001234",
      "delivery": {
        "accepted": 14820,
        "sent": 14810,
        "delivered": 14720,
        "undelivered": 60,
        "failed": 25,
        "rejected": 10,
        "expired": 5,
        "delivery_rate": 0.9932,
        "failure_rate": 0.0061
      },
      "latency": {
        "processing": {
          "p50_ms": 420,
          "p95_ms": 1820,
          "p99_ms": 4920
        },
        "delivery": {
          "p50_ms": 420,
          "p95_ms": 1820,
          "p99_ms": 4920
        },
        "total": {
          "p50_ms": 420,
          "p95_ms": 1820,
          "p99_ms": 4920
        }
      },
      "trend": [
        {
          "bucket": "2026-05-25",
          "delivery": {
            "accepted": 14820,
            "sent": 14810,
            "delivered": 14720,
            "undelivered": 60,
            "failed": 25,
            "rejected": 10,
            "expired": 5
          }
        }
      ]
    }
  ],
  "total": 8
}
```

## Query parameters

- `from` (string, optional): Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap.
- `to` (string, optional): End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days.
- `timezone` (string, optional): IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets.
- `sort` (string, optional)

  Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`.

  Possible values: `accepted`, `sent`, `delivered`, `undelivered`, `failed`, `rejected`, `expired`, `delivery_rate`, `failure_rate`
- `limit` (integer, optional): Maximum number of cost center rows to return, ranked by the `sort` field descending.
- `include_trend` (boolean, optional): When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422.
- `trend_grain` (string, optional)

  Bucket grain for the `trend` series. Has no effect unless `include_trend=true`.

  Possible values: `daily`, `hourly`

## Header parameters

- `X-Workspace-Id` (string, optional): Workspace context for the request. Required for dashboard authentication and master keys. Workspace keys and access tokens already identify their workspace; send that workspace or omit the header. A different one is rejected.
- `X-Organization-Id` (string, optional): Organization context for the request. Required for dashboard authentication. An API key or access token carries its own organization, so send either that organization or no header at all; a different one is rejected.

## Response body

- `period` (object, required): The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to.
- `period.from` (string, required): Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.
- `period.to` (string, required): Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary.
- `period.data_as_of` (nullable string): Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable.
- `data` (array of object, required): Cost center breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages with a cost center were sent in the period.
- `data.cost_center` (string, required): The cost center this row aggregates, as set on the message or taken from its messaging profile at send time.
- `data.delivery` (object, 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.
- `data.delivery.accepted` (integer, required): Distinct messages accepted for sending after admission checks. This is the denominator for `delivery_rate` and `failure_rate`.
- `data.delivery.sent` (integer, required): Distinct messages handed off to the carrier for delivery.
- `data.delivery.delivered` (integer, required): Distinct messages the carrier confirmed as delivered to the handset.
- `data.delivery.undelivered` (integer, required): Distinct messages the carrier reported as not delivered.
- `data.delivery.failed` (integer, required): Distinct messages that failed during sending.
- `data.delivery.rejected` (integer, required): Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure.
- `data.delivery.expired` (integer, required): Distinct messages that could not be delivered within their validity window and expired.
- `data.delivery.delivery_rate` (nullable number, required): Share of accepted messages that were delivered, computed as `delivered / accepted`. Null when no messages were accepted in scope.
- `data.delivery.failure_rate` (nullable number, required): Share of accepted messages that ultimately failed, computed as `(undelivered + failed + expired) / accepted`. Null when no messages were accepted in scope.
- `data.latency` (object, 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.
- `data.latency.processing` (object): Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.
- `data.latency.processing.p50_ms` (nullable integer, required): Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.processing.p95_ms` (nullable integer, required): 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.processing.p99_ms` (nullable integer, required): 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.delivery` (object): Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.
- `data.latency.delivery.p50_ms` (nullable integer, required): Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.delivery.p95_ms` (nullable integer, required): 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.delivery.p99_ms` (nullable integer, required): 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.total` (object): Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement.
- `data.latency.total.p50_ms` (nullable integer, required): Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.total.p95_ms` (nullable integer, required): 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.latency.total.p99_ms` (nullable integer, required): 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement.
- `data.trend` (array of object): Per-bucket lifecycle counts for this cost center, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`.
- `data.trend.bucket` (string, required): The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain.
- `data.trend.delivery` (object, 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.
- `data.trend.delivery.accepted` (integer, required): Distinct messages accepted for sending after admission checks.
- `data.trend.delivery.sent` (integer, required): Distinct messages handed off to the carrier for delivery.
- `data.trend.delivery.delivered` (integer, required): Distinct messages the carrier confirmed as delivered to the handset.
- `data.trend.delivery.undelivered` (integer, required): Distinct messages the carrier reported as not delivered.
- `data.trend.delivery.failed` (integer, required): Distinct messages that failed during sending.
- `data.trend.delivery.rejected` (integer, required): Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure.
- `data.trend.delivery.expired` (integer, required): Distinct messages that could not be delivered within their validity window and expired.
- `total` (integer, required): Total number of distinct cost centers 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.

## Related resources

- [Sending your first SMS](/learn/sms/sending-your-first-sms) (video)
- [What does SMS mean?](/explained/sms/what-does-sms-mean) (answer)
- [SMS](/sms-api) (product)
- [Build your first integration](/learn/paths/integration) (course)

[Get an implementation brief](/learn/workspace?topic=sms)
