# Get delivery analytics

Source: https://docs.coderhelm.com/api/reference/deliveries/get-delivery-analytics

`GET /api/deliveries/analytics`

How the team's deliveries went over a period, against the period before: webhooks CoderHelm received (GitHub, Jira, alerts), events it relayed, and what it sent (webhook endpoints, Slack, Teams, email). Per hour for `24h`, per day otherwise: volume, success rate, latency percentiles, retries and how many of them went fine, failures by reason in plain words, the event types that failed most, and each webhook endpoint's success rate, latency, failure streak and whether it is turned off. Counts are kept 120 days; each delivery's own record 14.

## Authentication

A team API key: `Authorization: Bearer chk_...`. The key needs the `deliveries` scope. See [Authentication](https://docs.coderhelm.com/api/authentication).

## Parameters

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `period` | string | no | `24h` (by hour), `7d` or `30d` (by day). Default `7d`. Each is compared with the period before it, as long. |
| `source` | string | no | Only one source: `github`, `relay`, `webhook`, `alerts`, `jira`, `slack`, `teams`, `email`, `broker`, `knowledge`. |
| `status` | string | no | `all` (default), `ok` (handled or ignored), `handled`, `ignored` or `failed`. Retrying deliveries count once they finish. |
| `endpoint` | string | no | Only one outbound webhook endpoint (its id). |

## Responses

### 200

The overview.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `backfill` | Backfill | yes | Whether the records from before rollups existed are all counted yet. |
| `backfill.complete` | boolean | yes | False while older records are still being counted; numbers grow until it is true. |
| `endpoints` | array of EndpointRow | yes | The team's outbound webhook endpoints, and any that had deliveries in the period since. Empty when another source is chosen. |
| `endpoints[].deleted` | boolean | yes | The endpoint no longer exists; its counts stay for the period. |
| `endpoints[].deliveries` | integer | yes | Finished deliveries to it in the period. |
| `endpoints[].description` | string | yes | The endpoint's description, as the team wrote it. |
| `endpoints[].disabled_reason` | string | yes | Why it's off: `manual` or `failing`. Empty while on. |
| `endpoints[].enabled` | boolean | yes | Whether the endpoint is on. |
| `endpoints[].failed` | integer | yes | Failed. |
| `endpoints[].failing_since` | string or null | no | The first failure since its last success, while it's failing. |
| `endpoints[].id` | string | yes | The endpoint's id. |
| `endpoints[].last_failure_at` | string or null | no | When it last failed. |
| `endpoints[].last_failure_message` | string or null | no | The last failure's reason in plain words. |
| `endpoints[].last_failure_reason` | string or null | no | The last failure's reason code. |
| `endpoints[].last_success_at` | string or null | no | When it last took a delivery. |
| `endpoints[].ok` | integer | yes | Delivered. |
| `endpoints[].p50_ms` | integer or null | no | Median latency, milliseconds. |
| `endpoints[].p95_ms` | integer or null | no | 95th percentile latency, milliseconds. |
| `endpoints[].streak` | integer | yes | Failures in a row since its last success. |
| `endpoints[].success_rate` | number or null | no | Delivered, out of finished. Null with none. |
| `endpoints[].url` | string | yes | The endpoint's address (empty once the endpoint is deleted). |
| `events` | array of EventRow | yes | The event types that failed most (up to 10). |
| `events[].event` | string | yes | The event or message type. |
| `events[].failed` | integer | yes | Failures of this type in the period. |
| `events[].source` | string | yes | Where it came from or went. |
| `from` | string | yes | The period: from (inclusive) to (exclusive), RFC 3339. |
| `granularity` | string | yes | `hour` or `day`. |
| `kept_days` | integer | yes | Days the counts are kept. |
| `period` | string | yes | `24h`, `7d` or `30d`. |
| `previous` | Totals | yes | Headline numbers for one period. |
| `previous.deliveries` | integer | yes | Finished deliveries (handled, ignored or failed). |
| `previous.failed` | integer | yes | Finished and failed. |
| `previous.handled` | integer | yes | Finished and handled. |
| `previous.ignored` | integer | yes | Finished and ignored on purpose (a duplicate, an event nothing needed). |
| `previous.p50_ms` | integer or null | no | Latency percentiles in milliseconds (the last attempt's), from a histogram, so within a bucket's width. Null with no timed deliveries. |
| `previous.p95_ms` | integer or null | no | 95th percentile latency, milliseconds. |
| `previous.p99_ms` | integer or null | no | 99th percentile latency, milliseconds. |
| `previous.retried` | integer | yes | Deliveries that took more than one attempt. |
| `previous.retried_ok` | integer | yes | Of those, the ones that went fine in the end. |
| `previous.retries` | integer or null | no | Attempts after the first, in all. Null when a status filter is on. |
| `previous.success_rate` | number or null | no | Handled or ignored, out of finished. Null with none. |
| `previous_from` | string | yes | The period before, as long as this one. |
| `previous_to` | string | yes | The end of the period before, exclusive. |
| `reasons` | array of ReasonRow | yes | Failures by reason, most first. |
| `reasons[].code` | string | yes | The reason code, as on each delivery. |
| `reasons[].count` | integer | yes | Failures with this reason in the period. |
| `reasons[].fix` | string | yes | What to do about it. |
| `reasons[].message` | string | yes | What happened, in plain words. |
| `reasons[].previous_count` | integer | yes | Failures with this reason in the period before. |
| `reasons[].sources` | array of string | yes | The sources it happened on, most first. |
| `records_kept_days` | integer | yes | Days each delivery's record is kept (what a drill-in can list). |
| `seen_sources` | array of string | yes | Every source with deliveries in either period, for the filter. |
| `series` | array of Point | yes | One point per hour (24h) or day, oldest first. |
| `series[].deliveries` | integer | yes | Finished deliveries in the bucket. |
| `series[].end` | string | yes | The bucket's end, exclusive. |
| `series[].failed` | integer | yes | Failed. |
| `series[].ok` | integer | yes | Handled or ignored. |
| `series[].p95_ms` | integer or null | no | 95th percentile latency, milliseconds. |
| `series[].previous_deliveries` | integer | yes | Deliveries in the matching bucket of the period before. |
| `series[].start` | string | yes | The bucket's start (RFC 3339, UTC). |
| `series[].success_rate` | number or null | no | Handled or ignored, out of finished. Null with none. |
| `sources` | array of SourceRow | yes | By source and direction, most deliveries first. |
| `sources[].deliveries` | integer | yes | Finished deliveries in the period. |
| `sources[].direction` | string | yes | `inbound` or `outbound`. |
| `sources[].failed` | integer | yes | Failed. |
| `sources[].ok` | integer | yes | Handled or ignored. |
| `sources[].p95_ms` | integer or null | no | 95th percentile latency, milliseconds. |
| `sources[].previous_deliveries` | integer | yes | Finished deliveries in the period before. |
| `sources[].previous_success_rate` | number or null | no | The success rate in the period before. |
| `sources[].source` | string | yes | `github`, `relay`, `webhook`, `alerts`, `jira`, `slack`, `teams`, `email`, `broker`, `knowledge`. |
| `sources[].success_rate` | number or null | no | Handled or ignored, out of finished. Null with none. |
| `summary` | Totals | yes | Headline numbers for one period. |
| `summary.deliveries` | integer | yes | Finished deliveries (handled, ignored or failed). |
| `summary.failed` | integer | yes | Finished and failed. |
| `summary.handled` | integer | yes | Finished and handled. |
| `summary.ignored` | integer | yes | Finished and ignored on purpose (a duplicate, an event nothing needed). |
| `summary.p50_ms` | integer or null | no | Latency percentiles in milliseconds (the last attempt's), from a histogram, so within a bucket's width. Null with no timed deliveries. |
| `summary.p95_ms` | integer or null | no | 95th percentile latency, milliseconds. |
| `summary.p99_ms` | integer or null | no | 99th percentile latency, milliseconds. |
| `summary.retried` | integer | yes | Deliveries that took more than one attempt. |
| `summary.retried_ok` | integer | yes | Of those, the ones that went fine in the end. |
| `summary.retries` | integer or null | no | Attempts after the first, in all. Null when a status filter is on. |
| `summary.success_rate` | number or null | no | Handled or ignored, out of finished. Null with none. |
| `to` | string | yes | The end of the period, exclusive. |

Headers: `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`.

### 400

An unknown `period` or `status`.

### 401

The key is missing, malformed, revoked or expired, or the person who made it left the team.

### 403

The key lacks the scope this endpoint needs (`missing_scope`).

### 429

Over the key's rate limit (`rate_limited`), or, for endpoints that start work, over the team's monthly token limit (`token_limit_reached`).

Headers: `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `Retry-After`.

### 500

Something failed on CoderHelm's side. Safe to retry reads.

## Example request

cURL:

```bash
curl -X GET 'https://api.coderhelm.com/api/deliveries/analytics' \
  -H "Authorization: Bearer $CODERHELM_API_KEY"
```

TypeScript:

```ts
const res = await fetch("https://api.coderhelm.com/api/deliveries/analytics", {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CODERHELM_API_KEY}`,
  },
});
if (!res.ok) {
  const { error, code } = await res.json();
  throw new Error(`${res.status} ${code}: ${error}`);
}
const data = await res.json();
```

Python:

```python
import os
import requests

headers = {
    "Authorization": f"Bearer {os.environ['CODERHELM_API_KEY']}",
}
res = requests.request("GET", "https://api.coderhelm.com/api/deliveries/analytics", headers=headers)
if not res.ok:
    raise RuntimeError(f"{res.status_code}: {res.json().get('error')}")
data = res.json()
```

## Example response

```json
{
  "backfill": {
    "complete": true
  },
  "endpoints": [
    {
      "deleted": false,
      "deliveries": 214,
      "description": "Incident bot",
      "disabled_reason": "",
      "enabled": true,
      "failed": 12,
      "failing_since": null,
      "id": "ep_01JBENDPT4K2M8N3P5Q7R9S1T3",
      "last_failure_at": "2026-10-26T09:14:02Z",
      "last_failure_message": "Your webhook endpoint answered with a server error (5xx).",
      "last_failure_reason": "webhook_server_error",
      "last_success_at": "2026-10-26T15:02:40Z",
      "ok": 202,
      "p50_ms": 320,
      "p95_ms": 2900,
      "streak": 0,
      "success_rate": 0.9439,
      "url": "https://hooks.example.com/coderhelm"
    }
  ],
  "events": [
    {
      "event": "run.failed",
      "failed": 9,
      "source": "webhook"
    }
  ],
  "from": "2026-10-20T00:00:00Z",
  "granularity": "day",
  "kept_days": 120,
  "period": "7d",
  "previous": {
    "deliveries": 1120,
    "failed": 28,
    "handled": 1040,
    "ignored": 52,
    "p50_ms": 170,
    "p95_ms": 1100,
    "p99_ms": 4100,
    "retried": 30,
    "retried_ok": 26,
    "retries": 44,
    "success_rate": 0.975
  },
  "previous_from": "2026-10-13T00:00:00Z",
  "previous_to": "2026-10-20T00:00:00Z",
  "reasons": [
    {
      "code": "webhook_server_error",
      "count": 12,
      "fix": "Check your endpoint's logs for this event id. CoderHelm retries for about a day.",
      "message": "Your webhook endpoint answered with a server error (5xx).",
      "previous_count": 3,
      "sources": [
        "webhook"
      ]
    }
  ],
  "records_kept_days": 14,
  "seen_sources": [
    "github",
    "webhook",
    "slack",
    "email"
  ],
  "series": [
    {
      "deliveries": 190,
      "end": "2026-10-27T00:00:00Z",
      "failed": 4,
      "ok": 186,
      "p95_ms": 1100,
      "previous_deliveries": 171,
      "start": "2026-10-26T00:00:00Z",
      "success_rate": 0.9789
    }
  ],
  "sources": [
    {
      "deliveries": 912,
      "direction": "inbound",
      "failed": 7,
      "ok": 905,
      "p95_ms": 640,
      "previous_deliveries": 870,
      "previous_success_rate": 0.9897,
      "source": "github",
      "success_rate": 0.9923
    }
  ],
  "summary": {
    "deliveries": 1284,
    "failed": 33,
    "handled": 1190,
    "ignored": 61,
    "p50_ms": 180,
    "p95_ms": 1240,
    "p99_ms": 4800,
    "retried": 41,
    "retried_ok": 37,
    "retries": 58,
    "success_rate": 0.9743
  },
  "to": "2026-10-27T00:00:00Z"
}
```
