# Analytics API Reference

HTTP reference for the Native Notify analytics endpoints — reporting events from your own code and reading the aggregates back.

Every analytics feature is also a plain HTTP API on your app's server, authenticated with your **app id + app token** like the rest of the Native Notify API. The native-notify SDK consumes the ingestion endpoints for you — these docs are for custom clients (web apps, backends, other frameworks) and for reading aggregates back.

Base URL: `https://app.nativenotify.com`

## Reporting events

### Screen views

`POST /api/analytics/screen`

```json
{
  "appId": 123,
  "appToken": "yourAppToken",
  "screenName": "Home",
  "deviceId": "optional-stable-device-id",
  "token": "ExponentPushToken[...]",
  "subId": "optional-indie-sub-id"
}
```

Or batch up to 50 events per request:

```json
{
  "appId": 123,
  "appToken": "yourAppToken",
  "events": [{ "screenName": "Home" }, { "screenName": "Settings" }]
}
```

- `screenName` is required per event (trimmed, max 120 chars).
- For **unique** views, send `deviceId` (best), or `subId`, or `token` — with none of them the view still counts toward totals.
- Responses: `201 {"ok": true, "accepted": N}`; `400` for missing/invalid fields; `401` for a bad app token.

### Sessions

`POST /api/analytics/session`

```json
{
  "appId": 123,
  "appToken": "yourAppToken",
  "sessionId": "unique-session-id",
  "durationMs": 45000,
  "deviceId": "optional-stable-device-id"
}
```

`sessionId` is required and deduplicated per app — sending the same id twice never double-counts. Durations are clamped to 24 hours.

## Reading aggregates

All read endpoints are `GET` with the app credentials in the path and support a `days`/`weeks` range where noted.

### Summary (growth + series)

`GET /api/analytics/summary/:appId/:appToken?days=30`

Returns:

- `current` — `dau`, `wau`, `mau` (rolling windows), `newUsers`, `returningUsers` (trailing `days` window), `sessions`, `avgSessionMs`, `stickiness` (DAU ÷ MAU).
- `previous` — the same windows shifted back one period, and `growth` — percent change (`null` when there is no baseline).
- `series` — one point per day: `dau`, `wau`, `mau`, `newUsers`, `returningUsers`, `sessions`, `avgSessionMs`.
- `seriesSource` — `"live"` (computed from raw activity, `days` ≤ 90) or `"rollup"` (from the nightly rollup, for longer ranges up to 365 days).

### Screens

`GET /api/analytics/screens/:appId/:appToken?days=30&limit=10`

Returns `screens`: `[{ screen_name, total_views, unique_views, series: [{ day, total, unique_views }] }]`, ranked by total views. `unique_views` counts distinct devices across the whole range.

### Retention

`GET /api/analytics/retention/:appId/:appToken?weeks=8`

Returns `cohorts`: weekly cohorts by first-activity week, each with `size` and per-week retention (`week`, `users`, `pct`).

### Best send times

`GET /api/analytics/best-times/:appId/:appToken?days=90`

Returns `cells`: `[{ dow, hour, opened }]` (0 = Sunday, hours in UTC) plus the `total` opens in the range.

### Delivery failure reasons

`GET /api/notification/failures/:appId/:appToken?days=30`

Returns `errors`: `[{ error_code, count, last_seen }]` aggregated from Expo receipt errors (`DeviceNotRegistered`, `InvalidCredentials`, `MismatchSenderId`, …).

### CSV export

`GET /api/analytics/export/:appId/:appToken?type=summary|screens&days=30`

Returns a `text/csv` download: the daily summary series (`type=summary`) or the per-day screen rows (`type=screens`). The dashboard's CSV buttons use this endpoint directly.

## Per-notification stats

`GET /api/notification/stats/:appId/:appToken?take=50&skip=0`

The per-notification rollup the dashboard's delivery/open table uses: `notification_id`, `source` (`"mass"` | `"indie"`), `accepted`, `delivered`, `failed`, `opened`, `opened_unique`, `first_opened_at`, `last_opened_at`, `date`, `title` — paginated with `X-Total-Count`. This is the read side of the open reporting described in [Sessions & Push Opens](/docs/analytics/sessions-and-opens).
