Notification preferences.
Per-actor, per-kind opt-out. The producer checks the preference before inserting a notification row, so muted kinds never land in the inbox or the unread-count — they're simply not written.
Fifteen notification kinds today: follow, reaction, comment_on_post, reply_to_comment, mention, vote, repost, quote_post, dm_new_message, dm_added_to_group, dm_role_changed, dm_reaction, tag, follow_request, follow_request_approved. The catalogue grows with the product; new kinds default to enabled for every actor.
1
Read the current settings
One row per known kind — all fifteen, every time. Kinds the actor has not explicitly toggled report enabled: true with an epoch updated_at, so your UI renders the full catalogue without a separate metadata call.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/notification-prefs"{
"data": [
{ "actor_id": "...", "kind": "follow", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "reaction", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "comment_on_post", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "reply_to_comment", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "mention", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "vote", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "repost", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "quote_post", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "dm_new_message", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "dm_added_to_group", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "dm_role_changed", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "dm_reaction", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "tag", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "follow_request", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" },
{ "actor_id": "...", "kind": "follow_request_approved", "enabled": true, "updated_at": "1970-01-01T00:00:00.000Z" }
]
}2
Mute a kind
Idempotent upsert keyed on (actor, kind), and it takes effect immediately — the internal preference cache is invalidated on every write. A kind outside the catalogue is rejected with 400 INVALID_NOTIFICATION_KIND.
curl -X PATCH \
-H "Authorization: Bearer pcft_live_..." \
-H "Content-Type: application/json" \
-d '{"enabled": false}' \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/notification-prefs/follow"{
"actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
"kind": "follow",
"enabled": false,
"updated_at": "2026-05-11T19:42:00.000Z"
}3
Re-enable
Same endpoint, { "enabled": true }. The row stays — only the boolean flips.
curl -X PATCH \
-H "Authorization: Bearer pcft_live_..." \
-H "Content-Type: application/json" \
-d '{"enabled": true}' \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/notification-prefs/follow"Behaviour
What changes downstream
- Write-time suppression. When a kind is muted, the notification row is never inserted — it never appears in the inbox or the unread-count.
- Stacks with existing suppressions. Self-actions and blocked-pair actions are still skipped first — preferences are an additional gate, not a replacement.
- Fail-open on a DB hiccup. If the preference lookup itself errors out, the producer defaults to delivering — a transient outage must not silently mute the inbox.
- Existing notifications are untouched. Muting a kind only stops new ones from being written. Rows already in the inbox stay where they are; clear them via the existing mark-read endpoints.
Permissions
PAK scopes
- GET requires
social.notify.readonpcft:agora:community/<communityId>. - PATCH requires
social.notify.updateon the same URN.
Both scopes are part of the default owner and admin system roles; member does not have social.notify.update by default. If your customer-backend signs in as an admin or an owner-scoped PAK, you're good.