Project your end-users as actors.
Every social interaction in Agora keys off an `actor` — your end-user's projection into a community. Create one with the id you already use on your side; Agora returns its own UUID and you key off that from then on.
1
Upsert an actor
POST /v1/communities/:c/actors is idempotent on (community, external_id). The first call creates; the second with the same external_idreturns the same row with created: false.
curl -X POST -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"external_id": "user_alice",
"display_name": "Alice Lin",
"avatar_url": "https://i.pravatar.cc/300?img=47",
"metadata": {
"bio": "Brand & product designer based in Lisbon ✦",
"location": "Lisbon, Portugal"
}
}' \
https://agora.productcraft.co/v1/communities/<community-uuid>/actors{
"actor": {
"id": "d6ee4836-1645-41b0-a1c3-c7e22ad01934",
"community_id": "4d5f5c9e-b4ee-4401-9275-283feb66c178",
"external_id": "user_alice",
"display_name": "Alice Lin",
"avatar_url": "https://i.pravatar.cc/300?img=47",
"metadata": {
"bio": "Brand & product designer based in Lisbon ✦",
"location": "Lisbon, Portugal"
},
"status": "active",
"created_at": "2026-05-02T19:28:58.294Z",
"updated_at": "2026-05-02T19:28:58.294Z"
},
"created": true
}2
Idempotency in practice
Repeating the call returns created: false with the same actor.id. The shape is replace-style — fields you omit will be overwritten by the new body, including metadata. Always include every field you want to keep on each upsert call.
3
List actors
Cursor-paginated, newest first. Default page size 50 (max 200). Pass ?cursor=<next_cursor> while has_more is true:
curl -H "Authorization: Bearer pcft_live_..." \
"https://agora.productcraft.co/v1/communities/<community-uuid>/actors?limit=10"{
"data": [
{ "id": "76054baa-...", "external_id": "user_carol", "display_name": "Carol Diaz", ... },
{ "id": "a54d4499-...", "external_id": "user_bob", "display_name": "Bob Park", ... },
{ "id": "d6ee4836-...", "external_id": "user_alice", "display_name": "Alice Lin", ... }
],
"pagination": { "next_cursor": null, "has_more": false }
}4
Resolve an external id → actor
On the wire, every Agora call below this stage takes an agora actor.id (a UUID). When you only have your customer-side external_id and need to resolve it, use the lookup helper:
curl -H "Authorization: Bearer pcft_live_..." \
https://agora.productcraft.co/v1/communities/<community-uuid>/actors/by-external/user_carol
# returns the actor row, or 4045
Edit profile fields
PATCH /actors/:actorId takes a partial body and updates only the fields present:
curl -X PATCH -H "Authorization: Bearer pcft_live_..." \
-H "content-type: application/json" \
-d '{
"metadata": {
"bio": "Travel photographer. Currently: Oaxaca.",
"location": "Oaxaca, MX"
}
}' \
https://agora.productcraft.co/v1/communities/<community-uuid>/actors/<actor-uuid>Mental model: in your app, an end-user has a row in your users table. Mirror that into Agora once at signup time — just call POST /actorswith the user's id as external_id. From then on, your backend keeps user.id ↔ actor.id mapped however you like (a column on your users table works fine).