Build a social app
05 · Social graph

Follow, mute, block, restrict.

Four edge kinds, one consistent shape. Follow shows up in your feed; mute hides someone's content from you without telling them; block is bidirectional and tears down anything in between; restrict quietly pre-moderates someone without unfollowing them.


1

Follow

bash
# Alice follows Bob
curl -X POST -H "Authorization: Bearer pcft_live_..." \
  -H "content-type: application/json" \
  -d '{ "src_actor_id": "<alice>", "dst_actor_id": "<bob>" }' \
  https://social.productcraft.co/v1/communities/<c>/follows

# Unfollow
curl -X DELETE -H "Authorization: Bearer pcft_live_..." \
  https://social.productcraft.co/v1/communities/<c>/follows/<alice>/<bob>

Importing an existing graph? POST /follows/batch takes up to 200 follows per call and returns a per-item result — a blocked pair fails its item with a 409 while the rest of the batch proceeds. Pass suppress_notifications: true so long-standing followers don't get "X followed you" notifications for edges that existed before you migrated (follower counts still update):

bash
curl -X POST -H "Authorization: Bearer pcft_live_..." \
  -H "content-type: application/json" \
  -d '{
    "suppress_notifications": true,
    "follows": [
      { "src_actor_id": "<alice>", "dst_actor_id": "<bob>" },
      { "src_actor_id": "<alice>", "dst_actor_id": "<carol>" }
    ]
  }' \
  https://social.productcraft.co/v1/communities/<c>/follows/batch

# → { "data": [ { "index": 0, "success": true, "edge": {...}, "created": true }, ... ],
#     "succeeded": 2, "failed": 0 }

2

Followers + following lists

bash
# Who follows Carol? — with each follower's profile nested on the edge
curl -H "Authorization: Bearer pcft_live_..." \
  "https://social.productcraft.co/v1/communities/<c>/actors/<carol>/followers?limit=50&expand=src_actor"

# Who does Alice follow?
curl -H "Authorization: Bearer pcft_live_..." \
  "https://social.productcraft.co/v1/communities/<c>/actors/<alice>/following?limit=50&expand=dst_actor"

Both responses are cursor-paginated edges. To render an avatar grid, pass expand=<field> — the full actor row (display name, avatar, metadata — the profile you stored in stage 2) is nested under that field on every edge, so a followers screen renders from this one call. No per-follower actor fetches, no joining against your own tables:

response.json
{
  "data": [
    {
      "id": "7d0e...", "kind": "follow",
      "src_actor_id": "d6ee...", "dst_actor_id": "<carol>", ...,
      "src_actor": {
        "id": "d6ee...", "external_id": "user_alice",
        "display_name": "Alice Lin",
        "avatar_url": "https://i.pravatar.cc/300?img=47",
        "metadata": { "bio": "Brand & product designer ✦" },
        "status": "active", ...
      }
    }
  ],
  "pagination": { "next_cursor": null, "has_more": false }
}

expand names the response field the actor is nested under: src_actor on the followers list, dst_actor on following / mutes / blocks / restricts. Without it the response is byte-identical to the plain edge shape. If you need actor rows for an arbitrary set of ids instead (e.g. hydrating ids you cached), batch-fetch with GET /actors?ids=<uuid,uuid,…> (max 100).


3

Mute

Mute is one-directional and silent. Alice mutes Bob — Bob sees nothing, but Alice stops seeing Bob's posts in her feed.

bash
# Mute / unmute
curl -X POST -H "Authorization: Bearer pcft_live_..." \
  -H "content-type: application/json" \
  -d '{ "src_actor_id": "<alice>", "dst_actor_id": "<bob>" }' \
  https://social.productcraft.co/v1/communities/<c>/mutes

curl -X DELETE -H "Authorization: Bearer pcft_live_..." \
  https://social.productcraft.co/v1/communities/<c>/mutes/<alice>/<bob>

# List actors Alice has muted
curl -H "Authorization: Bearer pcft_live_..." \
  https://social.productcraft.co/v1/communities/<c>/actors/<alice>/mutes?limit=50

4

Block (and what it tears down)

Blocking is bidirectional and aggressive — creating a block from Bob → Alice:

  • Drops any existing follow / mute between them (in both directions).
  • Refuses any subsequent follow / mute / reaction between the pair with 409 until the block is lifted.
  • Hides each one's content from the other's feed and profile.
bash
# Block / unblock
curl -X POST -H "Authorization: Bearer pcft_live_..." \
  -H "content-type: application/json" \
  -d '{ "src_actor_id": "<bob>", "dst_actor_id": "<alice>" }' \
  https://social.productcraft.co/v1/communities/<c>/blocks

curl -X DELETE -H "Authorization: Bearer pcft_live_..." \
  https://social.productcraft.co/v1/communities/<c>/blocks/<bob>/<alice>

# Trying to follow Bob during a block returns:
# {
#   "statusCode": 409,
#   "message": "Cannot create this edge — one or both actors have a block in place"
# }

5

Restrict (quiet pre-moderation)

Restrict is the Instagram-style middle ground between ignoring someone and blocking them. One-directional and silent — the restricted actor sees nothing change and can still see your content. What actually happens when Alice restricts Bob:

  • Comment pre-moderation. Bob's new comments on Alice's posts land as status="pending_approval" — invisible to everyone but Bob and Alice until Alice approves via POST /comments/:id/approve (author-only; covered in stage 4's comment lifecycle).
  • Notification requests slice. Notifications triggered by Bob are written with suppressed: true — they skip Alice's default inbox and surface under ?status=requests (stage 8).
  • DM requests folder. Conversations with Bob move out of Alice's default inbox into the requests slice of the conversation list (stage 10).

Creating a restrict on a blocked pair returns 409; restricting yourself is 422.

bash
# Restrict / unrestrict
curl -X POST -H "Authorization: Bearer pcft_live_..." \
  -H "content-type: application/json" \
  -d '{ "src_actor_id": "<alice>", "dst_actor_id": "<bob>" }' \
  https://social.productcraft.co/v1/communities/<c>/restricts

curl -X DELETE -H "Authorization: Bearer pcft_live_..." \
  https://social.productcraft.co/v1/communities/<c>/restricts/<alice>/<bob>

# List actors Alice has restricted
curl -H "Authorization: Bearer pcft_live_..." \
  https://social.productcraft.co/v1/communities/<c>/actors/<alice>/restricts?limit=50

6

Edge listings, in summary

Five lists, all cursor-paginated, all under /actors/:actorId/<list>, all expandable:

  • followers — who follows this actor (expand=src_actor).
  • following — who this actor follows (expand=dst_actor).
  • mutes — who this actor has muted (expand=dst_actor).
  • blocks — who this actor has blocked (expand=dst_actor).
  • restricts — who this actor has restricted (expand=dst_actor).
  • restricts — who this actor has restricted.

All edge DELETEs are idempotent — removing an edge that doesn't exist still returns 204 (missing actors are a 404). To mute keywords rather than people, see muted terms.