Muted terms.
Per-actor keyword muting. An actor mutes 'spoiler' and posts containing it vanish from their feeds — case-insensitive substring match, optional expiry, scoped per surface. The X-style 'mute this word for a week' primitive.
1
Mute a term
Terms are 1–200 characters (trimmed). Re-POSTing the same term is an upsert — it refreshes the scope and expires_at rather than erroring or duplicating, so "extend my spoiler mute by another week" is just the same call again with a new expiry.
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"term": "spoiler",
"scope": "feed",
"expires_at": "2026-05-08T12:00:00Z"
}' \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/muted-terms"{
"actor_id": "<actor-uuid>",
"community_id": "<community-uuid>",
"term": "spoiler",
"scope": "feed",
"expires_at": "2026-05-08T12:00:00.000Z",
"created_at": "2026-05-01T12:00:00.000Z"
}2
Matching semantics
- Case-insensitive substring. Muting
spoilerhides "Spoilers ahead!" and "no-spoiler zone" alike. There is no word-boundary or regex matching —catalso matches "catalog", so prefer specific terms. - What's scanned: the post's
bodyandtitle, concatenated. - Scopes:
feed,dm,mention, orall(default). A term applies to its own scope plusall. Today the only read paths that enforce muted terms are the home feed and the discover feed (thefeedscope) —dmandmentionscopes are stored and returned but not yet enforced anywhere, so stick tofeedorallfor behavior you can ship on. - Expiry:
expires_atis optional;nullmeans never. Expired terms simply stop matching and drop out of the list endpoint — no cleanup call needed.
3
List + remove
The list endpoint returns the actor's currently-active terms as a bare array (no pagination — the set is small by design). DELETE takes the URL-encoded term itself and is idempotent: 204 whether or not the term existed.
# Active terms
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/muted-terms"
# Remove (URL-encode the term; idempotent 204)
curl -X DELETE -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/actors/<actorId>/muted-terms/spoiler"Caveat
Short pages, correct cursors
Muted-term filtering runs after the feed page is fetched: matching posts are dropped from the returned data array, but pagination.next_cursor and has_more still describe the unfiltered page. Two consequences for your client:
- A page can come back with fewer rows than
limit— even zero — whilehas_moreis stilltrue. Keep followingnext_cursor; don't treat a short or empty page as the end of the feed. - If your UI needs a fixed page size, loop: request, filter lands, request again with the returned cursor until you have enough rows or
has_moreisfalse.
Permissions
PAK scopes
- POST:
social.createonpcft:agora:muted-term/*. - DELETE:
social.deleteonpcft:agora:muted-term/*. - GET:
social.liston the community URN.