Build a social app
04 · Engagement

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

bash
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
response.json
{
  "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.

bash
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>/comments

3

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).

bash
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.

bash
# 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>/love

5

Per-actor counters

For profile pages and avatar badges, the counters endpoint gives you a one-call summary:

bash
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.