Hashtags.
Write #storymode in a post body and Social indexes it. The directory is per-community, the parser lowercases + dedupes + caps at 30 per body. Indexed inside the same DB transaction as the post insert — a failed post never leaves orphan tag rows.
1
Use #tags in a post
Just write #whatever in body. The parser pulls every #[a-z0-9_]{1,32} match (case-insensitive, lowercased on store), dedupes, and caps at 30 per body. Anything beyond 30 is silently dropped.
curl -X POST \
-H "Authorization: Bearer pcft_live_..." \
-H "Content-Type: application/json" \
-d '{"actor_id":"<actorId>","body":"big day! #storymode #v2 launch is live"}' \
"https://social.productcraft.co/v1/communities/<communityId>/posts"2
Browse the directory
Per-community, ordered by first_used_at desc — newest tags first. Cursor-paginated.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/hashtags?limit=20"3
Posts for a tag
Cursor-paginated by post created_at desc. Filters out drafts, removed, hidden, and expired posts — matches what you'd see in feeds.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/hashtags/storymode/posts?limit=20"4
Prefix autocomplete
Backed by a text_pattern_ops partial index — fast even on millions of tags. Pass a lowercase prefix.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/search/hashtags?q=sto&limit=10"5
Tag detail with post count
Returns the directory row plus a live count of currently- published posts using the tag.
curl -H "Authorization: Bearer pcft_live_..." \
"https://social.productcraft.co/v1/communities/<communityId>/hashtags/storymode"{
"community_id": "...",
"tag": "storymode",
"first_used_at": "2026-04-01T12:00:00.000Z",
"post_count": 17
}Internals
Why the transaction matters
Hashtag rows are inserted INSIDE the same DB transaction as the post insert. The naive approach — insert the post, then insert the hashtags — produces orphan directory entries when the post insert fails: the hashtag row sits in the directory pointing at a post that never came into existence.
The TX-aware path also covers updates: editing a post body re-runs the parser, replaces the post'spost_hashtag rows, and ensures any new tags appear in the directory — all in one TX. If the update rolls back, the index doesn't drift.
Permissions
PAK scopes
- List + posts-for-tag + autocomplete:
social.listonpcft:agora:community/<communityId>. - Tag detail:
social.readon the same URN. - Writes ride the post-create / post-update endpoints and use those scopes; no extra grant needed for the hashtag side-effect.