Agora docs
API reference

Agora API.

Communities, posts, ranked feeds, stories, polls, moderation — every endpoint generated from the live OpenAPI spec.

Agora is keyed on communityId for the customer-backend lane. Each end-user of your product is projected as an actor viaPOST /v1/communities/{communityId}/actors. All subsequent posts, edges, comments, votes, and views reference the actor by their external_id — the customer-supplied id you assigned at upsert time.

Authentication uses your workspace's Platform API Key (pcft_live_*). The admin endpoints under /v1/workspaces/{workspaceId}/... use cookie / JWT auth from the console; they aren't shown here. The customer-backend surface below is what you wire from your application.

Base URL

https://agora.productcraft.co

Auth

Authorization: Bearer …

Spec version

OpenAPI 3.0.0 · v0.1.0

Agora-Actors

get/v1/communities/{communityId}/actorsAuth

List actors in a community. 50 per page by default (max 200) — paginate with the cursor returned in `pagination.next_cursor` while `pagination.has_more` is true.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Query parameters

limitinteger

Page size; capped at 200.

cursorstring

Opaque cursor from the previous page's `pagination.next_cursor`.

Response · 200 Page of actors with `next_cursor` + `has_more`.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "external_id": "user-42",
      "display_name": "Alice",
      "avatar_url": "string",
      "metadata": {},
      "status": "active",
      "shadow_banned_at": "string",
      "shadow_banned_until": "string",
      "shadow_banned_reason": "string",
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/actorsAuth

Upsert an actor by external_id. Creates on first call, updates on subsequent calls with the same external_id. Idempotent on `(community, external_id)`. Note: omitted fields on a follow-up call are overwritten with the new body — pass every field you want to keep on each call.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

external_id*string

Customer-side identifier (your user id). Idempotency key for upsert; unique per community.

Example: "user_abc123"

display_namestring

Display name shown in feeds / mentions.

Example: "Ada Lovelace"

avatar_urlstring

Avatar URL.

Example: "https://cdn.example.com/avatars/ada.png"

metadataobject

Free-form metadata blob (e.g. plan tier, internal flags).

Example: {"tier":"pro"}

Response · 201 Created (`created: true`) or upserted (`created: false`); the actor row is returned in both cases.

actor*object
id*string

Actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

external_id*string

Customer-supplied stable id.

Example: "user-42"

display_name*string

Display name.

Example: "Alice"

avatar_url*string

Avatar URL.

metadata*object

Free-form metadata.

status*enum (3)
shadow_banned_at*string

ISO timestamp the shadow ban was applied, or null.

shadow_banned_until*string

ISO timestamp the shadow ban auto-expires, or null for indefinite.

shadow_banned_reason*string

Moderator note attached to the ban.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` on first call (row was inserted), `false` on subsequent upserts that updated an existing row.

Example: true

Example

Request

POST /v1/communities/{communityId}/actors
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "external_id": "user_abc123",
  "display_name": "Ada Lovelace",
  "avatar_url": "https://cdn.example.com/avatars/ada.png",
  "metadata": {
    "tier": "pro"
  }
}

Response

{
  "actor": {
    "id": "aaaaaaaa-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "external_id": "user-42",
    "display_name": "Alice",
    "avatar_url": "string",
    "metadata": {},
    "status": "active",
    "shadow_banned_at": "string",
    "shadow_banned_until": "string",
    "shadow_banned_reason": "string",
    "created_at": "2026-05-01T12:00:00.000Z",
    "updated_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true
}
get/v1/communities/{communityId}/actors/by-external/{externalId}Auth

Look up an actor by the customer-supplied external_id.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

externalId*string

Customer-supplied identifier for the user (the `external_id` you upserted with).

Response · 200 Actor row.

id*string

Actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

external_id*string

Customer-supplied stable id.

Example: "user-42"

display_name*string

Display name.

Example: "Alice"

avatar_url*string

Avatar URL.

metadata*object

Free-form metadata.

status*enum (3)
shadow_banned_at*string

ISO timestamp the shadow ban was applied, or null.

shadow_banned_until*string

ISO timestamp the shadow ban auto-expires, or null for indefinite.

shadow_banned_reason*string

Moderator note attached to the ban.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/actors/by-external/{externalId}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "aaaaaaaa-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "external_id": "user-42",
  "display_name": "Alice",
  "avatar_url": "string",
  "metadata": {},
  "status": "active",
  "shadow_banned_at": "string",
  "shadow_banned_until": "string",
  "shadow_banned_reason": "string",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
get/v1/communities/{communityId}/actors/{actorId}Auth

Get an actor by its agora UUID.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID (the id returned by upsert).

Response · 200 Actor row.

id*string

Actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

external_id*string

Customer-supplied stable id.

Example: "user-42"

display_name*string

Display name.

Example: "Alice"

avatar_url*string

Avatar URL.

metadata*object

Free-form metadata.

status*enum (3)
shadow_banned_at*string

ISO timestamp the shadow ban was applied, or null.

shadow_banned_until*string

ISO timestamp the shadow ban auto-expires, or null for indefinite.

shadow_banned_reason*string

Moderator note attached to the ban.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "aaaaaaaa-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "external_id": "user-42",
  "display_name": "Alice",
  "avatar_url": "string",
  "metadata": {},
  "status": "active",
  "shadow_banned_at": "string",
  "shadow_banned_until": "string",
  "shadow_banned_reason": "string",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/actors/{actorId}Auth

Update actor profile fields. Pass only the fields you want to change.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Request body

display_namestring
avatar_urlstring
metadataobject
statusenum (3)

Response · 200 Actor updated.

id*string

Actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

external_id*string

Customer-supplied stable id.

Example: "user-42"

display_name*string

Display name.

Example: "Alice"

avatar_url*string

Avatar URL.

metadata*object

Free-form metadata.

status*enum (3)
shadow_banned_at*string

ISO timestamp the shadow ban was applied, or null.

shadow_banned_until*string

ISO timestamp the shadow ban auto-expires, or null for indefinite.

shadow_banned_reason*string

Moderator note attached to the ban.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/actors/{actorId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "display_name": "string",
  "avatar_url": "string",
  "metadata": {},
  "status": "active"
}

Response

{
  "id": "aaaaaaaa-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "external_id": "user-42",
  "display_name": "Alice",
  "avatar_url": "string",
  "metadata": {},
  "status": "active",
  "shadow_banned_at": "string",
  "shadow_banned_until": "string",
  "shadow_banned_reason": "string",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/actors/{actorId}Auth

Delete an actor. Hard delete — cascades to every post, comment, edge, flag, and notification it owns.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Response · 204

Actor deleted.


Post

get/v1/communities/{communityId}/posts

List posts. Pass `author_id` to filter to a single author. 50/page (max 200), cursor-paginated.

Path parameters

communityId*string

Query parameters

limitinteger
cursorstring

Opaque cursor from `pagination.next_cursor`.

author_idstring · uuid

Limit results to this actor.

Response · 200 Page of posts (only `published`, non-expired, public-visibility unless filtered by author).

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/posts

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": "Shipping Phase 8",
      "body": "Stories are live!",
      "url": "https://cdn.example.com/img.png",
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {
        "like": 12,
        "fire": 3
      },
      "comment_count": 4,
      "source_post_id": null,
      "repost_count": 0,
      "quote_count": 0,
      "view_count": 0,
      "edited_at": null,
      "edit_count": 0,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/posts

Create a post on behalf of an actor.

Path parameters

communityId*string

Request body

actor_id*string

Agora actor UUID who authors the post (returned from POST /actors).

Example: "aaaaaaaa-0000-0000-0000-000000000001"

kindstring

Post kind. Free-form short label; common values: text, link, image, story.

Example: "text"

titlestring

Optional title (1-240 chars).

Example: "Shipping Phase 8"

bodystring

Body markdown / plain text (up to 50000 chars).

Example: "Stories are live!"

urlstring

Optional URL (for link / image kinds).

Example: "https://cdn.example.com/img.png"

attributesobject

Free-form attributes blob. Polls live here at `attributes.poll`.

Example: {"media_url":"https://cdn.example.com/stories/1.jpg"}

visibilityenum (4)

Visibility scope. Defaults to `public`.

Example: "public"

expires_atstring

ISO-8601 expiry timestamp. Used by stories (24h disappearing).

Example: "2026-05-02T12:00:00Z"

statusenum (3)

Initial status. Defaults to `published`. Pass `draft` to save unpublished; pass `scheduled` with a future `scheduled_for` to defer publication. Scheduled posts are invisible to everyone except the author until the per-minute scheduler promotes them.

scheduled_forstring

Required when status='scheduled'. ISO timestamp at least 30 seconds in the future. The PostSchedulerCron promotes scheduled posts to published once now() >= scheduled_for.

source_post_idstring

For `kind='quote'` ONLY: the source post being quoted. Reposts use the dedicated POST /posts/:postId/repost route instead. Validation: when `kind='quote'`, both `source_post_id` and a non-empty `body` are required.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

Response · 201 Post created. Defaults: `kind=text`, `visibility=public`, `status=published`.

id*string

Post UUID.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

actor_id*string

Author actor UUID.

Example: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"

kind*string

Free-form post kind. Common: text, link, image, story.

Example: "text"

title*string

Optional title (1-240 chars).

Example: "Shipping Phase 8"

body*string

Body markdown / plain text (up to 50000 chars).

Example: "Stories are live!"

url*string

Optional URL.

Example: "https://cdn.example.com/img.png"

attributes*object

Free-form attributes blob (polls live at `attributes.poll`).

Example: {}

visibility*enum (4)

Visibility scope.

Example: "public"

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

reaction_counts*object

Map of reaction type to count, denormalised on the post row for cheap feed reads.

Example: {"like":12,"fire":3}

comment_count*number

Comment count, denormalised on the post row.

Example: 4

source_post_id*string

For kind='repost' / 'quote': the source post UUID.

Example: null

repost_count*number

Denormalised count of currently-published reposts.

Example: 0

quote_count*number

Denormalised count of currently-published quote posts.

Example: 0

view_count*number

Denormalised impression count, bumped via POST /posts/views.

Example: 0

edited_at*string

ISO timestamp of the most recent edit (body/title/attributes change). Null when never edited.

Example: null

edit_count*number

Number of edits applied; equals the count of post_revision rows.

Example: 0

expires_at*string

ISO timestamp; `null` = no expiry. Stories typically set this 24h ahead.

Example: null

pinned*boolean

When `true`, surfaces in the author's highlights endpoint regardless of expiry.

Example: false

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/posts
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "kind": "text",
  "title": "Shipping Phase 8",
  "body": "Stories are live!",
  "url": "https://cdn.example.com/img.png",
  "attributes": {
    "media_url": "https://cdn.example.com/stories/1.jpg"
  },
  "visibility": "public",
  "expires_at": "2026-05-02T12:00:00Z",
  "status": "published",
  "scheduled_for": "string",
  "source_post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
}

Response

{
  "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  "kind": "text",
  "title": "Shipping Phase 8",
  "body": "Stories are live!",
  "url": "https://cdn.example.com/img.png",
  "attributes": {},
  "visibility": "public",
  "status": "published",
  "reaction_counts": {
    "like": 12,
    "fire": 3
  },
  "comment_count": 4,
  "source_post_id": null,
  "repost_count": 0,
  "quote_count": 0,
  "view_count": 0,
  "edited_at": null,
  "edit_count": 0,
  "expires_at": null,
  "pinned": false,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
get/v1/communities/{communityId}/posts/{postId}

Get a post by id. Visibility-aware: pass `requester_id` for non-public posts.

Without `requester_id`, only `public` posts return. With one, `followers` / `close_friends` / the requester's own `private` posts surface per the visibility matrix. Returns 404 (opacity) when the requester is not allowed to read the post — never 403.

Path parameters

communityId*string
postId*string

Agora post UUID.

Query parameters

requester_idstring · uuid

Agora actor UUID of the viewer. Surfaces non-public posts they are entitled to see.

Response · 200 Post.

id*string

Post UUID.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

actor_id*string

Author actor UUID.

Example: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"

kind*string

Free-form post kind. Common: text, link, image, story.

Example: "text"

title*string

Optional title (1-240 chars).

Example: "Shipping Phase 8"

body*string

Body markdown / plain text (up to 50000 chars).

Example: "Stories are live!"

url*string

Optional URL.

Example: "https://cdn.example.com/img.png"

attributes*object

Free-form attributes blob (polls live at `attributes.poll`).

Example: {}

visibility*enum (4)

Visibility scope.

Example: "public"

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

reaction_counts*object

Map of reaction type to count, denormalised on the post row for cheap feed reads.

Example: {"like":12,"fire":3}

comment_count*number

Comment count, denormalised on the post row.

Example: 4

source_post_id*string

For kind='repost' / 'quote': the source post UUID.

Example: null

repost_count*number

Denormalised count of currently-published reposts.

Example: 0

quote_count*number

Denormalised count of currently-published quote posts.

Example: 0

view_count*number

Denormalised impression count, bumped via POST /posts/views.

Example: 0

edited_at*string

ISO timestamp of the most recent edit (body/title/attributes change). Null when never edited.

Example: null

edit_count*number

Number of edits applied; equals the count of post_revision rows.

Example: 0

expires_at*string

ISO timestamp; `null` = no expiry. Stories typically set this 24h ahead.

Example: null

pinned*boolean

When `true`, surfaces in the author's highlights endpoint regardless of expiry.

Example: false

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/posts/{postId}

Response

{
  "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  "kind": "text",
  "title": "Shipping Phase 8",
  "body": "Stories are live!",
  "url": "https://cdn.example.com/img.png",
  "attributes": {},
  "visibility": "public",
  "status": "published",
  "reaction_counts": {
    "like": 12,
    "fire": 3
  },
  "comment_count": 4,
  "source_post_id": null,
  "repost_count": 0,
  "quote_count": 0,
  "view_count": 0,
  "edited_at": null,
  "edit_count": 0,
  "expires_at": null,
  "pinned": false,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/posts/{postId}

Update post fields. `pinned` is the highlights toggle for stories; status transitions to `hidden`/`removed` should usually go through the moderation lane.

Path parameters

communityId*string
postId*string

Agora post UUID.

Request body

titlestring

Updated title.

bodystring

Updated body.

urlstring

Updated URL.

attributesobject

Replaces the attributes blob entirely.

visibilityenum (4)
statusenum (5)

Direct status transition. Pass `published` to promote a `draft` or `scheduled`. Prefer the moderation lane for `hidden`/`removed`. Transitions to/from `draft`/`scheduled` other than to `published` are 422.

expires_atstring

ISO-8601 expiry timestamp; pass null to clear.

scheduled_forstring

ISO-8601 publish-at timestamp. Updating only valid while the post is `status="scheduled"`. Pass null to clear (typically only the scheduler does this on promotion).

pinnedboolean

Toggle pinned status (used by highlights / stories).

Response · 200 Post updated.

id*string

Post UUID.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

actor_id*string

Author actor UUID.

Example: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"

kind*string

Free-form post kind. Common: text, link, image, story.

Example: "text"

title*string

Optional title (1-240 chars).

Example: "Shipping Phase 8"

body*string

Body markdown / plain text (up to 50000 chars).

Example: "Stories are live!"

url*string

Optional URL.

Example: "https://cdn.example.com/img.png"

attributes*object

Free-form attributes blob (polls live at `attributes.poll`).

Example: {}

visibility*enum (4)

Visibility scope.

Example: "public"

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

reaction_counts*object

Map of reaction type to count, denormalised on the post row for cheap feed reads.

Example: {"like":12,"fire":3}

comment_count*number

Comment count, denormalised on the post row.

Example: 4

source_post_id*string

For kind='repost' / 'quote': the source post UUID.

Example: null

repost_count*number

Denormalised count of currently-published reposts.

Example: 0

quote_count*number

Denormalised count of currently-published quote posts.

Example: 0

view_count*number

Denormalised impression count, bumped via POST /posts/views.

Example: 0

edited_at*string

ISO timestamp of the most recent edit (body/title/attributes change). Null when never edited.

Example: null

edit_count*number

Number of edits applied; equals the count of post_revision rows.

Example: 0

expires_at*string

ISO timestamp; `null` = no expiry. Stories typically set this 24h ahead.

Example: null

pinned*boolean

When `true`, surfaces in the author's highlights endpoint regardless of expiry.

Example: false

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/posts/{postId}
Content-Type: application/json

{
  "title": "string",
  "body": "string",
  "url": "string",
  "attributes": {},
  "visibility": "public",
  "status": "published",
  "expires_at": "string",
  "scheduled_for": "string",
  "pinned": false
}

Response

{
  "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  "kind": "text",
  "title": "Shipping Phase 8",
  "body": "Stories are live!",
  "url": "https://cdn.example.com/img.png",
  "attributes": {},
  "visibility": "public",
  "status": "published",
  "reaction_counts": {
    "like": 12,
    "fire": 3
  },
  "comment_count": 4,
  "source_post_id": null,
  "repost_count": 0,
  "quote_count": 0,
  "view_count": 0,
  "edited_at": null,
  "edit_count": 0,
  "expires_at": null,
  "pinned": false,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/posts/{postId}

Soft-delete a post. The row is marked `removed`; viewers see 404. Comments and reactions are kept for moderation audit.

Path parameters

communityId*string
postId*string

Agora post UUID.

Response · 204

Post soft-deleted.

get/v1/communities/{communityId}/posts/{postId}/revisions

List a post's edit history. Cursor-paginated by revision_n desc (newest edit first).

Each entry is a snapshot of body/title/attributes from BEFORE the edit landed. The post row itself carries the current state plus `edited_at` + `edit_count`. Drafts are not snapshotted — only published posts incur revisions.

Path parameters

communityId*string
postId*string

Agora post UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of revisions, newest first.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/revisions

Response

{
  "data": [
    {
      "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "revision_n": 1,
      "body": "shipping today.",
      "title": "Phase 8",
      "attributes": {},
      "edited_at": "2026-05-01T12:15:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
get/v1/communities/{communityId}/posts/{postId}/quotes

List the quote posts of a source post (X-style "show quotes" link).

Cursor-paginated by `(created_at DESC, id DESC)`. Each entry is the full quote post row — body, attributes, the quoter's actor_id, the source_post_id back-reference. Filters to status=published.

Path parameters

communityId*string
postId*string

Source post UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of quote posts.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/quotes

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": "Shipping Phase 8",
      "body": "Stories are live!",
      "url": "https://cdn.example.com/img.png",
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {
        "like": 12,
        "fire": 3
      },
      "comment_count": 4,
      "source_post_id": null,
      "repost_count": 0,
      "quote_count": 0,
      "view_count": 0,
      "edited_at": null,
      "edit_count": 0,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/posts/views

Batch-increment view counts on a list of posts. Client-side debounced; missing post ids are silently skipped (so a deleted post mid-batch doesn’t fail the rest).

Path parameters

communityId*string

Request body

post_ids*array

UUIDs of posts that became visible to the viewer in this batch. Capped at 200 to keep the payload sensible — the server will silently skip ids that aren't in the community or have been removed.

Example: ["bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb","cccccccc-cccc-cccc-cccc-cccccccccccc"]

Response · 204

Views recorded (or no-op if every id was missing).

Example

Request

POST /v1/communities/{communityId}/posts/views
Content-Type: application/json

{
  "post_ids": [
    "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
    "cccccccc-cccc-cccc-cccc-cccccccccccc"
  ]
}

Agora-Drafts

get/v1/communities/{communityId}/actors/{actorId}/draftsAuth

List the actor's drafts (status='draft'). Cursor-paginated newest-first.

Drafts are visible only via this endpoint — they're stripped from public feeds, lists, and `getById`. The PAK lane has no end-user principal, so the customer's backend is responsible for ensuring `actorId` matches the end-user behind the request before exposing the response.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID whose drafts to list.

Query parameters

limitinteger
cursorstring

Opaque cursor from `pagination.next_cursor`.

Response · 200 Page of draft posts.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/drafts
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": null,
      "body": "WIP — half a thought…",
      "url": null,
      "attributes": {},
      "visibility": "public",
      "status": "draft",
      "reaction_counts": {},
      "comment_count": 0,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
get/v1/communities/{communityId}/actors/{actorId}/scheduled-postsAuth

List the actor's scheduled posts (status='scheduled'), ordered soonest-to-publish first. Cursor-paginated.

Scheduled posts are invisible to everyone except the author until the per-minute PostSchedulerCron promotes them. Same end-user authorization rules as drafts: the PAK lane trusts the customer-backend to gate the caller.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID whose scheduled posts to list.

Query parameters

limitinteger
cursorstring

Response · 200 Page of scheduled posts.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/scheduled-posts
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": null,
      "body": "WIP — half a thought…",
      "url": null,
      "attributes": {},
      "visibility": "public",
      "status": "draft",
      "reaction_counts": {},
      "comment_count": 0,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}

Agora-Reposts

post/v1/communities/{communityId}/posts/{postId}/repostAuth

Repost a source post on behalf of an actor. Idempotent — returns the existing repost row with `created: false` on retry.

Visibility is inherited from the source. The reposter must be allowed to read the source (else 404). Self-repost 422; private-source repost 422. Bumps `source.repost_count` and the reposter's `post_count`. Fires a `repost` notification to the source author (subject to per-kind preferences from task 021).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Source post UUID.

Request body

actor_id*string

Reposting actor UUID — the customer-backend supplies the end-user identity here.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 201 Repost created (or returned via idempotent retry).

repost*object

The newly-created (or existing) repost post row.

source*object

The source post, with refreshed `repost_count`.

created*boolean

`true` on first call; `false` on idempotent retry of an existing repost.

Example: true

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/repost
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}

Response

{
  "repost": {},
  "source": {},
  "created": true
}
delete/v1/communities/{communityId}/posts/{postId}/repostAuth

Undo my repost of a source post. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Source post UUID.

Request body

actor_id*string

Reposting actor UUID — the customer-backend supplies the end-user identity here.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

Repost soft-deleted (or was never present).

Example

Request

DELETE /v1/communities/{communityId}/posts/{postId}/repost
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}
get/v1/communities/{communityId}/posts/{postId}/repostsAuth

List the actors who have reposted a source post. Cursor-paginated newest-first. Useful for "X reposted this" UI.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Source post UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of repost rows.

data*array

Page of repost post rows, each carrying the reposting actor_id.

pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/reposts
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "repost",
      "title": null,
      "body": null,
      "url": null,
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {},
      "comment_count": 0,
      "source_post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "repost_count": 0,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}

Agora-Counters

get/v1/communities/{communityId}/actors/{actorId}/countersAuth

Denormalised counters for one actor (followers, following, posts, comments, blocks).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID (returned from POST /actors).

Response · 200 Counters for the actor.

actor_id*string

Actor UUID.

Example: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

follower_count*number

How many actors follow this actor.

Example: 42

following_count*number

How many actors this actor follows.

Example: 17

post_count*number

Published posts authored by this actor.

Example: 5

comment_count*number

Published comments authored by this actor.

Example: 12

blocks_count*number

Actors this actor has blocked.

Example: 0

updated_at*string

ISO timestamp of the last counter update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/counters
Authorization: Bearer YOUR_TOKEN

Response

{
  "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "follower_count": 42,
  "following_count": 17,
  "post_count": 5,
  "comment_count": 12,
  "blocks_count": 0,
  "updated_at": "2026-05-01T12:00:00.000Z"
}

Agora-Edges

post/v1/communities/{communityId}/followsAuth

Follow another actor. Idempotent: returns existing edge with `created: false` if the follow already exists. Refused if either side has a block.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

src_actor_id*string

Source agora actor UUID (the one performing the follow / mute / block / restrict).

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination agora actor UUID (the one being followed / muted / blocked / restricted).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

payloadobject

Optional free-form payload stored on the edge (e.g. mute reason).

Response · 201 Follow edge created (or returned via idempotent retry).

edge*object
id*string

Edge UUID.

Example: "eeeeeeee-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*string

Edge kind. `follow`, `mute`, `block`, `save`, or `reaction:<type>` for post reactions.

Example: "follow"

src_actor_id*string

Source actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination actor UUID (for actor edges).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

dst_post_id*string

Destination post UUID (for reaction / save edges).

Example: null

payload*object

Free-form payload stored on the edge (e.g. mute reason).

Example: {}

created_at*string

ISO timestamp the edge was created.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` if the edge was created on this call; `false` on idempotent retry of an existing edge.

Example: true

Example

Request

POST /v1/communities/{communityId}/follows
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "payload": {}
}

Response

{
  "edge": {
    "id": "eeeeeeee-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "kind": "follow",
    "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
    "dst_post_id": null,
    "payload": {},
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true
}
delete/v1/communities/{communityId}/follows/{srcActorId}/{dstActorId}Auth

Unfollow. Idempotent — 204 even if the follow didn't exist.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

srcActorId*string

Follower actor UUID.

dstActorId*string

Followee actor UUID.

Response · 204

Unfollowed (or no edge to remove).

get/v1/communities/{communityId}/actors/{actorId}/followingAuth

List the actors this actor follows.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of follow edges.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/following
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "eeeeeeee-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "kind": "follow",
      "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "dst_post_id": null,
      "payload": {},
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
get/v1/communities/{communityId}/actors/{actorId}/followersAuth

List the actors who follow this actor.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of follow edges.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/followers
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "eeeeeeee-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "kind": "follow",
      "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "dst_post_id": null,
      "payload": {},
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/mutesAuth

Mute another actor. One-directional and silent — the muted actor isn't notified. Hides their content from the muter's feed.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

src_actor_id*string

Source agora actor UUID (the one performing the follow / mute / block / restrict).

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination agora actor UUID (the one being followed / muted / blocked / restricted).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

payloadobject

Optional free-form payload stored on the edge (e.g. mute reason).

Response · 201 Mute edge created (or returned via idempotent retry).

edge*object
id*string

Edge UUID.

Example: "eeeeeeee-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*string

Edge kind. `follow`, `mute`, `block`, `save`, or `reaction:<type>` for post reactions.

Example: "follow"

src_actor_id*string

Source actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination actor UUID (for actor edges).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

dst_post_id*string

Destination post UUID (for reaction / save edges).

Example: null

payload*object

Free-form payload stored on the edge (e.g. mute reason).

Example: {}

created_at*string

ISO timestamp the edge was created.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` if the edge was created on this call; `false` on idempotent retry of an existing edge.

Example: true

Example

Request

POST /v1/communities/{communityId}/mutes
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "payload": {}
}

Response

{
  "edge": {
    "id": "eeeeeeee-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "kind": "follow",
    "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
    "dst_post_id": null,
    "payload": {},
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true
}
delete/v1/communities/{communityId}/mutes/{srcActorId}/{dstActorId}Auth

Unmute. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

srcActorId*string

Muter actor UUID.

dstActorId*string

Mutee actor UUID.

Response · 204

Unmuted.

get/v1/communities/{communityId}/actors/{actorId}/mutesAuth

List actors this actor has muted.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of mute edges.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/mutes
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "eeeeeeee-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "kind": "follow",
      "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "dst_post_id": null,
      "payload": {},
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/blocksAuth

Block another actor. Removes any existing follows/mutes between the two in either direction (one transaction).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

src_actor_id*string

Source agora actor UUID (the one performing the follow / mute / block / restrict).

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination agora actor UUID (the one being followed / muted / blocked / restricted).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

payloadobject

Optional free-form payload stored on the edge (e.g. mute reason).

Response · 201 Block edge created (or returned via idempotent retry); follows + mutes between the pair are torn down in the same transaction.

edge*object
id*string

Edge UUID.

Example: "eeeeeeee-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*string

Edge kind. `follow`, `mute`, `block`, `save`, or `reaction:<type>` for post reactions.

Example: "follow"

src_actor_id*string

Source actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination actor UUID (for actor edges).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

dst_post_id*string

Destination post UUID (for reaction / save edges).

Example: null

payload*object

Free-form payload stored on the edge (e.g. mute reason).

Example: {}

created_at*string

ISO timestamp the edge was created.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` if the edge was created on this call; `false` on idempotent retry of an existing edge.

Example: true

Example

Request

POST /v1/communities/{communityId}/blocks
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "payload": {}
}

Response

{
  "edge": {
    "id": "eeeeeeee-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "kind": "follow",
    "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
    "dst_post_id": null,
    "payload": {},
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true
}
delete/v1/communities/{communityId}/blocks/{srcActorId}/{dstActorId}Auth

Unblock. Idempotent — 204 either way. Does NOT restore prior follows / mutes (the block tear-down is one-way).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

srcActorId*string

Blocker actor UUID.

dstActorId*string

Blockee actor UUID.

Response · 204

Unblocked.

get/v1/communities/{communityId}/actors/{actorId}/blocksAuth

List actors this actor has blocked.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of block edges.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/blocks
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "eeeeeeee-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "kind": "follow",
      "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "dst_post_id": null,
      "payload": {},
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/restrictsAuth

Restrict another actor (Instagram-style). One-directional. Restricted actors can still see content, but new comments by them are pre-moderated as `pending_approval` until the post-author approves.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

src_actor_id*string

Source agora actor UUID (the one performing the follow / mute / block / restrict).

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination agora actor UUID (the one being followed / muted / blocked / restricted).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

payloadobject

Optional free-form payload stored on the edge (e.g. mute reason).

Response · 201 Restrict edge created (or returned via idempotent retry).

edge*object
id*string

Edge UUID.

Example: "eeeeeeee-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*string

Edge kind. `follow`, `mute`, `block`, `save`, or `reaction:<type>` for post reactions.

Example: "follow"

src_actor_id*string

Source actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination actor UUID (for actor edges).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

dst_post_id*string

Destination post UUID (for reaction / save edges).

Example: null

payload*object

Free-form payload stored on the edge (e.g. mute reason).

Example: {}

created_at*string

ISO timestamp the edge was created.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` if the edge was created on this call; `false` on idempotent retry of an existing edge.

Example: true

Example

Request

POST /v1/communities/{communityId}/restricts
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "payload": {}
}

Response

{
  "edge": {
    "id": "eeeeeeee-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "kind": "follow",
    "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
    "dst_post_id": null,
    "payload": {},
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true
}
delete/v1/communities/{communityId}/restricts/{srcActorId}/{dstActorId}Auth

Unrestrict. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

srcActorId*string

Restrictor actor UUID.

dstActorId*string

Restricted actor UUID.

Response · 204

Unrestricted.

get/v1/communities/{communityId}/actors/{actorId}/restrictsAuth

List actors this actor has restricted.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of restrict edges.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/restricts
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "eeeeeeee-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "kind": "follow",
      "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "dst_post_id": null,
      "payload": {},
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}

Agora-Hashtags

get/v1/communities/{communityId}/hashtagsAuth

List hashtags in this community. Cursor-paginated by `first_used_at` desc.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Query parameters

limitinteger
cursorstring

Response · 200 Page of hashtags.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/hashtags
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "tag": "storymode",
      "first_used_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
get/v1/communities/{communityId}/hashtags/{tag}Auth

Get a single hashtag with its current published post count.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

tag*string

Lowercase tag (without `#`).

Response · 200 Hashtag detail.

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

tag*string

Lowercase tag (1..32 chars from [a-z0-9_]).

Example: "storymode"

first_used_at*string

ISO timestamp the tag first appeared in this community.

Example: "2026-05-01T12:00:00.000Z"

post_count*number

Number of currently-published posts using the tag.

Example: 17

Example

Request

GET /v1/communities/{communityId}/hashtags/{tag}
Authorization: Bearer YOUR_TOKEN

Response

{
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "tag": "storymode",
  "first_used_at": "2026-05-01T12:00:00.000Z",
  "post_count": 17
}
get/v1/communities/{communityId}/search/hashtagsAuth

Prefix-search hashtags for autocomplete.

Backed by a `text_pattern_ops` partial index — sub-100ms for any prefix length up to communities with millions of tags. Results are alphabetically ordered.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Query parameters

q*string

Lowercase prefix (1..32 chars).

limitinteger

Response · 200 Matching hashtags.

data*array

Example

Request

GET /v1/communities/{communityId}/search/hashtags
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "tag": "storymode",
      "first_used_at": "2026-05-01T12:00:00.000Z"
    }
  ]
}

Agora-Hashtag-Follows

get/v1/communities/{communityId}/actors/{actorId}/hashtag-followsAuth

List the hashtags an actor follows. Cursor-paginated newest-first by followed_at.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of followed hashtags.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/hashtag-follows
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "tag": "storymode",
      "followed_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/actors/{actorId}/hashtag-followsAuth

Follow a hashtag on behalf of an actor. Idempotent — re-follow refreshes the followed_at timestamp but doesn't error.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID who is following.

Request body

tag*string

Lowercase tag (without `#`). Must already exist in the community directory.

Example: "storymode"

Response · 201 Follow created (or refreshed).

actor_id*string

Agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

tag*string

Lowercase tag (without `#`).

Example: "storymode"

followed_at*string

ISO timestamp the follow was created.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/actors/{actorId}/hashtag-follows
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "tag": "storymode"
}

Response

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "tag": "storymode",
  "followed_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/actors/{actorId}/hashtag-follows/{tag}Auth

Unfollow a hashtag. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

tag*string

Lowercase tag.

Response · 204

Unfollowed (or was never followed).


Agora-Comments

get/v1/communities/{communityId}/posts/{postId}/commentsAuth

List comments on a post. Default returns top-level comments newest-first; pass `parent_id` to expand a thread (oldest-first). Pass `requester_id` so DM-style `author_only` replies surface only to the post-author and the comment-author pair.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID the comments thread under.

Query parameters

limitinteger
cursorstring
parent_idstring · uuid

List replies of this parent comment instead of top-level.

requester_idstring · uuid

Viewer actor UUID — surfaces author_only replies they're entitled to see.

Response · 200 Page of comments.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/comments
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "parent_id": null,
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "body": "Looks great, shipping today.",
      "depth": 0,
      "status": "published",
      "visibility": "public",
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/posts/{postId}/commentsAuth

Add a comment on a post. Pass `parent_id` to reply; the depth is computed and capped by the community config.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID the comments thread under.

Request body

actor_id*string

Agora actor UUID who authors the comment.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

body*string

Comment body (1-10000 chars after trim).

Example: "Looks great, shipping today."

parent_idstring

Parent comment UUID for replies. Omit for a top-level comment.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

visibilityenum (2)

Visibility scope. `author_only` = DM-style (post author + comment author only).

Example: "public"

Response · 201 Comment created.

id*string

Comment UUID.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

post_id*string

Post the comment threads under.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

parent_id*string

Parent comment UUID for replies; `null` for top-level.

Example: null

actor_id*string

Comment author actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

body*string

Comment body.

Example: "Looks great, shipping today."

depth*number

Thread depth — 0 for top-level, 1 for first reply, etc. Capped per community.

Example: 0

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

visibility*enum (2)

`public` (default) is visible to anyone who can read the post; `author_only` is DM-style (post-author + comment-author only).

Example: "public"

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/comments
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "body": "Looks great, shipping today.",
  "parent_id": "bbbbbbbb-0000-0000-0000-000000000001",
  "visibility": "public"
}

Response

{
  "id": "bbbbbbbb-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "parent_id": null,
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "body": "Looks great, shipping today.",
  "depth": 0,
  "status": "published",
  "visibility": "public",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
get/v1/communities/{communityId}/posts/{postId}/comments/{commentId}Auth

Get a single comment by id.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID the comments thread under.

commentId*string

Agora comment UUID.

Query parameters

requester_idstring · uuid

Response · 200 Comment.

id*string

Comment UUID.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

post_id*string

Post the comment threads under.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

parent_id*string

Parent comment UUID for replies; `null` for top-level.

Example: null

actor_id*string

Comment author actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

body*string

Comment body.

Example: "Looks great, shipping today."

depth*number

Thread depth — 0 for top-level, 1 for first reply, etc. Capped per community.

Example: 0

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

visibility*enum (2)

`public` (default) is visible to anyone who can read the post; `author_only` is DM-style (post-author + comment-author only).

Example: "public"

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/comments/{commentId}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "bbbbbbbb-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "parent_id": null,
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "body": "Looks great, shipping today.",
  "depth": 0,
  "status": "published",
  "visibility": "public",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/posts/{postId}/comments/{commentId}Auth

Update a comment's body, status, or visibility.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID the comments thread under.

commentId*string

Agora comment UUID.

Request body

bodystring

Updated body.

statusenum (3)

Direct status transition. Prefer the moderation lane for `hidden`/`removed`.

visibilityenum (2)

Response · 200 Comment updated.

id*string

Comment UUID.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

post_id*string

Post the comment threads under.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

parent_id*string

Parent comment UUID for replies; `null` for top-level.

Example: null

actor_id*string

Comment author actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

body*string

Comment body.

Example: "Looks great, shipping today."

depth*number

Thread depth — 0 for top-level, 1 for first reply, etc. Capped per community.

Example: 0

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

visibility*enum (2)

`public` (default) is visible to anyone who can read the post; `author_only` is DM-style (post-author + comment-author only).

Example: "public"

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/posts/{postId}/comments/{commentId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "body": "string",
  "status": "published",
  "visibility": "public"
}

Response

{
  "id": "bbbbbbbb-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "parent_id": null,
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "body": "Looks great, shipping today.",
  "depth": 0,
  "status": "published",
  "visibility": "public",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/posts/{postId}/comments/{commentId}Auth

Soft-delete a comment.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID the comments thread under.

commentId*string

Agora comment UUID.

Response · 204

Comment soft-deleted.

post/v1/communities/{communityId}/posts/{postId}/comments/{commentId}/approveAuth

Approve a pending_approval comment (restricted-actor pre-moderation). Reserved to the post-author. No-op if already published.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID the comments thread under.

commentId*string

Agora comment UUID.

Request body

approver_actor_id*string

The post-author — must equal the post.actor_id.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 200 Comment approved (now published).

id*string

Comment UUID.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

post_id*string

Post the comment threads under.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

parent_id*string

Parent comment UUID for replies; `null` for top-level.

Example: null

actor_id*string

Comment author actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

body*string

Comment body.

Example: "Looks great, shipping today."

depth*number

Thread depth — 0 for top-level, 1 for first reply, etc. Capped per community.

Example: 0

status*enum (3)

Lifecycle status. `removed` rows are hidden from public reads.

Example: "published"

visibility*enum (2)

`public` (default) is visible to anyone who can read the post; `author_only` is DM-style (post-author + comment-author only).

Example: "public"

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/comments/{commentId}/approve
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "approver_actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}

Response

{
  "id": "bbbbbbbb-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
  "parent_id": null,
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "body": "Looks great, shipping today.",
  "depth": 0,
  "status": "published",
  "visibility": "public",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}

Agora-Comment-Reactions

get/v1/communities/{communityId}/posts/{postId}/comments/{commentId}/reactionsAuth

List reactions on a comment, newest-first. Cursor encodes `(created_at, actor_id, type)` so multi-reaction-from-same-actor stays stable.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Parent post UUID.

commentId*string

Comment UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of reactions.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/comments/{commentId}/reactions
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "comment_id": "bbbbbbbb-0000-0000-0000-000000000001",
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "type": "like",
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/posts/{postId}/comments/{commentId}/reactionsAuth

React to a comment. Idempotent: re-posting the same `(actor, type)` returns the existing row with `created: false`. Multiple reactions per `(actor, comment)` are allowed (different `type` values count separately).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Parent post UUID.

commentId*string

Comment UUID.

Request body

src_actor_id*string

Reactor actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

type*string

Reaction type (free-form short label, e.g. `like`, `fire`, `heart`).

Example: "like"

Response · 201 Reaction added; response carries live `reaction_counts`.

reaction*object
comment_id*string

Comment UUID.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

actor_id*string

Reactor actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

type*string

Reaction type label.

Example: "like"

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` on first call; `false` on idempotent retry.

Example: true

reaction_counts*object

Live denormalised counts on the comment after this write.

Example: {"like":4}

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/comments/{commentId}/reactions
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "type": "like"
}

Response

{
  "reaction": {
    "comment_id": "bbbbbbbb-0000-0000-0000-000000000001",
    "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "type": "like",
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true,
  "reaction_counts": {
    "like": 4
  }
}
delete/v1/communities/{communityId}/posts/{postId}/comments/{commentId}/reactions/{srcActorId}/{type}Auth

Unreact. Idempotent: returns 200 with current `reaction_counts` even if there was nothing to remove.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Parent post UUID.

commentId*string

Comment UUID.

srcActorId*string

Reactor actor UUID.

type*string

Reaction type label (matches the value passed at create time).

Response · 200 Reaction removed (or already absent); response carries live `reaction_counts`.

reaction_counts*object

Live denormalised counts after the delete (or current counts if the reaction was already absent).

Example: {"like":3}

Example

Request

DELETE /v1/communities/{communityId}/posts/{postId}/comments/{commentId}/reactions/{srcActorId}/{type}
Authorization: Bearer YOUR_TOKEN

Response

{
  "reaction_counts": {
    "like": 3
  }
}

Agora-Feed

get/v1/communities/{communityId}/actors/{actorId}/feedAuth

Feed for an actor: their own posts + posts from actors they follow, with blocks excluded. Default order is `ranked` (recency + engagement + follow-graph signals); pass `order=chronological` to get the time-ordered feed. 50/page (max 200). The cursor shape differs between orders — passing a chronological cursor with `order=ranked` returns 400 INVALID_CURSOR.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Viewer agora actor UUID — feed is candidate-pooled to their follow-graph.

Query parameters

limitinteger
cursorstring

Opaque cursor; not interchangeable between `ranked` and `chronological` modes.

orderenum (2)

Response · 200 Page of posts (only `published`, non-expired, visibility-filtered).

data*array
pagination*object

Cursor encodes `(score, id)` for `ranked` feeds and `(created_at, id)` for `chronological` feeds. Cursors are NOT interchangeable between modes.

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/feed
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": null,
      "body": "Stories are live!",
      "url": null,
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {
        "like": 12
      },
      "comment_count": 4,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {}
}
get/v1/communities/{communityId}/discover-feedAuth

Discover / explore feed (task 019). Ranked surface over every public post in the community — Instagram Explore / X "For You without follow constraint". Optional `viewer_actor_id` filters the viewer's blocks and applies a small self-boost; without one, the result is the global discovery slice.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Query parameters

viewer_actor_idstring · uuid
limitinteger
cursorstring

Opaque ranked cursor; encodes (score, id).

Response · 200 Page of posts.

data*array
pagination*object

Cursor encodes `(score, id)` for `ranked` feeds and `(created_at, id)` for `chronological` feeds. Cursors are NOT interchangeable between modes.

Example

Request

GET /v1/communities/{communityId}/discover-feed
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": null,
      "body": "Stories are live!",
      "url": null,
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {
        "like": 12
      },
      "comment_count": 4,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {}
}
get/v1/communities/{communityId}/lists/{listId}/feedAuth

List-scoped feed (task 018). Returns posts authored by members of the list, filtered by what the viewer can see. Default order is `chronological`; pass `order=ranked` for the same engagement-blended scoring used by the actor feed.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

listId*string

List UUID.

Query parameters

viewer_actor_id*string · uuid

Viewer — required for visibility checks + block-filter.

limitinteger
cursorstring

Opaque cursor; differs in shape between ranked and chronological.

orderenum (2)

Response · 200 Page of posts.

data*array
pagination*object

Cursor encodes `(score, id)` for `ranked` feeds and `(created_at, id)` for `chronological` feeds. Cursors are NOT interchangeable between modes.

Example

Request

GET /v1/communities/{communityId}/lists/{listId}/feed
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": null,
      "body": "Stories are live!",
      "url": null,
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {
        "like": 12
      },
      "comment_count": 4,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {}
}

Agora-Lists

get/v1/communities/{communityId}/listsAuth

List discoverable lists in this community. Public lists are visible to anyone; private lists surface only when `viewer_actor_id` is their owner.

Path parameters

communityId*string

Agora community UUID.

Query parameters

viewer_actor_idstring · uuid

Surface the viewer's own private lists alongside the public catalog.

owner_actor_idstring · uuid

Filter to lists owned by this actor.

limitinteger
cursorstring

Response · 200 Page of lists.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/lists
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "data": [
        {
          "id": "aaaaaaaa-0000-0000-0000-000000000001",
          "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
          "external_id": "user-42",
          "display_name": "Alice",
          "avatar_url": "string",
          "metadata": {},
          "status": "active",
          "shadow_banned_at": "string",
          "shadow_banned_until": "string",
          "shadow_banned_reason": "string",
          "created_at": "2026-05-01T12:00:00.000Z",
          "updated_at": "2026-05-01T12:00:00.000Z"
        }
      ],
      "pagination": {
        "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
        "has_more": true
      }
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/listsAuth

Create a new list. Default visibility `private`.

Path parameters

communityId*string

Agora community UUID.

Request body

owner_id*string

List owner — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

name*string

Display name (1..200).

Example: "Tech news"

descriptionstring

Optional description (0..2000).

visibilityenum (2)

Default `private`.

Example: "public"

Response · 201 List created.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

POST /v1/communities/{communityId}/lists
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "owner_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "name": "Tech news",
  "description": "string",
  "visibility": "public"
}

Response

{
  "data": [
    {
      "id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "external_id": "user-42",
      "display_name": "Alice",
      "avatar_url": "string",
      "metadata": {},
      "status": "active",
      "shadow_banned_at": "string",
      "shadow_banned_until": "string",
      "shadow_banned_reason": "string",
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
get/v1/communities/{communityId}/lists/{listId}Auth

Get one list by id.

Path parameters

communityId*string

Agora community UUID.

listId*string

List UUID.

Query parameters

viewer_actor_idstring · uuid

Required to see a private list (must equal owner).

Response · 200 List.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/lists/{listId}
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "external_id": "user-42",
      "display_name": "Alice",
      "avatar_url": "string",
      "metadata": {},
      "status": "active",
      "shadow_banned_at": "string",
      "shadow_banned_until": "string",
      "shadow_banned_reason": "string",
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
patch/v1/communities/{communityId}/lists/{listId}Auth

Update list name / description / visibility. Owner-only.

Path parameters

communityId*string

Agora community UUID.

listId*string

List UUID.

Request body

caller_actor_id*string

Caller — agora actor UUID. Must equal the list owner.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

namestring

New name.

descriptionstring

New description.

visibilityenum (2)

New visibility.

Response · 200 List updated.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

PATCH /v1/communities/{communityId}/lists/{listId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "name": "string",
  "description": "string",
  "visibility": "public"
}

Response

{
  "data": [
    {
      "id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "external_id": "user-42",
      "display_name": "Alice",
      "avatar_url": "string",
      "metadata": {},
      "status": "active",
      "shadow_banned_at": "string",
      "shadow_banned_until": "string",
      "shadow_banned_reason": "string",
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
delete/v1/communities/{communityId}/lists/{listId}Auth

Delete a list. Owner-only.

Path parameters

communityId*string

Agora community UUID.

listId*string

List UUID.

Request body

caller_actor_id*string

Caller — agora actor UUID. Must equal the list owner.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

List deleted.

Example

Request

DELETE /v1/communities/{communityId}/lists/{listId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}
get/v1/communities/{communityId}/lists/{listId}/membersAuth

List members of a list. Public lists are visible to anyone; private lists surface only when viewer_actor_id is the owner.

Path parameters

communityId*string

Agora community UUID.

listId*string

List UUID.

Query parameters

viewer_actor_idstring · uuid
limitinteger
cursorstring

Response · 200 Page of members.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/lists/{listId}/members
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "list_id": "llllllll-0000-0000-0000-000000000001",
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "added_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/lists/{listId}/membersAuth

Add a member to a list. Owner-only.

Path parameters

communityId*string

Agora community UUID.

listId*string

List UUID.

Request body

caller_actor_id*string

Group admin performing the add — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

actor_id*string

The new member — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000004"

roleenum (2)

Default `member`.

Example: "member"

Response · 201 Member added (or returned via idempotent retry).

list_id*string

Owning list UUID.

Example: "llllllll-0000-0000-0000-000000000001"

actor_id*string

Member actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000002"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

added_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/lists/{listId}/members
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000004",
  "role": "member"
}

Response

{
  "list_id": "llllllll-0000-0000-0000-000000000001",
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "added_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/lists/{listId}/members/{actorId}Auth

Remove a member from a list. Owner-only. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID.

listId*string

List UUID.

actorId*string

Member — agora actor UUID.

Request body

caller_actor_id*string

Agora actor UUID of the acting party. Equal to the target for self-leave.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

Member removed.

Example

Request

DELETE /v1/communities/{communityId}/lists/{listId}/members/{actorId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}

Agora-Muted-Terms

get/v1/communities/{communityId}/actors/{actorId}/muted-termsAuth

List the actor's currently-active muted terms.

Path parameters

communityId*string

Agora community UUID.

actorId*string

Agora actor UUID.

Response · 200 Active muted terms.

Array of object

actor_id*string

Owning actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

term*string

Case-insensitive substring.

Example: "spoiler"

scope*enum (4)

Where the mute applies.

Example: "all"

expires_at*string

ISO timestamp; null = never expires.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/muted-terms
Authorization: Bearer YOUR_TOKEN

Response

[
  {
    "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "term": "spoiler",
    "scope": "all",
    "expires_at": null,
    "created_at": "2026-05-01T12:00:00.000Z"
  }
]
post/v1/communities/{communityId}/actors/{actorId}/muted-termsAuth

Add (or refresh) a muted term for the actor. Re-posting the same `term` updates the scope + expires_at.

Path parameters

communityId*string

Agora community UUID.

actorId*string

Agora actor UUID.

Request body

term*string

Term to mute. 1..200 chars after trim.

Example: "spoiler"

scopeenum (4)

Default `all`.

expires_atstring

Optional expiry (ISO timestamp in the future).

Response · 201 Muted term applied.

actor_id*string

Owning actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

term*string

Case-insensitive substring.

Example: "spoiler"

scope*enum (4)

Where the mute applies.

Example: "all"

expires_at*string

ISO timestamp; null = never expires.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/actors/{actorId}/muted-terms
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "term": "spoiler",
  "scope": "feed",
  "expires_at": "string"
}

Response

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "term": "spoiler",
  "scope": "all",
  "expires_at": null,
  "created_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/actors/{actorId}/muted-terms/{term}Auth

Remove a muted term. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID.

actorId*string

Agora actor UUID.

term*string

URL-encoded term.

Response · 204

Muted term removed.


Agora-Flags

post/v1/communities/{communityId}/flagsAuth

File a moderation report against a post or comment. Idempotent per (reporter, target) while open.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

reporter_actor_id*string

Agora actor UUID who is reporting the content.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

target_kind*enum (2)

What kind of object is being flagged.

Example: "post"

target_id*string

UUID of the post or comment being flagged.

Example: "pppppppp-pppp-pppp-pppp-pppppppppppp"

reasonstring

Optional free-form reason (up to 1000 chars).

Example: "Spam — same link posted 6 times in 5 minutes."

Response · 201 Flag created (or de-duplicated to existing open flag).

id*string

Flag UUID.

Example: "ffffffff-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

target_kind*enum (2)

What kind of object is being flagged.

Example: "post"

target_id*string

UUID of the post or comment being flagged.

Example: "pppppppp-pppp-pppp-pppp-pppppppppppp"

reporter_actor_id*string

Agora actor UUID who reported.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

reason*string

Free-text reason; empty string when none provided.

Example: "Spam — same link posted 6 times in 5 minutes."

status*enum (3)

Lifecycle. `open` is in the queue; admins close to `dismissed` or `actioned`.

Example: "open"

created_at*string

ISO timestamp the row was inserted.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp of the last update.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/flags
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "reporter_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "target_kind": "post",
  "target_id": "pppppppp-pppp-pppp-pppp-pppppppppppp",
  "reason": "Spam — same link posted 6 times in 5 minutes."
}

Response

{
  "id": "ffffffff-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "target_kind": "post",
  "target_id": "pppppppp-pppp-pppp-pppp-pppppppppppp",
  "reporter_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "reason": "Spam — same link posted 6 times in 5 minutes.",
  "status": "open",
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}

Agora-Notifications

get/v1/communities/{communityId}/actors/{actorId}/notificationsAuth

List notifications for an actor (newest first).

Reverse-chronological inbox. Pass `unread_only=true` to filter to unread rows only. Cursor encodes `(created_at, id)` and round-trips through the response. Default page size 50, max 200. Suppression of self-actions and blocked-actor pairs happens at write time, so every row returned here has already passed those filters.

Path parameters

communityId*string

Community UUID.

actorId*string

Agora actor UUID (returned from POST /actors).

Query parameters

limitinteger

Items per page (1..200, default 50).

cursorstring

Opaque base64url cursor returned by the prior page.

unread_onlyboolean

When `true`, returns only rows where `read_at IS NULL`.

statusenum (3)

Filter slice. `inbox` (default) hides suppressed rows; `requests` shows only suppressed rows (notifications from actors the viewer has restricted); `all` merges both.

Response · 200 Page of notifications.

data*array

Page of notifications, newest first.

pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiIxMTExMSJ9"

has_more*boolean

Whether more pages exist after this one.

Example: false

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/notifications
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "11111111-1111-1111-1111-111111111111",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "recipient_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "kind": "reaction",
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "target_kind": "post",
      "target_id": "bbbbbbbb-0000-0000-0000-000000000001",
      "payload": {
        "type": "like"
      },
      "read_at": null,
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiIxMTExMSJ9",
    "has_more": false
  }
}
get/v1/communities/{communityId}/actors/{actorId}/notifications/unread-countAuth

Count unread notifications for an actor.

Cheap badge endpoint backed by a partial index — safe to poll at the cadence of an in-product notification bell.

Path parameters

communityId*string

Community UUID.

actorId*string

Agora actor UUID (returned from POST /actors).

Response · 200 Count of unread rows.

count*number

Number of unread notifications for the actor.

Example: 7

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/notifications/unread-count
Authorization: Bearer YOUR_TOKEN

Response

{
  "count": 7
}
patch/v1/communities/{communityId}/actors/{actorId}/notifications/{notificationId}Auth

Mark a single notification as read or unread.

Idempotent: marking an already-read row read again is a no-op 204. Returns 404 if the notification belongs to a different actor.

Path parameters

communityId*string

Community UUID.

actorId*string

Agora actor UUID (returned from POST /actors).

notificationId*string

Notification UUID returned from the list endpoint.

Request body

read*boolean

`true` to mark read, `false` to mark unread.

Example: true

Response · 204

Read state updated.

Example

Request

PATCH /v1/communities/{communityId}/actors/{actorId}/notifications/{notificationId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "read": true
}
post/v1/communities/{communityId}/actors/{actorId}/notifications/read-allAuth

Mark every unread notification for the actor as read.

Single bulk write. Optional `before` ISO timestamp scopes to rows older than that — useful for "mark all older than today as read" UX. Returns the number of rows that flipped.

Path parameters

communityId*string

Community UUID.

actorId*string

Agora actor UUID (returned from POST /actors).

Request body

beforestring

Optional ISO timestamp; only flips rows older than this. Omit to flip all unread.

Example: "2026-05-01T00:00:00.000Z"

Response · 200 Bulk update applied; `updated` is the row count.

updated*number

Number of rows whose `read_at` flipped from null to now().

Example: 12

Example

Request

POST /v1/communities/{communityId}/actors/{actorId}/notifications/read-all
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "before": "2026-05-01T00:00:00.000Z"
}

Response

{
  "updated": 12
}

Agora-Notification-Preferences

get/v1/communities/{communityId}/actors/{actorId}/notification-prefsAuth

List the actor's per-kind notification preferences. Synthesises `enabled=true` defaults for kinds the actor has not explicitly toggled.

Returns one row per known NotificationKind so the client UI can render every toggle without a separate catalogue lookup. Synthesised rows carry an epoch `updated_at`; explicit rows carry the real timestamp.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID (returned from POST /actors).

Response · 200 Per-kind preferences.

data*array

One row per known NotificationKind. Kinds the actor has not explicitly toggled report `enabled=true` and an epoch `updated_at`, so the client always sees the full catalogue with effective state.

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/notification-prefs
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "kind": "follow",
      "enabled": true,
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ]
}
patch/v1/communities/{communityId}/actors/{actorId}/notification-prefs/{kind}Auth

Set a single notification-kind preference for the actor.

Idempotent upsert. Pass `{ enabled: false }` to mute, `{ enabled: true }` to re-enable. Cache is invalidated on write so the next NotificationService.tryEmit picks up the change immediately. Returns the resulting preference row.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

kind*string

NotificationKind label (`follow` | `reaction` | `comment_on_post` | `reply_to_comment` | `mention` | `vote`).

Request body

enabled*boolean

`true` to receive notifications of this kind, `false` to mute. Default behaviour (no row) is `true`.

Example: false

Response · 200 Preference upserted.

actor_id*string

Agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

kind*enum (8)

NotificationKind this row applies to.

Example: "follow"

enabled*boolean

When `false`, the producer silently skips notifications of this kind for this actor. Default `true`.

Example: true

updated_at*string

ISO timestamp of the last update. Synthesised entries (no explicit row) report epoch.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/actors/{actorId}/notification-prefs/{kind}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "enabled": false
}

Response

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "kind": "follow",
  "enabled": true,
  "updated_at": "2026-05-01T12:00:00.000Z"
}

Agora-Suggestions

get/v1/communities/{communityId}/actors/{actorId}/suggested-followsAuth

Suggested follows for an actor.

Friend-of-a-friend signal first (people the requester's followees also follow), with a cold-start fallback to top actors in the community by follower count when FoF underfills the limit. Excludes self, existing follows / mutes / blocks, and actors who have blocked the requester. Cached 1h per (actor, limit).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID for whom to compute suggestions.

Query parameters

limitinteger

Items to return; capped at 50.

Response · 200 Ordered candidates.

data*array

Ordered candidates. FoF candidates are returned first (highest count first); cold-start fallbacks fill any remaining slots up to the requested limit.

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/suggested-follows
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "actor": {
        "id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
        "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
        "external_id": "user_abc123",
        "display_name": "Ada Lovelace",
        "avatar_url": "https://cdn.example.com/avatars/ada.png",
        "metadata": {
          "tier": "pro"
        },
        "status": "active",
        "created_at": "2026-05-01T12:00:00.000Z",
        "updated_at": "2026-05-01T12:00:00.000Z"
      },
      "score": 4,
      "reason": "followed_by_friends"
    }
  ]
}

Agora-Bookmarks

post/v1/communities/{communityId}/posts/{postId}/bookmarkAuth

Bookmark a post on behalf of an actor. Idempotent — returns the existing row with `created: false` on retry.

Visibility-aware: the actor must be allowed to read the post. If they can't, returns 404 (opaque), matching the read-path opacity rule used everywhere else in agora.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID.

Request body

actor_id*string

Agora actor UUID who is bookmarking — the customer-backend supplies the end-user identity here.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 201 Bookmark created (or returned via idempotent retry).

bookmark*object
actor_id*string

Agora actor UUID who saved the post.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

post_id*string

Saved post UUID.

Example: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

created_at*string

ISO timestamp the bookmark was created.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` on first call (row inserted); `false` on idempotent retry of an existing bookmark.

Example: true

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/bookmark
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}

Response

{
  "bookmark": {
    "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true
}
delete/v1/communities/{communityId}/posts/{postId}/bookmarkAuth

Remove a bookmark on behalf of an actor. Idempotent — 204 either way.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

postId*string

Agora post UUID.

Request body

actor_id*string

Agora actor UUID who is bookmarking — the customer-backend supplies the end-user identity here.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

Bookmark removed (or was already absent).

Example

Request

DELETE /v1/communities/{communityId}/posts/{postId}/bookmark
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}
get/v1/communities/{communityId}/actors/{actorId}/bookmarksAuth

List the actor's bookmarks. Cursor-paginated newest-first.

Bookmarks are private — only the bookmarker should see their own list. The PAK lane has no end-user principal; the customer's backend must scope this call to the right end-user before exposing the response. Bookmarks against posts whose visibility has narrowed since (or which have been deleted) stay in the list — we don't cross-check post visibility on read of bookmarks.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID.

Query parameters

limitinteger
cursorstring

Opaque cursor from `pagination.next_cursor`.

Response · 200 Page of bookmarks.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/bookmarks
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}

Agora-Mentions

get/v1/communities/{communityId}/actors/{actorId}/mentionsAuth

List every place this actor has been @-mentioned (posts + comments).

Cursor-paginated, newest-first. Interleaves post + comment mentions in a single feed; the `source` field discriminates which surface each row came from. The `post_id` field is always set so the client can deep-link, even for comment mentions.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

actorId*string

Agora actor UUID whose mentions to list.

Query parameters

limitinteger
cursorstring

Opaque cursor from `pagination.next_cursor`.

Response · 200 Page of mention entries.

data*array

Interleaved post + comment mentions, newest-first. Cursor is opaque base64url(JSON({created_at, source, source_id})).

pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/mentions
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "source": "post",
      "source_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "post_id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "mentioned_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}

Agora-Stories

get/v1/communities/{communityId}/actors/{actorId}/story-trayAuth

Story tray for an actor — one entry per author with at least one unexpired or pinned story.

Includes the requester themselves and every actor they follow. Ordered by latest story timestamp. Capped at 200 entries (no cursor in v1; tray is bounded by follow-graph in normal usage). Returns `has_unviewed=true` when the requester has not recorded a `view` edge against the author's most recent story.

Path parameters

communityId*string

Community UUID.

actorId*string

Agora actor UUID (returned from POST /actors).

Response · 200 Story tray entries.

data*array

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/story-tray
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "actor_external_id": "alice",
      "actor_display_name": "Alice",
      "actor_avatar_url": "https://...",
      "latest_story_created_at": "2026-05-01T11:00:00.000Z",
      "story_count": 3,
      "has_unviewed": true
    }
  ]
}
get/v1/communities/{communityId}/actors/{actorId}/highlightsAuth

List the actor's pinned posts (highlights).

Pinned posts appear here regardless of their `expires_at`. Visibility still applies — pass `requester_id` to surface non-public pinned posts the requester is allowed to see. Without that param, only `public` pinned rows are returned (legacy-safe default). Cursor-paginated on `(created_at DESC, id DESC)`.

Path parameters

communityId*string

Community UUID.

actorId*string

Agora actor UUID whose highlights to list.

Query parameters

limitinteger

Items per page.

cursorstring

Cursor from the prior page.

requester_idstring

Requester's external_id. Without it, only public pinned posts surface.

Response · 200 Page of highlights.

data*array

Page of pinned posts.

Example: []

pagination*object
next_cursor*string

Opaque base64url cursor; null when no further pages.

Example: null

has_more*boolean

Whether more pages exist after this one.

Example: false

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/highlights
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [],
  "pagination": {
    "next_cursor": null,
    "has_more": false
  }
}
post/v1/communities/{communityId}/posts/{postId}/viewsAuth

Record a view of a post (idempotent).

Idempotent on `(community, viewer, post)`. 204 on success or no-op duplicate. 422 on self-view (you cannot view your own story). 404 if the post is hidden by visibility, expired, removed, or if a block exists between viewer and author. Does NOT fire a notification — the author sees views via the viewers endpoint instead.

Path parameters

communityId*string

Community UUID.

postId*string

Story (post) UUID.

Request body

viewer_actor_id*string

Viewer agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000002"

Response · 204

View recorded (or already existed).

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/views
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "viewer_actor_id": "aaaaaaaa-0000-0000-0000-000000000002"
}
get/v1/communities/{communityId}/posts/{postId}/viewersAuth

List viewers of a post (author-only).

Only the post author can list viewers. The PAK passes `requester_id`; if that resolves to an actor whose id != post.actor_id, returns 403. Viewers are ordered by view timestamp, newest-first.

Path parameters

communityId*string

Community UUID.

postId*string

Story (post) UUID.

Query parameters

requester_id*string

Must resolve to the post author.

limitinteger
cursorstring

Cursor from the prior page.

Response · 200 Page of viewers.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor; null when no further pages.

Example: null

has_more*boolean

Whether more pages exist after this one.

Example: false

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/viewers
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "viewer_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "viewer_external_id": "bob",
      "viewer_display_name": "Bob",
      "viewer_avatar_url": "https://...",
      "viewed_at": "2026-05-01T11:30:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": null,
    "has_more": false
  }
}
post/v1/communities/{communityId}/close-friendsAuth

Add a member to an actor's close-friends list.

Idempotent on `(community, owner, member)`. 422 on self-add. 409 if a block exists between the pair in either direction. Visibility-`close_friends` posts authored by `owner` become visible to `member` after this call.

Path parameters

communityId*string

Community UUID.

Request body

owner_actor_id*string

Owner agora actor UUID (the actor whose close-friends list this is).

Example: "aaaaaaaa-0000-0000-0000-000000000001"

member_actor_id*string

Member agora actor UUID (the actor being added).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

Response · 204

Member added (or already present).

Example

Request

POST /v1/communities/{communityId}/close-friends
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "owner_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "member_actor_id": "aaaaaaaa-0000-0000-0000-000000000002"
}
delete/v1/communities/{communityId}/close-friends/{ownerActorId}/{memberActorId}Auth

Remove a member from an actor's close-friends list.

Idempotent: 204 whether or not the edge exists. After removal, `close_friends`-visibility posts by `owner` no longer surface to `member`.

Path parameters

communityId*string

Community UUID.

ownerActorId*string

Owner agora actor UUID.

memberActorId*string

Member agora actor UUID.

Response · 204

Removed (or was not in the list).

get/v1/communities/{communityId}/actors/{actorId}/close-friendsAuth

List the close-friends members of an actor.

Returns the actors on `externalId`'s close-friends list, with denormalised actor metadata. Cursor paginated.

Path parameters

communityId*string

Community UUID.

actorId*string

Owner agora actor UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of close friends.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor; null when no further pages.

Example: null

has_more*boolean

Whether more pages exist after this one.

Example: false

Example

Request

GET /v1/communities/{communityId}/actors/{actorId}/close-friends
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "owner_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "member_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
      "member_external_id": "bob",
      "member_display_name": "Bob",
      "member_avatar_url": "https://...",
      "created_at": "2026-05-01T11:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": null,
    "has_more": false
  }
}
post/v1/communities/{communityId}/posts/{postId}/votesAuth

Cast or change a vote on a poll.

Last-vote-wins: an actor changing their vote replaces the prior one. 422 if the post has no poll, if `option_index` is out of range, or on self-vote of own poll. 409 if a block exists between voter and post author. Fires a `vote` notification to the post author.

Path parameters

communityId*string

Community UUID.

postId*string

Post UUID containing the poll.

Request body

actor_id*string

Voter agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000002"

option_index*number

Zero-based index into post.attributes.poll.options.

Example: 1

Response · 204

Vote recorded or replaced.

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/votes
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "option_index": 1
}
get/v1/communities/{communityId}/posts/{postId}/poll-resultsAuth

Aggregate poll results for a post.

Returns counts per option, zero-filling options that received no votes, plus `total_votes`. The `question` and option `label` strings are echoed from `post.attributes.poll`.

Path parameters

communityId*string

Community UUID.

postId*string

Post UUID containing the poll.

Response · 200 Aggregated poll results.

post_id*string

Post UUID the poll lives on.

Example: "bbbbbbbb-0000-0000-0000-000000000001"

question*string

Poll question, copied from post.attributes.poll.question.

Example: "Best dev tool?"

options*array
total_votes*number

Sum of counts across every option.

Example: 67

Example

Request

GET /v1/communities/{communityId}/posts/{postId}/poll-results
Authorization: Bearer YOUR_TOKEN

Response

{
  "post_id": "bbbbbbbb-0000-0000-0000-000000000001",
  "question": "Best dev tool?",
  "options": [
    {
      "index": 0,
      "label": "VS Code",
      "count": 42
    }
  ],
  "total_votes": 67
}

Agora-Conversations

get/v1/communities/{communityId}/conversations/unread-countAuth

Global unread message count for the caller across every conversation in this community.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Query parameters

actor_id*string · uuid

The viewer actor UUID.

Response · 200 Unread count.

count*number

Number of unread incoming messages across every conversation the actor participates in.

Example: 7

Example

Request

GET /v1/communities/{communityId}/conversations/unread-count
Authorization: Bearer YOUR_TOKEN

Response

{
  "count": 7
}
get/v1/communities/{communityId}/conversationsAuth

List the caller's conversations in this community, most-recent-activity first.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Query parameters

actor_id*string · uuid

The viewer actor UUID.

limitinteger
cursorstring
statusenum (3)

Filter slice. `inbox` (default) excludes conversations whose other participant the viewer has restricted; `requests` shows only those; `all` merges both.

Response · 200 Page of conversations.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/conversations
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "11111111-2222-3333-4444-555555555555",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "kind": "direct",
      "name": null,
      "created_by": null,
      "participants": [
        {
          "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
          "joined_at": "2026-05-01T12:00:00.000Z",
          "last_read_at": null,
          "muted": false,
          "role": "member"
        }
      ],
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z",
      "last_message_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/conversationsAuth

Open a conversation. Default `kind="direct"` is the idempotent 1-on-1 path. Pass `kind="group"` with `actor_ids` for a multi-participant chat (always creates a new row; no idempotency for groups).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

Request body

actor_id*string

Agora actor UUID opening the conversation.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

kindenum (2)

Conversation shape. Default `direct`.

Example: "direct"

recipient_actor_idstring

For 1-on-1 (`kind="direct"`): the other participant — agora actor UUID. Required when kind=direct.

Example: "aaaaaaaa-0000-0000-0000-000000000002"

actor_idsarray

For groups (`kind="group"`): initial members (excluding the creator). Required when kind=group. Capped by `community.settings.max_group_conversation_size` (default 50).

Example: ["aaaaaaaa-0000-0000-0000-000000000002","aaaaaaaa-0000-0000-0000-000000000003"]

namestring

Optional group display name. Ignored for 1-on-1.

Response · 201 Conversation created or (1-on-1 only) returned.

id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*enum (2)

Conversation shape — 1-on-1 (`direct`) or multi-participant (`group`).

Example: "direct"

name*string

Group display name; null for 1-on-1.

Example: null

created_by*string

Agora actor UUID of the group founder; null for legacy / 1-on-1.

Example: null

participants*array

Participants, joined-at ascending.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

last_message_at*string

ISO timestamp of the most recent message (or conversation create when no messages yet).

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/conversations
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "kind": "direct",
  "recipient_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
  "actor_ids": [
    "aaaaaaaa-0000-0000-0000-000000000002",
    "aaaaaaaa-0000-0000-0000-000000000003"
  ],
  "name": "string"
}

Response

{
  "id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "kind": "direct",
  "name": null,
  "created_by": null,
  "participants": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "joined_at": "2026-05-01T12:00:00.000Z",
      "last_read_at": null,
      "muted": false,
      "role": "member"
    }
  ],
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "last_message_at": "2026-05-01T12:00:00.000Z"
}
post/v1/communities/{communityId}/conversations/{conversationId}/membersAuth

Add a member to a group conversation. Admin-only. Refuses if the new member has a block with any existing participant.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Request body

caller_actor_id*string

Group admin performing the add — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

actor_id*string

The new member — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000004"

roleenum (2)

Default `member`.

Example: "member"

Response · 201 Member added.

id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*enum (2)

Conversation shape — 1-on-1 (`direct`) or multi-participant (`group`).

Example: "direct"

name*string

Group display name; null for 1-on-1.

Example: null

created_by*string

Agora actor UUID of the group founder; null for legacy / 1-on-1.

Example: null

participants*array

Participants, joined-at ascending.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

last_message_at*string

ISO timestamp of the most recent message (or conversation create when no messages yet).

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/conversations/{conversationId}/members
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000004",
  "role": "member"
}

Response

{
  "id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "kind": "direct",
  "name": null,
  "created_by": null,
  "participants": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "joined_at": "2026-05-01T12:00:00.000Z",
      "last_read_at": null,
      "muted": false,
      "role": "member"
    }
  ],
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "last_message_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/conversations/{conversationId}/members/{actorId}Auth

Promote / demote a participant. Admin-only.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

actorId*string

Target participant — agora actor UUID.

Request body

caller_actor_id*string

The admin performing the role change — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

role*enum (2)

Target role.

Example: "admin"

Response · 200 Role updated.

id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*enum (2)

Conversation shape — 1-on-1 (`direct`) or multi-participant (`group`).

Example: "direct"

name*string

Group display name; null for 1-on-1.

Example: null

created_by*string

Agora actor UUID of the group founder; null for legacy / 1-on-1.

Example: null

participants*array

Participants, joined-at ascending.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

last_message_at*string

ISO timestamp of the most recent message (or conversation create when no messages yet).

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/conversations/{conversationId}/members/{actorId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "role": "admin"
}

Response

{
  "id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "kind": "direct",
  "name": null,
  "created_by": null,
  "participants": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "joined_at": "2026-05-01T12:00:00.000Z",
      "last_read_at": null,
      "muted": false,
      "role": "member"
    }
  ],
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "last_message_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/conversations/{conversationId}/members/{actorId}Auth

Remove a participant. Admin-only unless caller_actor_id equals the target (self-leave).

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

actorId*string

Target participant — agora actor UUID.

Request body

caller_actor_id*string

Agora actor UUID of the acting party. Equal to the target for self-leave.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

Participant removed.

Example

Request

DELETE /v1/communities/{communityId}/conversations/{conversationId}/members/{actorId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}
patch/v1/communities/{communityId}/conversations/{conversationId}/nameAuth

Rename a group conversation. Admin-only.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Request body

caller_actor_id*string

Agora actor UUID of the renaming admin.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

name*string

New display name. Pass null or empty to clear.

Response · 200 Group renamed.

id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*enum (2)

Conversation shape — 1-on-1 (`direct`) or multi-participant (`group`).

Example: "direct"

name*string

Group display name; null for 1-on-1.

Example: null

created_by*string

Agora actor UUID of the group founder; null for legacy / 1-on-1.

Example: null

participants*array

Participants, joined-at ascending.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

last_message_at*string

ISO timestamp of the most recent message (or conversation create when no messages yet).

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/conversations/{conversationId}/name
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "caller_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "name": "string"
}

Response

{
  "id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "kind": "direct",
  "name": null,
  "created_by": null,
  "participants": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "joined_at": "2026-05-01T12:00:00.000Z",
      "last_read_at": null,
      "muted": false,
      "role": "member"
    }
  ],
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "last_message_at": "2026-05-01T12:00:00.000Z"
}
get/v1/communities/{communityId}/conversations/{conversationId}Auth

Get a single conversation by id.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Query parameters

actor_id*string · uuid

The viewer actor UUID — must be a participant.

Response · 200 Conversation.

id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*enum (2)

Conversation shape — 1-on-1 (`direct`) or multi-participant (`group`).

Example: "direct"

name*string

Group display name; null for 1-on-1.

Example: null

created_by*string

Agora actor UUID of the group founder; null for legacy / 1-on-1.

Example: null

participants*array

Participants, joined-at ascending.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

last_message_at*string

ISO timestamp of the most recent message (or conversation create when no messages yet).

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/conversations/{conversationId}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "kind": "direct",
  "name": null,
  "created_by": null,
  "participants": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "joined_at": "2026-05-01T12:00:00.000Z",
      "last_read_at": null,
      "muted": false,
      "role": "member"
    }
  ],
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "last_message_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/conversations/{conversationId}Auth

Update the caller's participant row — toggle muted today.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Request body

actor_id*string

The participant whose row is being updated.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

mutedboolean

Mute / unmute on the caller's participant row.

Example: true

Response · 200 Conversation after update.

id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*enum (2)

Conversation shape — 1-on-1 (`direct`) or multi-participant (`group`).

Example: "direct"

name*string

Group display name; null for 1-on-1.

Example: null

created_by*string

Agora actor UUID of the group founder; null for legacy / 1-on-1.

Example: null

participants*array

Participants, joined-at ascending.

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

last_message_at*string

ISO timestamp of the most recent message (or conversation create when no messages yet).

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/conversations/{conversationId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "muted": true
}

Response

{
  "id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "kind": "direct",
  "name": null,
  "created_by": null,
  "participants": [
    {
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "joined_at": "2026-05-01T12:00:00.000Z",
      "last_read_at": null,
      "muted": false,
      "role": "member"
    }
  ],
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "last_message_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/conversations/{conversationId}Auth

Leave the conversation. Drops the caller's participant row; the conversation is hard-deleted when the last participant leaves.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Request body

actor_id*string

The participant leaving.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

Caller removed from the conversation.

Example

Request

DELETE /v1/communities/{communityId}/conversations/{conversationId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001"
}
get/v1/communities/{communityId}/conversations/{conversationId}/messagesAuth

List messages in the conversation. Default newest-first cursor. Pass `since` (encoded cursor of the last-seen message) to poll for newer messages instead.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Query parameters

actor_id*string · uuid

The viewer actor UUID — must be a participant.

limitinteger
cursorstring
sincestring

Polling cursor — encoded last-seen message id; returns newer messages in ascending order.

Response · 200 Page of messages.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/conversations/{conversationId}/messages
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "mmmmmmmm-0000-0000-0000-000000000001",
      "conversation_id": "11111111-2222-3333-4444-555555555555",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "sender_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "kind": "text",
      "body": "Hey there.",
      "attachments": [
        {
          "kind": "image",
          "url": "https://cdn.example/attachments/abc.png"
        }
      ],
      "reply_to_id": null,
      "edited_at": null,
      "deleted_at": null,
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/conversations/{conversationId}/messagesAuth

Send a message in the conversation.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Request body

sender_id*string

The sender — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

bodystring

Message body. Required when `kind="text"`. 1..10000 chars after trim.

Example: "Hey!"

kindenum (5)

Example: "text"

attachmentsarray
reply_to_idstring

Threaded inline reply target.

Response · 201 Message sent.

id*string

Message UUID.

Example: "mmmmmmmm-0000-0000-0000-000000000001"

conversation_id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

sender_id*string

Sender actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

kind*enum (5)

Example: "text"

body*string

Message body. Returns `"(deleted)"` when soft-deleted.

Example: "Hey there."

attachments*array
reply_to_id*string

Threaded inline reply target.

Example: null

edited_at*string

ISO timestamp of the last edit; `null` for never-edited.

Example: null

deleted_at*string

ISO timestamp when soft-deleted; `null` until then.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

POST /v1/communities/{communityId}/conversations/{conversationId}/messages
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "sender_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "body": "Hey!",
  "kind": "text",
  "attachments": [
    {
      "kind": "image",
      "url": "https://cdn.example/attachments/abc.png"
    }
  ],
  "reply_to_id": "string"
}

Response

{
  "id": "mmmmmmmm-0000-0000-0000-000000000001",
  "conversation_id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "sender_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "kind": "text",
  "body": "Hey there.",
  "attachments": [
    {
      "kind": "image",
      "url": "https://cdn.example/attachments/abc.png"
    }
  ],
  "reply_to_id": null,
  "edited_at": null,
  "deleted_at": null,
  "created_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}Auth

Edit a message body. Allowed within 5 minutes of send and only by the sender.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

messageId*string

Direct message UUID.

Request body

body*string

Updated body.

Example: "edited"

sender_id*string

The sender — must match the message's sender_id.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 200 Message updated.

id*string

Message UUID.

Example: "mmmmmmmm-0000-0000-0000-000000000001"

conversation_id*string

Conversation UUID.

Example: "11111111-2222-3333-4444-555555555555"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

sender_id*string

Sender actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

kind*enum (5)

Example: "text"

body*string

Message body. Returns `"(deleted)"` when soft-deleted.

Example: "Hey there."

attachments*array
reply_to_id*string

Threaded inline reply target.

Example: null

edited_at*string

ISO timestamp of the last edit; `null` for never-edited.

Example: null

deleted_at*string

ISO timestamp when soft-deleted; `null` until then.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "body": "edited",
  "sender_id": "aaaaaaaa-0000-0000-0000-000000000001"
}

Response

{
  "id": "mmmmmmmm-0000-0000-0000-000000000001",
  "conversation_id": "11111111-2222-3333-4444-555555555555",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "sender_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "kind": "text",
  "body": "Hey there.",
  "attachments": [
    {
      "kind": "image",
      "url": "https://cdn.example/attachments/abc.png"
    }
  ],
  "reply_to_id": null,
  "edited_at": null,
  "deleted_at": null,
  "created_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}Auth

Soft-delete a message. Reserved to the sender; the row stays so the conversation timeline doesn't orphan replies. Reads return body `"(deleted)"`.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

messageId*string

Direct message UUID.

Request body

sender_id*string

The sender — must match the message's sender_id.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

Response · 204

Message soft-deleted.

Example

Request

DELETE /v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "sender_id": "aaaaaaaa-0000-0000-0000-000000000001"
}
post/v1/communities/{communityId}/conversations/{conversationId}/readAuth

Mark messages as read up to a given timestamp (default: now()). Sets the caller's `last_read_at`.

Path parameters

communityId*string

Agora community UUID. Must belong to the PAK's workspace.

conversationId*string

Conversation UUID.

Request body

actor_id*string

The reader — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

read_throughstring

ISO timestamp. Messages with `created_at <= read_through` count as read. Omit to mark every message up to now().

Example: "2026-05-01T12:00:00.000Z"

Response · 204

Read marker updated.

Example

Request

POST /v1/communities/{communityId}/conversations/{conversationId}/read
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "read_through": "2026-05-01T12:00:00.000Z"
}

Agora-Dm-Reactions

get/v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}/reactionsAuth

List the reactions on a direct message. Cursor-paginated, newest-first.

Path parameters

communityId*string

Agora community UUID.

conversationId*string

Conversation UUID.

messageId*string

Direct message UUID.

Query parameters

actor_id*string · uuid

The viewer — must be a participant.

limitinteger
cursorstring

Response · 200 Page of reactions.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}/reactions
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "message_id": "mmmmmmmm-0000-0000-0000-000000000001",
      "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "type": "heart",
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}
post/v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}/reactionsAuth

Add a reaction to a direct message. Idempotent — re-posting the same (actor, type) is a no-op.

Path parameters

communityId*string

Agora community UUID.

conversationId*string

Conversation UUID.

messageId*string

Direct message UUID.

Request body

src_actor_id*string

The reactor — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

type*string

Reaction type (emoji or label, 1..64 chars).

Example: "heart"

Response · 201 Reaction applied (created or pre-existing).

reaction*object
message_id*string

Reacted message UUID.

Example: "mmmmmmmm-0000-0000-0000-000000000001"

actor_id*string

Reactor actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

type*string

Reaction type (emoji or label, 1..64 chars).

Example: "heart"

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

True when this call inserted the row; false on idempotent retry.

Example: true

reaction_counts*object

Denormalised map of reaction type → count after the change.

Example: {"heart":3,"laugh":1}

Example

Request

POST /v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}/reactions
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "type": "heart"
}

Response

{
  "reaction": {
    "message_id": "mmmmmmmm-0000-0000-0000-000000000001",
    "actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "type": "heart",
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true,
  "reaction_counts": {
    "heart": 3,
    "laugh": 1
  }
}
delete/v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}/reactions/{actorId}/{type}Auth

Remove the (actor, type) reaction. Idempotent — calling on a missing reaction returns the current counts.

Path parameters

communityId*string

Agora community UUID.

conversationId*string

Conversation UUID.

messageId*string

Direct message UUID.

actorId*string

Reactor actor UUID.

type*string

Reaction type to remove.

Response · 200 Reaction removed (or already absent).

reaction_counts*object

Denormalised map of reaction type → count after the change.

Example: {"heart":2}

Example

Request

DELETE /v1/communities/{communityId}/conversations/{conversationId}/messages/{messageId}/reactions/{actorId}/{type}
Authorization: Bearer YOUR_TOKEN

Response

{
  "reaction_counts": {
    "heart": 2
  }
}

Agora-Post-Reactions

post/v1/communities/{communityId}/posts/{postId}/reactionsAuth

React to a post. Idempotent: re-posting the same (actor, type) returns the existing edge with `created: false`.

Path parameters

communityId*string

Agora community UUID.

postId*string

Agora post UUID.

Request body

src_actor_id*string

Reactor — agora actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

type*string

Reaction type (1..64 chars).

Example: "like"

Response · 201 Reaction added.

edge*object
id*string

Edge UUID.

Example: "eeeeeeee-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

kind*string

Edge kind. `follow`, `mute`, `block`, `save`, or `reaction:<type>` for post reactions.

Example: "follow"

src_actor_id*string

Source actor UUID.

Example: "aaaaaaaa-0000-0000-0000-000000000001"

dst_actor_id*string

Destination actor UUID (for actor edges).

Example: "aaaaaaaa-0000-0000-0000-000000000002"

dst_post_id*string

Destination post UUID (for reaction / save edges).

Example: null

payload*object

Free-form payload stored on the edge (e.g. mute reason).

Example: {}

created_at*string

ISO timestamp the edge was created.

Example: "2026-05-01T12:00:00.000Z"

created*boolean

`true` on first call; `false` on idempotent retry.

Example: true

reaction_counts*object

Denormalised reaction counts after the write.

Example: {"like":13,"fire":3}

Example

Request

POST /v1/communities/{communityId}/posts/{postId}/reactions
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
  "type": "like"
}

Response

{
  "edge": {
    "id": "eeeeeeee-0000-0000-0000-000000000001",
    "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
    "kind": "follow",
    "src_actor_id": "aaaaaaaa-0000-0000-0000-000000000001",
    "dst_actor_id": "aaaaaaaa-0000-0000-0000-000000000002",
    "dst_post_id": null,
    "payload": {},
    "created_at": "2026-05-01T12:00:00.000Z"
  },
  "created": true,
  "reaction_counts": {
    "like": 13,
    "fire": 3
  }
}
delete/v1/communities/{communityId}/posts/{postId}/reactions/{srcActorId}/{type}Auth

Unreact. Idempotent: returns 200 with current `reaction_counts` even if there was nothing to remove.

Path parameters

communityId*string

Agora community UUID.

postId*string

Agora post UUID.

srcActorId*string

Reactor actor UUID.

type*string

Reaction type label.

Response · 200 Reaction removed (or already absent).

reaction_counts*object

Denormalised reaction counts after the write.

Example: {"like":12}

Example

Request

DELETE /v1/communities/{communityId}/posts/{postId}/reactions/{srcActorId}/{type}
Authorization: Bearer YOUR_TOKEN

Response

{
  "reaction_counts": {
    "like": 12
  }
}

Agora-Hashtag-Posts

get/v1/communities/{communityId}/hashtags/{tag}/postsAuth

List posts using a hashtag. Same wire shape as the regular post list.

Path parameters

communityId*string

Community UUID.

tag*string

Lowercase tag (without `#`).

Query parameters

limitinteger
cursorstring

Response · 200 Page of posts.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/hashtags/{tag}/posts
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
      "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
      "actor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "kind": "text",
      "title": "Shipping Phase 8",
      "body": "Stories are live!",
      "url": "https://cdn.example.com/img.png",
      "attributes": {},
      "visibility": "public",
      "status": "published",
      "reaction_counts": {
        "like": 12,
        "fire": 3
      },
      "comment_count": 4,
      "source_post_id": null,
      "repost_count": 0,
      "quote_count": 0,
      "view_count": 0,
      "edited_at": null,
      "edit_count": 0,
      "expires_at": null,
      "pinned": false,
      "created_at": "2026-05-01T12:00:00.000Z",
      "updated_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}

Agora-Webhooks

get/v1/communities/{communityId}/webhooksAuth

Read the community's active webhook (or null).

Path parameters

communityId*string

Agora community UUID.

Response · 200 Webhook or null.

id*string

Webhook UUID.

Example: "wwwwwwww-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

url*string

Subscriber URL.

Example: "https://example.com/webhooks/agora"

event_types*array

Subscribed event types. Each item is one of the agora event names (e.g. `post.created`, `comment.created`, `edge.follow.created`).

Example: ["post.created","comment.created"]

signing_secret_hint*string

Last 4 chars of the signing secret. Use the full secret returned by create / rotate to verify the X-Pcft-Signature header.

Example: "abcd"

disabled_at*string

ISO timestamp when the webhook was auto-disabled (or null if active).

Example: null

disabled_reason*string

Reason the dispatcher auto-disabled the row.

Example: null

consecutive_failures*number

Consecutive failed attempts since the last success.

Example: 0

last_success_at*string

ISO timestamp of the most recent 2xx response.

Example: null

last_failure_at*string

ISO timestamp of the most recent non-2xx response.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/webhooks
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "wwwwwwww-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "url": "https://example.com/webhooks/agora",
  "event_types": [
    "post.created",
    "comment.created"
  ],
  "signing_secret_hint": "abcd",
  "disabled_at": null,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
post/v1/communities/{communityId}/webhooksAuth

Create the community webhook. One active subscriber per community. Returns the signing secret ONCE — store it.

Path parameters

communityId*string

Agora community UUID.

Request body

url*string

Subscriber URL. Must be `https://`.

Example: "https://example.com/webhooks/agora"

event_types*array

Event types to subscribe to. Currently supported: `post.created`, `post.updated`, `post.reaction.created`, `repost.created`, `comment.created`, `comment.updated`, `comment.reaction.created`, `edge.follow.created`.

Example: ["post.created","comment.created"]

Response · 201 Webhook created.

id*string

Webhook UUID.

Example: "wwwwwwww-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

url*string

Subscriber URL.

Example: "https://example.com/webhooks/agora"

event_types*array

Subscribed event types. Each item is one of the agora event names (e.g. `post.created`, `comment.created`, `edge.follow.created`).

Example: ["post.created","comment.created"]

signing_secret_hint*string

Last 4 chars of the signing secret. Use the full secret returned by create / rotate to verify the X-Pcft-Signature header.

Example: "abcd"

disabled_at*string

ISO timestamp when the webhook was auto-disabled (or null if active).

Example: null

disabled_reason*string

Reason the dispatcher auto-disabled the row.

Example: null

consecutive_failures*number

Consecutive failed attempts since the last success.

Example: 0

last_success_at*string

ISO timestamp of the most recent 2xx response.

Example: null

last_failure_at*string

ISO timestamp of the most recent non-2xx response.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

signing_secret*string

Full plaintext HMAC signing secret. Returned ONCE — store it; subsequent reads only expose the last-4 hint.

Example: "abc...xyz"

Example

Request

POST /v1/communities/{communityId}/webhooks
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "https://example.com/webhooks/agora",
  "event_types": [
    "post.created",
    "comment.created"
  ]
}

Response

{
  "id": "wwwwwwww-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "url": "https://example.com/webhooks/agora",
  "event_types": [
    "post.created",
    "comment.created"
  ],
  "signing_secret_hint": "abcd",
  "disabled_at": null,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "signing_secret": "abc...xyz"
}
get/v1/communities/{communityId}/webhooks/{webhookId}Auth

Read a single webhook by id.

Path parameters

communityId*string

Agora community UUID.

webhookId*string

Webhook UUID.

Response · 200 Webhook.

id*string

Webhook UUID.

Example: "wwwwwwww-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

url*string

Subscriber URL.

Example: "https://example.com/webhooks/agora"

event_types*array

Subscribed event types. Each item is one of the agora event names (e.g. `post.created`, `comment.created`, `edge.follow.created`).

Example: ["post.created","comment.created"]

signing_secret_hint*string

Last 4 chars of the signing secret. Use the full secret returned by create / rotate to verify the X-Pcft-Signature header.

Example: "abcd"

disabled_at*string

ISO timestamp when the webhook was auto-disabled (or null if active).

Example: null

disabled_reason*string

Reason the dispatcher auto-disabled the row.

Example: null

consecutive_failures*number

Consecutive failed attempts since the last success.

Example: 0

last_success_at*string

ISO timestamp of the most recent 2xx response.

Example: null

last_failure_at*string

ISO timestamp of the most recent non-2xx response.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

GET /v1/communities/{communityId}/webhooks/{webhookId}
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "wwwwwwww-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "url": "https://example.com/webhooks/agora",
  "event_types": [
    "post.created",
    "comment.created"
  ],
  "signing_secret_hint": "abcd",
  "disabled_at": null,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
patch/v1/communities/{communityId}/webhooks/{webhookId}Auth

Update webhook url + subscribed event types.

Path parameters

communityId*string

Agora community UUID.

webhookId*string

Webhook UUID.

Request body

urlstring

New subscriber URL.

event_typesarray

New subscription set.

Response · 200 Webhook updated.

id*string

Webhook UUID.

Example: "wwwwwwww-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

url*string

Subscriber URL.

Example: "https://example.com/webhooks/agora"

event_types*array

Subscribed event types. Each item is one of the agora event names (e.g. `post.created`, `comment.created`, `edge.follow.created`).

Example: ["post.created","comment.created"]

signing_secret_hint*string

Last 4 chars of the signing secret. Use the full secret returned by create / rotate to verify the X-Pcft-Signature header.

Example: "abcd"

disabled_at*string

ISO timestamp when the webhook was auto-disabled (or null if active).

Example: null

disabled_reason*string

Reason the dispatcher auto-disabled the row.

Example: null

consecutive_failures*number

Consecutive failed attempts since the last success.

Example: 0

last_success_at*string

ISO timestamp of the most recent 2xx response.

Example: null

last_failure_at*string

ISO timestamp of the most recent non-2xx response.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

Example

Request

PATCH /v1/communities/{communityId}/webhooks/{webhookId}
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json

{
  "url": "string",
  "event_types": [
    "string"
  ]
}

Response

{
  "id": "wwwwwwww-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "url": "https://example.com/webhooks/agora",
  "event_types": [
    "post.created",
    "comment.created"
  ],
  "signing_secret_hint": "abcd",
  "disabled_at": null,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z"
}
delete/v1/communities/{communityId}/webhooks/{webhookId}Auth

Delete the webhook.

Path parameters

communityId*string

Agora community UUID.

webhookId*string

Webhook UUID.

Response · 204

Webhook deleted.

post/v1/communities/{communityId}/webhooks/{webhookId}/rotate-secretAuth

Rotate the signing secret. Also clears any auto-disable flag. Returns the new secret ONCE.

Path parameters

communityId*string

Agora community UUID.

webhookId*string

Webhook UUID.

Response · 200 New secret.

id*string

Webhook UUID.

Example: "wwwwwwww-0000-0000-0000-000000000001"

community_id*string

Owning community UUID.

Example: "cccccccc-cccc-cccc-cccc-cccccccccccc"

url*string

Subscriber URL.

Example: "https://example.com/webhooks/agora"

event_types*array

Subscribed event types. Each item is one of the agora event names (e.g. `post.created`, `comment.created`, `edge.follow.created`).

Example: ["post.created","comment.created"]

signing_secret_hint*string

Last 4 chars of the signing secret. Use the full secret returned by create / rotate to verify the X-Pcft-Signature header.

Example: "abcd"

disabled_at*string

ISO timestamp when the webhook was auto-disabled (or null if active).

Example: null

disabled_reason*string

Reason the dispatcher auto-disabled the row.

Example: null

consecutive_failures*number

Consecutive failed attempts since the last success.

Example: 0

last_success_at*string

ISO timestamp of the most recent 2xx response.

Example: null

last_failure_at*string

ISO timestamp of the most recent non-2xx response.

Example: null

created_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

updated_at*string

ISO timestamp.

Example: "2026-05-01T12:00:00.000Z"

signing_secret*string

Full plaintext HMAC signing secret. Returned ONCE — store it; subsequent reads only expose the last-4 hint.

Example: "abc...xyz"

Example

Request

POST /v1/communities/{communityId}/webhooks/{webhookId}/rotate-secret
Authorization: Bearer YOUR_TOKEN

Response

{
  "id": "wwwwwwww-0000-0000-0000-000000000001",
  "community_id": "cccccccc-cccc-cccc-cccc-cccccccccccc",
  "url": "https://example.com/webhooks/agora",
  "event_types": [
    "post.created",
    "comment.created"
  ],
  "signing_secret_hint": "abcd",
  "disabled_at": null,
  "disabled_reason": null,
  "consecutive_failures": 0,
  "last_success_at": null,
  "last_failure_at": null,
  "created_at": "2026-05-01T12:00:00.000Z",
  "updated_at": "2026-05-01T12:00:00.000Z",
  "signing_secret": "abc...xyz"
}
get/v1/communities/{communityId}/webhooks/{webhookId}/deliveriesAuth

List delivery attempts (newest first).

Path parameters

communityId*string

Agora community UUID.

webhookId*string

Webhook UUID.

Query parameters

limitinteger
cursorstring

Response · 200 Page of deliveries.

data*array
pagination*object
next_cursor*string

Opaque base64url cursor for the next page; `null` when no further pages.

Example: "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0"

has_more*boolean

`true` when at least one more page exists. Mirrors `next_cursor !== null`; published as a separate field so SDKs can drive a `while` loop without parsing the cursor.

Example: true

Example

Request

GET /v1/communities/{communityId}/webhooks/{webhookId}/deliveries
Authorization: Bearer YOUR_TOKEN

Response

{
  "data": [
    {
      "id": "string",
      "webhook_id": "string",
      "event_id": "evt_…",
      "event_type": "post.created",
      "attempt_number": 1,
      "request_body": {},
      "response_status": 200,
      "response_body": "OK",
      "error_message": null,
      "latency_ms": 67,
      "succeeded": true,
      "created_at": "2026-05-01T12:00:00.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNS0wMVQxMjowMDowMC4wMDBaIiwiaWQiOiJiYmJiIn0",
    "has_more": true
  }
}