Inbox & Audience Views
The owner's side of an Agent Notify project — the universal inbox as sends with read counts, one device's or subscriber's read state, removing a send from every inbox, and who is registered (devices, subscribers, masked tokens, token health).
These are the account-side reads behind the dashboard's Push Notifications → Inbox and Audience tabs for an Agent Notify project — the data an owner (or their agent) needs to answer "did it arrive?", "did they read it?" and "why didn't they get it?". The device-facing inbox that apps and the web bell read is on Web Bell & Inbox; these routes read the same entries from the owner's side.
All routes are account routes: a dashboard session or an MCP access token (Authorization: Bearer nnmcp_…) — an app token is refused with 403. Every call re-checks that the account (or its team) can reach the app. A classic Expo app answers 409 classic_inbox / 409 classic_audience: its inbox and subscribers are the classic ones (Mass Notification Inbox, Indie Push Notifications).
GET https://app.nativenotify.com/api/agent/apps/:appId/inbox — sends + read state
DELETE https://app.nativenotify.com/api/agent/apps/:appId/inbox/sends/:sendId — remove a send from every inbox
GET https://app.nativenotify.com/api/agent/apps/:appId/audience/universal — who is registered
GET https://app.nativenotify.com/api/agent/apps/:appId/audience/universal/lookup — one subscriber or device
GET https://app.nativenotify.com/api/agent/apps/:appId/audience/universal/token-health — token health
Inbox — sends and read state
GET /api/agent/apps/:appId/inbox?take=20&skip=0&environment=production&subscriberId=reader-8241
| Query | Notes |
|---|---|
take | Sends per page, 1–100 (default 20). A larger value is a 400, never a silent cap. |
skip | Sends to skip (default 0). |
environment | Only sends made in production, staging or development (default: every environment). |
deviceId or subscriberId | Optional lookup: every row then says whether that device — or any device of that subscriber — got it and read it. Not both. |
Response — 200, newest send first:
{
"ok": true,
"pipeline": "universal",
"take": 20,
"skip": 0,
"total": 38,
"sends": [
{
"id": 1000000412,
"sendId": 1000000412,
"title": "Story hour this Saturday",
"body": "Bring the family to the children's corner at 10:00.",
"data": { "url": "/events/story-hour" },
"sentAt": "2026-09-24T14:03:11.000Z",
"audienceType": "all",
"audience": "All devices",
"environment": "production",
"source": "schedule",
"devices": 1284,
"targeted": 1284,
"read": 311,
"deletedByDevice": 12,
"lookup": { "state": "read", "targeted": 2, "read": 1, "deleted": 0, "readAt": "2026-09-24T15:10:02.000Z" }
}
],
"lookup": {
"by": "subscriber",
"id": "reader-8241",
"registered": true,
"devices": [
{
"deviceId": "A1B2C3D4-install-key",
"platform": "ios",
"environment": "production",
"subscriberId": "reader-8241",
"appVersion": "2.4.0",
"lastSeenAt": "2026-09-25T08:14:00.000Z",
"registeredAt": "2026-09-01T12:00:00.000Z"
}
],
"total": 18,
"unread": 3
}
}
| Field | Meaning |
|---|---|
sendId | The send's stable id — the same id a send response returns, the pushed notification carries, and a schedule run records. id repeats it. |
audienceType / audience | all, devices, subscribers or group, and its label. |
source | Who sent it — for example api (the send endpoint), schedule (a schedule rule) or composer (the dashboard). |
devices | Devices the send's audience resolved to. |
targeted | Inbox entries written — one per targeted device. |
read | Entries marked read. |
deletedByDevice | Entries a device removed from its own inbox. |
lookup.state | For the looked-up device or subscriber: read (read on at least one of their devices), unread, deleted (removed from every inbox they got it in) or not_sent (the send did not target them). |
lookup (top level) | Who the id is — their registered devices (registered: false when none) and their inbox total / unread. null without a lookup. |
totalcounts every listed send (for thisenvironment), so page withskipuntil you have readtotalrows.- A send is listed once at least one inbox entry carries its id; a send that matched no device reached no inbox and is not listed.
- Reading changes nothing — no entry is marked read.
Remove a send from every inbox
DELETE /api/agent/apps/:appId/inbox/sends/:sendId
{ "ok": true, "deleted": 1000000412, "rows": 1272 }
The notification disappears from every device's inbox. It is a soft delete: each entry is hidden and the send leaves the dashboard lists, while the send record and its delivery and open stats are kept. There is no undo route. rows is how many inbox entries were hidden (0 when every device had already removed it).
- Owner or admin only — a teammate with the developer role gets
403 admin_only. - An unknown or already deleted send is
404 not_found; a malformed id is400 bad_id.
Audience — who is registered
GET /api/agent/apps/:appId/audience/universal?q=&environment=&platform=&take=25&skip=0
{
"ok": true,
"pipeline": "universal",
"summary": {
"devices": 1349,
"subscribers": 97,
"anonymousDevices": 1228,
"byPlatform": { "ios": 640, "android": 689, "web": 20 },
"byEnvironment": { "production": 1344, "staging": 4, "development": 1 },
"tokens": {
"apns": { "live": 639, "dead": 3, "struck": 1 },
"fcm": { "live": 687, "dead": 5, "struck": 2 },
"web": { "live": 20, "dead": 0, "struck": 0 }
}
},
"take": 25,
"skip": 0,
"total": 97,
"subscribers": [
{
"subscriberId": "reader-8241",
"devices": 2,
"platforms": ["ios", "web"],
"environments": ["production"],
"lastSeenAt": "2026-09-25T08:14:00.000Z",
"firstSeenAt": "2026-09-01T12:00:00.000Z"
}
]
}
summarycovers the whole project: registereddevices, distinctsubscribers,anonymousDevices(registered without asubscriberId), devices by platform and by environment, and the token census per type —live,dead(retired) andstruck(live, but failed at least once).subscribersis one page of subscriber ids, most recently seen first.q(a subscriber-id fragment),environmentandplatform(ios,android,web) filter this page and itstotal.takeis1–100(default25) — a larger value is a400, never a silent cap.
One subscriber or device
GET /api/agent/apps/:appId/audience/universal/lookup?subscriberId=reader-8241&days=30
Pass exactly one of subscriberId (every device registered under it) or deviceId (one device). days is the failure window: 1–365, default 30 (a larger value reads 365 days).
{
"ok": true,
"pipeline": "universal",
"by": "subscriber",
"id": "reader-8241",
"devices": [
{
"deviceId": "A1B2C3D4-install-key",
"subscriberId": "reader-8241",
"platform": "ios",
"environment": "production",
"appVersion": "2.4.0",
"timezone": "America/New_York",
"registeredAt": "2026-09-01T12:00:00.000Z",
"lastSeenAt": "2026-09-25T08:14:00.000Z",
"tokens": [
{
"type": "apns",
"tokenMasked": "…9d8e7f",
"strikes": 0,
"dead": false,
"registeredAt": "2026-09-01T12:00:00.000Z",
"lastSeenAt": "2026-09-25T08:14:00.000Z"
}
]
}
],
"inbox": { "total": 18, "unread": 3 },
"failures": {
"windowDays": 30,
"reasons": [{ "errorCode": "APNs:BadDeviceToken", "count": 1, "lastSeen": "2026-09-12T09:30:04.000Z" }]
},
"cleanupEnabled": true
}
- Token values are always masked (
…9d8e7f; a browser keeps its push-service host,fcm.googleapis.com/…a1b2c3) — support needs the type and health, never the value. failures.reasonsare the transport answers recorded for these tokens (APNs:…,FCM:…,WebPush:…) — see Verify Delivery for what each means.- Nothing registered under the id is
404 subscriber_not_found/device_not_found— the app registers a subscriber's devices with itssubscriberIdat login (Device Registration).
Token health
GET /api/agent/apps/:appId/audience/universal/token-health?days=30
{
"ok": true,
"pipeline": "universal",
"windowDays": 30,
"cleanupEnabled": true,
"byType": {
"apns": { "live": 639, "dead": 3, "struck": 1 },
"fcm": { "live": 687, "dead": 5, "struck": 2 },
"web": { "live": 20, "dead": 0, "struck": 0 }
},
"struck": [
{
"type": "fcm",
"tokenMasked": "…c4d5e6",
"strikes": 1,
"dead": false,
"lastSeenAt": "2026-09-20T18:02:11.000Z",
"deviceId": "android-install-7",
"subscriberId": null,
"platform": "android",
"environment": "production"
}
],
"failures": [{ "errorCode": "FCM:UNREGISTERED", "count": 4, "lastSeen": "2026-09-24T14:03:12.000Z" }]
}
strucklists up to 50 tokens that have failed with a dead-token answer or were retired — live ones with the most strikes first, then retired ones.failuresare the top 20 transport failure codes of the lastdays(1–365, default30).cleanupEnabledsays whether this server retires dead tokens at all (after 2+ separate dead-token failures) — see Tokens & Receipts.
Errors
Errors use the envelope { "error": { "code", "message", "field"? } }:
| Status | Code | Meaning |
|---|---|---|
400 | invalid_field | A bad take, skip or platform, or both deviceId and subscriberId on the inbox. |
400 | invalid_environment | Not production, staging or development. |
400 | invalid_device_id, invalid_subscriber_id, missing_field | A too-long id, or the lookup without exactly one id. |
400 | bad_app_id, bad_id | A malformed app id or send id. |
403 | admin_only | A developer tried to remove a send from every inbox. |
404 | app_not_found, not_found, subscriber_not_found, device_not_found | The app, the send, or the registration does not exist. |
409 | classic_inbox, classic_audience | A classic Expo app — use its classic inbox and subscriber APIs. |
For agents
| MCP tool | Route |
|---|---|
list_universal_inbox | GET …/inbox (device_id / subscriber_id for the lookup) |
delete_universal_inbox_send | DELETE …/inbox/sends/:sendId |
get_universal_audience | GET …/audience/universal |
lookup_universal_audience | GET …/audience/universal/lookup |
get_universal_token_health | GET …/audience/universal/token-health |
A typical support answer chains them: lookup_universal_audience (is the person registered, in which environment, are their tokens healthy?) → list_universal_inbox with their subscriber_id (did the send reach them, did they read it?). The in-dashboard agent answers the same questions from the same data. Agents are instructed to confirm with you before removing a send from every inbox.
Related
- Web Bell & Inbox — the device-facing inbox API and widget.
- Verify Delivery — test-send, health counts and failure reasons.
- Scheduling — every schedule run links its
sendId.