Notifications Inbox
The notifications inbox delivers activity alerts to members within a hub. All endpoints are scoped to a single hub and require a valid contact JWT (aud=contact).
Resource types:
Endpoints
Authentication
- Read endpoints (
GET) — accept a Bearer token or the refresh cookie (get_current_contact_for_read). - Write endpoints (
POST,PUT) — require a Bearer token only (get_current_contact). - Hub membership is enforced on every request. A non-member (or banned member) receives
404 hub_not_foundor403 hub_membership_required.
List notifications
Response (200):
page[after]=<next_cursor> to fetch the next page. The cursor is the raw notification UUID (not base64-encoded). When meta.page.has_more is false, you are on the last page.
Nullable attributes: actor, actors, count, target, preview, deep_link, highlight, and icon are always present in the response body (serialized as null when absent — never omitted). Clients must handle null.
Filtering a tab’s content: filter[category] and filter[kind] narrow the list server-side so a tab’s content and its pagination are always complete — do not client-filter an already-fetched page. Both compose (AND) with each other, with filter[status]=unread, and with page[after]. Because the list and the /counts endpoint (below) share the same visibility rules, a filtered list’s total row count across all pages always matches that filter’s /counts bucket (e.g. filter[kind]=mention sums to kinds.mention.total). meta.unread_count is never affected by these filters — it always reflects the global bell-visible unread total, matching /unread-count; use /counts for a per-tab unread number. filter[kind]=direct-message always returns an empty page — direct messages are conversation-owned, not bell content.
Notification attributes
actor shape:
target shape:
Get unread count
Get per-category/kind counts
attributes.unread always matches the unread-count endpoint’s total for the same contact.
categoriesis a fixed set of four keys (transactional,content,community,moderation) — always present, even at zero.kindsis sparse — only kinds with at least one matching notification appear (e.g.mention,comment,new-report).- Each bucket is
{ "total": <int>, "unread": <int> }, wheretotalcounts read + unread andunreadcounts unread only.
attributes.unread; Mentions uses attributes.kinds.mention.total; Moderation uses attributes.categories.moderation.total. Any future tab can read the same generic map without a backend change.
Get single notification
404 notification_not_found if the notification does not belong to the authenticated contact.
Response (200): same shape as a single item in the list data array, wrapped in { "data": { ... } }.
Mark one notification read
204 No Content (no body).
Errors:
404 notification_not_found— notification does not belong to this contact.403 hub_membership_required— contact is banned from this hub.
Mark all notifications read
marked_count will be 0 if there were no unread notifications — this is not an error.
Record inbox last-opened time
last_opened_at = now() for this (hub_id, contact_id) pair. Use this whenever the member opens the inbox UI so the backend can determine which notifications arrived since the last visit (e.g. for a “new since you last looked” divider in the UI).
Response (200):
Typical inbox polling pattern
Error codes
Web Push (browser notifications)
Web Push enables the browser to receive notifications even when the member does not have the hub tab open. The backend uses the VAPID protocol (MIO-1145). Resource types:Endpoints
Three-step setup
Step 1 — Get the VAPID public keyattributes.public_key to the browser’s PushManager.subscribe() call as the applicationServerKey.
Step 2 — Subscribe in the browser
201 Created with the created subscription resource.
Removing a subscription
204 No Content. A 404 is expected and safe to ignore if the subscription was already deleted.
Notification preferences
notification_prefs.push is now a valid key on the notification-preferences PATCH. Valid preference keys:
Set
push: false to suppress web push delivery without deleting the subscription.
Error codes
Rate limit: 60 subscribe requests per hour per contact per hub. Exceeding the limit returns
429.
Real-time badge sync via SSE
When a new notification arrives, the server emits acommunity.notification.created event on the SSE stream. Listen for this event to update the unread badge without polling: