Notifications
In-app notifications are delivered per-user and scoped to the authenticated identity. All operations are identity-scoped via JWT — no workspace or account identifier is required in request bodies.
Endpoints
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| GET | /notifications | List notifications (paginated) | JWT | 60/min |
| GET | /notifications/unread-count | Get unread notification count | JWT | 60/min |
| PATCH | /notifications/:id/read | Mark one notification as read | JWT | 30/min |
| POST | /notifications/mark-all-read | Mark all notifications as read | JWT | 10/min |
| PATCH | /notifications/:id/archive | Archive a notification | JWT | 30/min |
GET /notifications
List notifications for the authenticated user. Returns paginated results ordered by most recent first.
Request
Authentication: JWT Bearer token
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | No | Items per page |
offset | number | No | Pagination offset |
Response
Response Example
{
"data": [
{
"id": "notif-001",
"title": "Your chain streak is at risk",
"body": "You have not logged any bits in Vocabulary Practice today.",
"icon": null,
"actionUrl": "/chains/cm5chain01",
"category": "productivity",
"priority": "high",
"status": "unread",
"readAt": null,
"expiresAt": null,
"createdAt": "2026-04-01T08:00:00.000Z"
}
],
"meta": {
"total": 1,
"limit": 20,
"offset": 0
}
}Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Notification ID |
title | string | Notification title |
body | string | Notification body text |
icon | string | null | Optional icon identifier |
actionUrl | string | null | Optional deep-link URL |
category | string | Notification category (e.g. productivity, system) |
priority | string | low, normal, or high |
status | string | unread, read, or archived |
readAt | string | null | ISO 8601 — when the notification was read |
expiresAt | string | null | ISO 8601 — when the notification expires |
createdAt | string | ISO 8601 |
Code Examples
curl "https://api.chainabit.com/api/v1/notifications?limit=20&offset=0" \
-H "Authorization: Bearer $TOKEN"const res = await fetch(`${BASE_URL}/notifications?limit=20&offset=0`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data, meta } = await res.json();import requests
res = requests.get(
f"{BASE_URL}/notifications",
headers={"Authorization": f"Bearer {TOKEN}"},
params={"limit": 20, "offset": 0},
)
body = res.json()GET /notifications/unread-count
Get the count of unread notifications for the authenticated user. Use this to drive badge indicators in your UI.
Request
Authentication: JWT Bearer token
Response
{
"data": {
"count": 3
}
}Code Examples
curl "https://api.chainabit.com/api/v1/notifications/unread-count" \
-H "Authorization: Bearer $TOKEN"const res = await fetch(`${BASE_URL}/notifications/unread-count`, {
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();
console.log(data.count); // number of unread notificationsimport requests
res = requests.get(
f"{BASE_URL}/notifications/unread-count",
headers={"Authorization": f"Bearer {TOKEN}"},
)
count = res.json()["data"]["count"]PATCH /notifications/:id/read
Mark a single notification as read. The readAt timestamp is set to the time of the request. Calling this on an already-read notification is a no-op.
Request
Authentication: JWT Bearer token
Response
{
"data": {
"status": "read"
}
}Code Examples
Use the
idfrom a notification in List Notifications's response as$NOTIFICATION_ID.
curl -X PATCH "https://api.chainabit.com/api/v1/notifications/$NOTIFICATION_ID/read" \
-H "Authorization: Bearer $TOKEN"const NOTIFICATION_ID = process.env.NOTIFICATION_ID; // from List Notifications' response
const res = await fetch(`${BASE_URL}/notifications/${NOTIFICATION_ID}/read`, {
method: "PATCH",
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();import requests, os
notification_id = os.environ["NOTIFICATION_ID"] # from List Notifications' response
res = requests.patch(
f"{BASE_URL}/notifications/{notification_id}/read",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]POST /notifications/mark-all-read
Mark all unread notifications as read for the authenticated user in a single operation.
Request
Authentication: JWT Bearer token
Response
{
"data": {
"status": "all_read"
}
}Code Examples
curl -X POST "https://api.chainabit.com/api/v1/notifications/mark-all-read" \
-H "Authorization: Bearer $TOKEN"const res = await fetch(`${BASE_URL}/notifications/mark-all-read`, {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();import requests
res = requests.post(
f"{BASE_URL}/notifications/mark-all-read",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]PATCH /notifications/:id/archive
Archive a notification. Archived notifications are excluded from the default listing but retained for the user's history. Archiving an already-archived notification is a no-op.
Request
Authentication: JWT Bearer token
Response
{
"data": {
"status": "archived"
}
}Code Examples
Reuses
$NOTIFICATION_IDfrom List Notifications's response.
curl -X PATCH "https://api.chainabit.com/api/v1/notifications/$NOTIFICATION_ID/archive" \
-H "Authorization: Bearer $TOKEN"const NOTIFICATION_ID = process.env.NOTIFICATION_ID; // from List Notifications' response
const res = await fetch(`${BASE_URL}/notifications/${NOTIFICATION_ID}/archive`, {
method: "PATCH",
headers: { Authorization: `Bearer ${TOKEN}` },
});
const { data } = await res.json();import requests, os
notification_id = os.environ["NOTIFICATION_ID"] # from List Notifications' response
res = requests.patch(
f"{BASE_URL}/notifications/{notification_id}/archive",
headers={"Authorization": f"Bearer {TOKEN}"},
)
data = res.json()["data"]Error Reference
| Status | When it occurs |
|---|---|
| 401 | Missing or expired JWT token |
| 403 | Notification belongs to another user |
| 404 | Notification ID does not exist |
| 422 | Invalid UUID format for :id |