# Get the numbers

> Counts for the last 7, 30 or 90 days, ending now and read in your time zone: texts in and out by day and by hour, delivery and reply rates, how long people take to answer you and how long you take to answer them, iMessage against SMS, where texts came from, and the conversations waiting on you with what to fix next.

`GET /v1/analytics`

Counts for the last 7, 30 or 90 days, ending now and read in your time zone: texts in and out by day and by hour, delivery and reply rates, how long people take to answer you and how long you take to answer them, iMessage against SMS, where texts came from, and the conversations waiting on you with what to fix next. The numbers on the dashboard’s Analytics page.

`delivery_rate` is texts that reported delivered or read, out of texts that left the Mac or failed; canceled, expired, waiting and unconfirmed texts aren’t counted, and a regular text that never reports delivery counts as sent. `reply_rate` is people you texted one to one who texted back within `reply_window_hours`. Response times are medians over one-to-one conversations, counting a reply only within seven days.

Needs a key with the `messages:read` scope. [Keys and scopes](https://usebluebot.com/docs/concepts/api-keys)

## Query parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `days` | integer | No | How many days to cover, ending today in your time zone: 7, 30 or 90. Default 30. Default `30` |

## Example

```bash
curl "https://usebluebot.com/api/v1/analytics" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY"
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const analytic = await client.analytics.get();
console.log(analytic);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

analytic = client.analytics.get()
print(analytic)
```

## Response

`200`: The numbers.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | "analytics" | Yes |   |
| `days` | integer | Yes |   |
| `time_zone` | string | Yes |   |
| `from` | timestamp | Yes | The start of the first day covered, local midnight in `time_zone`. |
| `to` | timestamp | Yes | The time the numbers were read. |
| `first_text_at` | timestamp or null | Yes | When the account’s first text went or came in; null before any. |
| `totals` | object | Yes | Counts over the range. |
| `totals.sent` | integer | Yes | Texts that left the Mac (sent, delivered or read), by when they went. |
| `totals.received` | integer | Yes | Texts people sent you, by when they came in. |
| `totals.delivered` | integer | Yes | Sent texts that reported delivered or read. |
| `totals.read` | integer | Yes | Sent texts that reported read. |
| `totals.failed` | integer | Yes | Texts that failed. |
| `totals.unconfirmed` | integer | Yes | Texts the Mac couldn’t confirm, by when they were handed to it. |
| `totals.canceled` | integer | Yes | Texts canceled before they went. |
| `totals.expired` | integer | Yes | Texts that waited past their expiry. |
| `totals.people_texted` | integer | Yes | People you texted one to one. |
| `totals.people_replied` | integer | Yes | Of them, people who texted back within `reply_window_hours`. |
| `totals.new_people` | integer | Yes | People texted for the first time. |
| `totals.stops` | integer | Yes | People who asked to stop, by reply or added by you. |
| `delivery_rate` | number or null | Yes | Delivered or read, out of texts that left the Mac or failed. Canceled, expired, waiting and unconfirmed texts aren’t counted. A fraction from 0 to 1; null when there is nothing to count yet. 0 to 1 |
| `reply_rate` | number or null | Yes | People texted one to one who texted back within `reply_window_hours` of a text. A fraction from 0 to 1; null when there is nothing to count yet. 0 to 1 |
| `reply_window_hours` | integer | Yes |   |
| `response_times` | object | Yes |   |
| `response_times.them_median_seconds` | integer or null | Yes | How long people take to answer you: from your text to their next one. In seconds; null when there are no replies to measure. |
| `response_times.them_count` | integer | Yes | Replies measured. |
| `response_times.you_median_seconds` | integer or null | Yes | How long you take to answer them: from their text to your next one. Away messages and keyword replies aren’t counted. In seconds; null when there are no replies to measure. |
| `response_times.you_count` | integer | Yes |   |
| `services` | object | Yes | Texts that left the Mac, by the service they went as. RCS counts as SMS. |
| `services.imessage` | integer | Yes |   |
| `services.sms` | integer | Yes |   |
| `by_day` | array of object | Yes |   |
| `by_day[].day` | string | Yes | YYYY-MM-DD in `time_zone`. |
| `by_day[].sent` | integer | Yes |   |
| `by_day[].received` | integer | Yes |   |
| `by_hour` | array of array of integers | Yes | Texts in and out by weekday (Monday first) and hour, in `time_zone`. Up to 7 items |
| `by_source` | array of object | Yes | Texts that left the Mac by where they came from, most first. |
| `by_source[].source` | string | Yes | One of `api`, `zapier`, `make`, `n8n`, `pipedream`, `cli`, `mcp`, `hook`, `broadcast`, `sequence`, `inbox`, `autopilot`, `keyword`, `away`, `widget`, `assistant`, `test`, `mac` |
| `by_source[].count` | integer | Yes |   |
| `health` | object | Yes |   |
| `health.waiting` | integer | Yes | Conversations whose last text is theirs and has no answer from you. |
| `health.follow_up_gaps` | integer | Yes | Of those, people who answered a text of yours and have heard nothing for two days. |
| `health.oldest_waiting_at` | timestamp or null | Yes | When the longest-waiting of those texts came in. |
| `health.waiting_list` | array of WaitingConversation | Yes | Up to 10 conversations waiting on you, follow-up gaps and oldest first. |
| `health.waiting_list[].conversation_id` | string | Yes | A conv_ id. Starts with `conv_` |
| `health.waiting_list[].name` | string | Yes | The person’s name, the group’s name, or their number when there is no name. |
| `health.waiting_list[].handle` | string or null | Yes | Their number or email address; null for a group chat. |
| `health.waiting_list[].is_group` | boolean | Yes |   |
| `health.waiting_list[].since` | timestamp | Yes | When their last text came in. |
| `health.waiting_list[].follow_up_gap` | boolean | Yes | True when they answered a text of yours and have heard nothing for two days. |
| `health.unconfirmed` | integer | Yes | Texts waiting for you to say whether they went. |
| `health.held` | integer | Yes | Queued texts held by a rule, not just waiting their turn. |
| `fixes` | array of AnalyticsFix | Yes | What to fix next, most important first. |
| `fixes[].key` | string | Yes | One of `follow_up_gaps`, `waiting`, `unconfirmed`, `failed`, `held`, `mac_checks`, `mac_offline`, `delivery`, `stops`, `slow_replies` |
| `fixes[].title` | string | Yes | What needs doing, in a sentence. |
| `fixes[].why` | string | Yes | Why it matters, in a sentence. |
| `fixes[].count` | integer | Yes |   |
| `fixes[].href` | string | Yes | The dashboard page where it is fixed. |
| `fixes[].severity` | string | Yes | One of `high`, `medium`, `low` |

The answer, `200`:

```json
{
  "object": "analytics",
  "days": 7,
  "time_zone": "America/Chicago",
  "from": "2026-11-07T06:00:00.000Z",
  "to": "2026-11-13T19:41:00.000Z",
  "first_text_at": "2026-09-02T16:20:00.000Z",
  "totals": {
    "sent": 182,
    "received": 96,
    "delivered": 171,
    "read": 120,
    "failed": 3,
    "unconfirmed": 0,
    "canceled": 4,
    "expired": 1,
    "people_texted": 64,
    "people_replied": 41,
    "new_people": 22,
    "stops": 1
  },
  "delivery_rate": 0.9243,
  "reply_rate": 0.6406,
  "reply_window_hours": 72,
  "response_times": {
    "them_median_seconds": 1260,
    "them_count": 58,
    "you_median_seconds": 540,
    "you_count": 37
  },
  "services": {
    "imessage": 151,
    "sms": 31
  },
  "by_day": [
    {
      "day": "2026-11-07",
      "sent": 20,
      "received": 9
    }
  ],
  "by_hour": [
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    [
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ]
  ],
  "by_source": [
    {
      "source": "zapier",
      "count": 104
    },
    {
      "source": "inbox",
      "count": 61
    },
    {
      "source": "sequence",
      "count": 17
    }
  ],
  "health": {
    "waiting": 3,
    "follow_up_gaps": 1,
    "oldest_waiting_at": "2026-11-10T15:02:00.000Z",
    "waiting_list": [
      {
        "conversation_id": "conv_1Hq2LxW9cVbN3mKpRtYs0a",
        "name": "Marcus Lee",
        "handle": "+15555550123",
        "is_group": false,
        "since": "2026-11-10T15:02:00.000Z",
        "follow_up_gap": true
      }
    ],
    "unconfirmed": 0,
    "held": 2
  },
  "fixes": [
    {
      "key": "follow_up_gaps",
      "title": "1 person answered you and hasn’t heard back in two days.",
      "why": "A reply that waits days often goes cold.",
      "count": 1,
      "href": "/app/inbox?filter=waiting",
      "severity": "high"
    }
  ]
}
```

## Errors

Every refusal has the same envelope and a page of its own. These are the ones this operation can give.

| Code                                                    | Status | What it means                                                                                                                                    |
| ------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`unauthorized`](https://usebluebot.com/docs/errors/unauthorized)             | 401    | The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API.                                |
| [`insufficient_scope`](https://usebluebot.com/docs/errors/insufficient_scope) | 403    | The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope.                                                   |
| [`rate_limited`](https://usebluebot.com/docs/errors/rate_limited)             | 429    | Too many requests. A key may make 120 requests a minute. The `RateLimit` header says how many are left, and `Retry-After` how long to wait.      |
| [`internal`](https://usebluebot.com/docs/errors/internal)                     | 500    | Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe.                    |
| [`invalid_request`](https://usebluebot.com/docs/errors/invalid_request)       | 400    | Something in the request doesn’t fit: a missing or malformed field, a bad id, or a combination the endpoint refuses. `details` names each field. |

## Try it
