Comments, reactions, counters.
Comments are threaded with a configurable depth limit. Reactions are multi-type — each post tracks a `{ love: 12, bookmark: 3, … }` shape. Counters give you a fast O(1) read for profile + UI badges.
1
Top-level comment
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"actor_id": "<alice-actor-uuid>",
"body": "That cobalt is unreal — what street?"
}' \
https://agora.productcraft.co/v1/communities/<community-uuid>/posts/<post-uuid>/comments{
"id": "eedd02fc-ce31-4714-bf1a-40772a901ea3",
"post_id": "c7859361-...",
"parent_id": null,
"actor_id": "d6ee4836-...",
"body": "That cobalt is unreal — what street?",
"depth": 0,
"status": "published",
"visibility": "public",
"created_at": "2026-05-02T19:29:44.641Z",
"updated_at": "2026-05-02T19:29:44.641Z"
}2
Threaded reply
Reply to a comment by setting parent_id. Depth is computed from the parent and capped per-community via community.settings.max_comment_depth (default 3). Going past the cap returns 409.
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"actor_id": "<carol-actor-uuid>",
"body": "Calle Trujano 6 — go before 9am for this light.",
"parent_id": "eedd02fc-ce31-4714-bf1a-40772a901ea3"
}' \
https://agora.productcraft.co/v1/communities/<community-uuid>/posts/<post-uuid>/comments3
List comments + replies
Without parent_id: top-level comments, newest first.
With parent_id=<commentId>: direct replies of that comment, oldest first (so threads read naturally top-to-bottom).
curl -H "Authorization: Bearer pcft_live_..." \
"https://agora.productcraft.co/v1/communities/<c>/posts/<p>/comments?limit=20"
curl -H "Authorization: Bearer pcft_live_..." \
"https://agora.productcraft.co/v1/communities/<c>/posts/<p>/comments?parent_id=<comment-uuid>&limit=20"4
Reactions
Reactions are typed edges from an actor to a post. Pass any string in type — Agora stores the count per type on post.reaction_counts. Pick a small fixed set in your client (love, bookmark, fire, insightful…) and let the server tally them.
# POST — add a reaction
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{ "src_actor_id": "<alice-actor-uuid>", "type": "love" }' \
https://agora.productcraft.co/v1/communities/<c>/posts/<p>/reactions
# response carries the live counts so your UI doesn't need a follow-up GET:
# {
# "edge": { ... },
# "created": true,
# "reaction_counts": { "love": 1 }
# }
# DELETE — remove a reaction
curl -X DELETE -H "Authorization: Bearer pcft_live_..." \
https://agora.productcraft.co/v1/communities/<c>/posts/<p>/reactions/<src-actor-uuid>/love5
Per-actor counters
For profile pages and avatar badges, the counters endpoint gives you a one-call summary:
curl -H "Authorization: Bearer pcft_live_..." \
https://agora.productcraft.co/v1/communities/<c>/actors/<actor-uuid>/counters
# response:
# {
# "actor_id": "76054baa-...",
# "follower_count": 2,
# "following_count": 0,
# "post_count": 1,
# "comment_count": 1,
# "blocks_count": 0,
# "updated_at": "2026-05-02T19:30:08.033Z"
# }Counters update synchronously on every relevant write — no polling, no eventual consistency window for the UI to reconcile. post_count / comment_count only count publishedrows, not soft-deleted ones.
Block-aware semantics
One thing to know
If a block exists between commenter and post-author (in either direction), the comment endpoint returns 409. Same for reactions. This is covered in detail in stage 5.