Communication Mobile API Reference
Integration reference for Communication mobile channel read endpoints.
Communication Mobile API Reference
Audience: Mobile/frontend developers
Scope: Mobile channel + discussion contracts currently implemented.
Mobile APIs
Realtime Integration
Mobile clients can join communication websocket rooms for live channel-post and discussion-message deltas. For room names, join/sync socket events, replay flow, and payload contracts, see Communication Realtime Implementation.
Access vs Join
Communication mobile now distinguishes:
- Access policy: decides whether the user can read the channel right now.
- Join state: decides whether the user is subscribed to broadcast post notifications.
Rules:
OPENchannels remain readable even if the user never joins.EXTERNAL_GATEDchannels require current entitlement to read.- Joining does not grant read access by itself.
- Leaving stops future broadcast notifications, but does not remove content access for
OPENor currently-entitled gated channels. - If a joined gated user loses entitlement, the channel becomes joined but locked and opens into a resubscribe/paywall flow. Posts and discussion stay blocked.
List Channels
Endpoint
GET /api/mobile/channels?cursor=&limit=Auth
- Optional JWT (
OptionalJwtAuthGuard) - Public/open channels can be listed without login.
- Gated/private visibility is still evaluated by access policy.
Query params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
cursor | string | No | eyJjcmVhdGVkQXQiOiI... | Opaque cursor token |
limit | number | No | 20 | Default 20, max 100 |
Response item fields
id,kind,accessPolicy,status,visibility,title,description,createdAt,updatedAtisJoined: whether the authenticated user has joined the channel for notificationsisLocked: whether the channel is joined but currently blocked by gated access loss
Common error scenarios
422: invalid cursor value.
Get Channel Detail
Endpoint
GET /api/mobile/channels/{id}Auth
- Optional JWT (
OptionalJwtAuthGuard) - Access policy enforcement still applies for gated channels.
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
Response
- Returns channel metadata:
id,kind,accessPolicy,status,visibility,title,description,createdAt,updatedAt,isJoined,isLocked
Locked-channel behavior:
- If a joined gated user loses entitlement, detail may still return the channel shell with
isJoined = trueandisLocked = true. - Frontend should open a resubscribe/paywall screen instead of trying to render posts.
Common error scenarios
400: invalid non-numericid.403: user is not entitled to read the channel.404: channel not found.
Join Channel
Endpoint
POST /api/mobile/channels/{id}/joinAuth
Authorization: Bearer <user-jwt>
Behavior
- Supported for broadcast channels only.
- Current channel access must pass before join succeeds.
- Idempotent: joining an already-joined channel returns the active joined state again.
Response
{
"message": "Channel joined successfully",
"data": {
"channelId": 3,
"isJoined": true,
"joinedAt": "2026-03-19T12:00:00.000Z",
"leftAt": null
}
}Leave Channel
Endpoint
POST /api/mobile/channels/{id}/leaveAuth
Authorization: Bearer <user-jwt>
Behavior
- Supported for broadcast channels only.
- Idempotent.
- Works even if the user already lost gated access.
- Leaving stops future broadcast push notifications immediately.
Response
{
"message": "Channel left successfully",
"data": {
"channelId": 3,
"isJoined": false,
"joinedAt": "2026-03-10T10:00:00.000Z",
"leftAt": "2026-03-19T12:30:00.000Z"
}
}List Channel Posts (Cursor Pagination)
Endpoint
GET /api/mobile/channels/{id}/posts?after_id=&before_id=&limit=Auth
- Optional JWT (
OptionalJwtAuthGuard) - Access policy still applies (
OPENchannels can be read publicly,EXTERNAL_GATEDchannels require entitlement decision).
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
Query params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
after_id | string | No | 2 | Return newer posts where id > after_id |
before_id | string | No | 4 | Return older posts where id < before_id |
limit | number | No | 20 | Default 20, max 100 |
Important rules
- Send only one of
after_idorbefore_idin the same request. idis the canonical channel post cursor and is serialized as string.- Channel posts are ordered by
posts.id(insert order), not bypublishedAt. publishedAtremains metadata for display and access-window checks.- Cursor behavior:
next_after_id: newest post id in current page.next_before_id: oldest post id in current page.
How ordering works
after_idmode (catch-up/newer): ascending order by post id.before_idmode (scroll older): descending order by post id.
Real test example (channel 3 has post ids 1, 2, 3)
Case A: Initial newer window
GET /api/mobile/channels/3/posts?after_id=0&limit=2Expected shape:
items: post ids1,2cursor.next_after_id:"2"cursor.next_before_id:"1"has_more:true(because post id3still exists)
Case B: Older page from upper boundary
GET /api/mobile/channels/3/posts?before_id=4&limit=2Expected shape:
items: post ids3,2cursor.next_after_id:"3"cursor.next_before_id:"2"has_more:true(because post id1still exists)
This is why your result is correct:
before_id=4means "give me posts withid < 4".- Returned order is descending for older-scroll mode, so higher ids appear first.
Common error scenarios
400: invalid non-numericid.403: user is not entitled to read the channel.404: channel not found.422: invalid cursor combination/format.
Get Post Detail
Endpoint
GET /api/mobile/posts/{postId}Auth
- Optional JWT (
OptionalJwtAuthGuard) - Channel and post visibility policy checks are still enforced.
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
postId | number | Yes | 101 | Post ID |
Response fields
id,channelId,publishedAt,createdByAdminId,typebodyText,linkUrl,chartPayloadJsonisPinned,pinnedAtisDeleted,deletedAtattachments(structured metadata + resolved URL)id,storageKey,url,fileName,mimeType,sizeBytes,displayOrder
createdAt,updatedAt
Common error scenarios
400: invalid non-numericpostId.403: user is not entitled to read the post.404: post not found.
Get Channel Discussion
Endpoint
GET /api/mobile/channels/{id}/discussionAuth
Authorization: Bearer <user-jwt>
Behavior
- Uses the same access decision as the channel itself.
- If the user cannot access the channel, they cannot access the discussion.
- If the discussion has
requiredFeatureKey, user must have that feature.
Important:
- Joined notification membership does not unlock discussion access.
- A joined gated user in grace period may still receive broadcast post push notifications, but discussion read/post/realtime access remains blocked until entitlement is restored.
Example response
{
"message": "Channel discussion fetched successfully",
"data": {
"id": 1,
"channelId": 3,
"title": "VIP Signals Discussion",
"isEnabled": true,
"requiredFeatureKey": "chat.discussion",
"slowModeEnabled": false,
"slowModeIntervalSeconds": null,
"createdByAdminId": "019ce156-1647-75b9-a5ba-07758a4f7675",
"createdAt": "2026-03-16T10:00:00.000Z",
"updatedAt": "2026-03-16T10:00:00.000Z"
}
}List Discussion Messages
Endpoint
GET /api/mobile/channels/{id}/discussion/messages?before_id=&limit=&view=Auth
Authorization: Bearer <user-jwt>
Query params
| Param | Type | Required | Notes |
|---|---|---|---|
before_id | string | No | Fetch older messages with ids lower than this value |
limit | number | No | Default 20, max 100 |
view | string | No | full (default) or compact |
Example response (full)
{
"message": "Channel discussion messages fetched successfully",
"data": {
"items": [
{
"id": 11,
"discussionId": 1,
"authorType": "ADMIN",
"authorUserId": null,
"authorAdminId": "019ce156-1647-75b9-a5ba-07758a4f7675",
"body": "Wait for confirmation above resistance before entering.",
"isDeleted": false,
"deletedAt": null,
"deletedByAdminId": null,
"editedAt": null,
"editedByUserId": null,
"editedByAdminId": null,
"createdAt": "2026-03-16T10:05:00.000Z",
"updatedAt": "2026-03-16T10:05:00.000Z"
}
],
"cursor": {},
"limit": 20,
"has_more": false,
"count": 1
}
}Example response (compact)
{
"message": "Channel discussion messages fetched successfully",
"data": {
"items": [
{
"id": 11,
"authorType": "ADMIN",
"authorUserId": null,
"authorAdminId": "019ce156-1647-75b9-a5ba-07758a4f7675",
"body": "Wait for confirmation above resistance before entering.",
"isDeleted": false,
"createdAt": "2026-03-16T10:05:00.000Z"
}
],
"cursor": {},
"limit": 20,
"has_more": false,
"count": 1
}
}Create Discussion Message
Endpoint
POST /api/mobile/channels/{id}/discussion/messagesAuth
Authorization: Bearer <user-jwt>
Raw request body
{
"body": "I am waiting for candle confirmation before entry."
}Behavior
- Requires channel access.
- Denied when the user is banned from the discussion.
- Denied when the user is muted.
- Slow mode is enforced for user-authored message creation.
Update Own Discussion Message
Endpoint
PATCH /api/mobile/discussion-messages/{id}Raw request body
{
"body": "Updated: I will wait for confirmation above resistance."
}Delete Own Discussion Message
Endpoint
DELETE /api/mobile/discussion-messages/{id}Update Channel Read State
Endpoint
POST /api/mobile/channels/{id}/read-stateAuth
- JWT required (
JwtAuthGuard) - This endpoint is not public.
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
Raw Request Body
{
"last_seen_post_id": "1200"
}Request field rules
last_seen_post_idmust be a non-negative integer string.
Behavior
- Stores per-user per-channel read cursor.
- Uses monotonic update semantics: server keeps the max value (
GREATEST(old, incoming)), so read cursor never moves backward. - Returns:
channelId,lastSeenPostId,lastSeenAt
Common error scenarios
400: invalid non-numericid.401: missing/invalid JWT.403: user is not entitled to read the channel.404: channel not found.422: invalidlast_seen_post_idformat.