Social guides
Guides

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.

bash
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"
response.json
{
  "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 spoiler hides "Spoilers ahead!" and "no-spoiler zone" alike. There is no word-boundary or regex matching — cat also matches "catalog", so prefer specific terms.
  • What's scanned: the post's body and title, concatenated.
  • Scopes: feed, dm, mention, or all (default). A term applies to its own scope plus all. Today the only read paths that enforce muted terms are the home feed and the discover feed (the feed scope) — dm and mention scopes are stored and returned but not yet enforced anywhere, so stick to feed or all for behavior you can ship on.
  • Expiry: expires_at is optional; null means 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.

bash
# 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 — while has_more is still true. Keep following next_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_more is false.

Permissions

PAK scopes

  • POST: social.create on pcft:agora:muted-term/*.
  • DELETE: social.delete on pcft:agora:muted-term/*.
  • GET: social.list on the community URN.