Communication Admin API Reference
Integration reference for Communication admin endpoints.
Communication Admin API Reference
Audience: Admin/frontend developers, API consumers Scope: Admin Communication endpoint contracts.
Admin APIs
Realtime Side Effect
Admin write endpoints for channel posts and discussion messages now emit realtime delta events after DB commit. For exact room names, socket events, and payload contracts, see Communication Realtime Implementation.
Access Policy vs Membership
Communication now separates:
- Access policy: whether the user can read channel content right now
- Membership: whether the user explicitly joined the broadcast channel for notifications
Rules:
OPENchannels are readable without subscription entitlement.EXTERNAL_GATEDchannels require current entitlement through the feature/access provider.- Membership does not grant content access by itself.
- Admin broadcast post push notifications go only to joined members, not to every user who could theoretically read the channel.
- If a joined gated user loses entitlement, content access is blocked immediately, but membership may remain active for a 7-day notification grace window.
Broadcast Notification Audience
For new admin-created broadcast posts:
- notify active joined members only
- do not notify non-members
- do not notify users who explicitly left
- for gated channels:
- currently entitled joined users are notified
- previously joined users inside their 7-day grace window are also notified
- previously joined users after grace expiry are not notified
Push content is generic:
- title: channel title
- body:
New update available - payload data includes
channelId,postId, andnotificationType = "channel_broadcast_post"
Discussion Access Inheritance
Discussion always depends on parent channel access first.
If channel access is denied, then discussion access is denied too. That applies to:
- fetching discussion metadata
- reading discussion messages
- posting discussion messages
- joining discussion realtime rooms
An expired gated member in notification grace may still receive broadcast post push, but discussion access stays blocked until entitlement is restored.
Create Channel
Endpoint
POST /api/admin/channelsAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_CREATE
Idempotency
- Header required:
Idempotency-Key: <unique-key> - Same key + same payload => replay same response
- Same key + different payload =>
409 Conflict - Different key => treated as a new create request
Raw Request Body (Free Channel / OPEN)
{
"kind": "BROADCAST",
"access_policy": "OPEN",
"visibility": "PUBLIC",
"title": "Daily Market Updates",
"description": "Daily market briefs and macro highlights."
}Real-life example
- A public announcement channel where any user can read market updates without subscription checks.
Raw Request Body (Premium Channel / EXTERNAL_GATED)
{
"kind": "BROADCAST",
"access_policy": "EXTERNAL_GATED",
"visibility": "PRIVATE",
"title": "VIP Signals",
"description": "Members-only signals and premium charts.",
"access_policy_config": {
"first_subscribe_history_policy": "NO_PAST",
"resubscribe_backfill_days": 7,
"preserve_prior_entitled_history": true,
"required_feature_key": "chat.broadcast"
}
}Real-life example
- A paid members-only channel. Read access is decided by external entitlement logic (subscription wrapper/provider).
- Users may still need to explicitly join the channel if they want push notifications for new broadcast posts.
Request Field Reference
| Field | What it represents | Type | Required | Example |
|---|---|---|---|---|
kind | Channel type | string | Yes | "BROADCAST" |
access_policy | Access mode | string | Yes | "OPEN" |
visibility | Discovery visibility | string | Yes | "PUBLIC" |
title | Channel display name | string | Yes | "Daily Market Updates" |
description | Optional channel summary | string | No | "Daily market briefs and macro highlights." |
access_policy_config | Premium access-history policy config | object | No | {...} |
access_policy_config.first_subscribe_history_policy | First subscribe history rule | string | No | "NO_PAST" |
access_policy_config.resubscribe_backfill_days | Backfill days on re-subscribe | integer | No | 7 |
access_policy_config.preserve_prior_entitled_history | Keep previously entitled windows visible | boolean | No | true |
access_policy_config.required_feature_key | Subscription feature key required for gated channel reads | string | For EXTERNAL_GATED | "chat.broadcast" |
Allowed Enum Values
kind:BROADCAST,PREMIUM_VIEW_ONLYaccess_policy:OPEN,EXTERNAL_GATEDvisibility:PUBLIC,PRIVATEfirst_subscribe_history_policy:NO_PAST,ALLOW_PAST
Frontend Integration Notes
- Generate a new
Idempotency-Keyper user action (create click). - Reuse the same key only for retrying that same request.
- Do not reuse the same key for a different payload.
Gated Channel Requirement (EXTERNAL_GATED)
POST /api/admin/channels stores the required Subscription feature directly in access_policy_config.required_feature_key.
Rules:
required_feature_keyis required forEXTERNAL_GATED.required_feature_keymust be a known Subscription feature key.- Communication reads the user’s enabled plan features and checks that key directly.
- No separate
channel_access_mappingtable is used.
List Channels
Endpoint
GET /api/admin/channels?status=&kind=&sort=&order=&cursor=&limit=Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_READ
Raw request body
{}Query params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
status | string | No | active | active, archived, soft_deleted |
kind | string | No | BROADCAST | BROADCAST, PREMIUM_VIEW_ONLY |
sort | string | No | createdAt | createdAt, updatedAt |
order | string | No | desc | asc, desc |
cursor | string | No | eyJjcmVhdGVkQXQiOiI... | Opaque pagination cursor |
limit | number | No | 20 | Default 20, max 100 |
Response shape
GET /api/admin/channelsreturns list fields only:id,title,kind,accessPolicy,visibility,status,createdAt,updatedAt- Lifecycle/admin metadata is provided by:
GET /api/admin/channels/:id - Cursor is sort-aware:
the same
cursormust be used with the samesortandordervalues.
Example response
{
"message": "Channels fetched successfully",
"data": {
"items": [
{
"id": 3,
"kind": "PREMIUM_VIEW_ONLY",
"accessPolicy": "EXTERNAL_GATED",
"status": "active",
"visibility": "PRIVATE",
"title": "VIP Signals",
"createdAt": "2026-02-23T08:24:51.135Z",
"updatedAt": "2026-02-23T08:24:51.135Z"
},
{
"id": 1,
"kind": "BROADCAST",
"accessPolicy": "OPEN",
"status": "active",
"visibility": "PUBLIC",
"title": "Daily Market Updates",
"createdAt": "2026-02-23T08:13:44.644Z",
"updatedAt": "2026-02-23T08:13:44.644Z"
}
],
"cursor": {},
"limit": 20,
"has_more": false,
"count": 2
}
}Get Channel Detail
Endpoint
GET /api/admin/channels/{id}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_READ
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 1 | Channel ID |
Raw request body
{}Important terms in detail response
| Field | What it means in simple terms |
|---|---|
createdByAdminId | Which admin originally created this channel |
status | Lifecycle state of the channel: active, archived, or soft_deleted |
archivedAt / archivedByAdminId | When and by which admin the channel was archived |
deletedAt / deletedByAdminId | When and by which admin the channel was soft-deleted |
purgeAfter | Planned time for permanent cleanup (if retention cleanup is configured) |
accessPolicyConfig | Extra entitlement/history rules used for channel access behavior |
accessPolicyConfig.firstSubscribeHistoryPolicy | On first subscription, whether user can see older posts (NO_PAST / ALLOW_PAST) |
accessPolicyConfig.resubscribeBackfillDays | If user re-subscribes, how many previous days of posts become visible |
accessPolicyConfig.preservePriorEntitledHistory | If true, user keeps access to posts from periods they were entitled before |
Important integration note
- Request key style depends on endpoint:
POST /api/admin/channelsusessnake_casekeys likeaccess_policy_config.PATCH /api/admin/channels/:iduses metadata keys likekind,visibility,title,description. - Response payloads use
camelCasekeys (example:accessPolicyConfig).
Example response
{
"message": "Channel fetched successfully",
"data": {
"id": 1,
"kind": "BROADCAST",
"accessPolicy": "OPEN",
"status": "active",
"visibility": "PUBLIC",
"title": "Daily Market Updates",
"description": "Daily market briefs and macro highlights.",
"createdByAdminId": "192c6636-b361-4a4d-9a4d-efb00e694e9b",
"archivedAt": null,
"archivedByAdminId": null,
"deletedAt": null,
"deletedByAdminId": null,
"purgeAfter": null,
"createdAt": "2026-02-23T08:13:44.644Z",
"updatedAt": "2026-02-23T08:13:44.644Z",
"accessPolicyConfig": {
"channelId": 1,
"firstSubscribeHistoryPolicy": "NO_PAST",
"resubscribeBackfillDays": 7,
"preservePriorEntitledHistory": true,
"updatedByAdminId": "192c6636-b361-4a4d-9a4d-efb00e694e9b",
"updatedAt": "2026-02-23T08:13:44.644Z"
}
}
}Update Channel Metadata
Endpoint
PATCH /api/admin/channels/{id}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 1 | Channel ID |
What this API accepts
| Field | Type | Required | Allowed values | Notes |
|---|---|---|---|---|
kind | string | No | BROADCAST, PREMIUM_VIEW_ONLY | Optional channel type change |
visibility | string | No | PUBLIC, PRIVATE | Optional visibility change |
title | string | No | Any non-empty string | Trimmed before save |
description | string | null | No | Any string or null | Send null to clear |
Validation rules
- At least one metadata field is required.
- Empty/blank
titleis rejected. - If channel status is
soft_deleted, update is rejected (422); restore first. - This endpoint does not update
access_policyor lifecyclestatus.
Raw Request Body (Update title + description)
{
"title": "Daily Market Updates v2",
"description": "Updated summary from admin"
}Raw Request Body (Change kind + visibility)
{
"kind": "PREMIUM_VIEW_ONLY",
"visibility": "PRIVATE"
}Raw Request Body (Clear description)
{
"description": null
}Response
- Returns full channel detail payload (same shape as
GET /api/admin/channels/:id).
Common error scenarios
422: empty patch body or invalid field combination.422: soft-deleted channel (must call restore endpoint first).404: channel not found.
Update Channel Access Policy
Endpoint
PATCH /api/admin/channels/{id}/access-policyAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
What this API accepts
| Field | Type | Required | Allowed values / range | Notes |
|---|---|---|---|---|
first_subscribe_history_policy | string | No | NO_PAST, ALLOW_PAST | First-time subscribe history rule |
resubscribe_backfill_days | number | No | Integer 0..3650 | Days of history shown when re-subscribed |
preserve_prior_entitled_history | boolean | No | true, false | Keep history from previously entitled windows |
required_feature_key | string | No | e.g. chat.broadcast | Required when channel access policy is EXTERNAL_GATED |
Validation rules
- At least one policy field is required (
422if body has none). resubscribe_backfill_daysmust be an integer and within0..3650.- If channel access policy is
EXTERNAL_GATED,required_feature_keymust be set. - Returns full channel detail payload (same shape as
GET /api/admin/channels/:id).
Raw Request Body (All fields)
{
"first_subscribe_history_policy": "ALLOW_PAST",
"resubscribe_backfill_days": 14,
"preserve_prior_entitled_history": true
}Raw Request Body (Single field update)
{
"resubscribe_backfill_days": 30
}Common error scenarios
400: invalididpath param (non-numeric).422: empty body (no updatable policy field sent).422: invalid enum/range/type in body.404: channel not found.
Update Channel Status
Endpoint
PATCH /api/admin/channels/{id}/statusAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
What this API accepts
| Field | Type | Required | Allowed values | Notes |
|---|---|---|---|---|
status | string | Yes | active, archived, soft_deleted | Target lifecycle status |
purge_after | string (ISO datetime) | No | e.g. 2026-03-31T00:00:00.000Z | Allowed only when status=soft_deleted |
Lifecycle behavior
archived: sets archive metadata and clears delete metadata.active: clears archive/delete metadata and clearspurgeAfter.soft_deleted: sets delete metadata; can optionally setpurgeAfter.- If channel is already
soft_deleted, this endpoint cannot move it back to active/archive. Use restore endpoint instead.
Validation rules
purge_afteris rejected unlessstatusissoft_deleted(422).- Soft-deleted channel must be revived via restore endpoint (
422on direct status change).
Raw Request Body (Archive)
{
"status": "archived"
}Raw Request Body (Reactivate)
{
"status": "active"
}Raw Request Body (Soft delete with purge schedule)
{
"status": "soft_deleted",
"purge_after": "2026-03-31T00:00:00.000Z"
}Response
- Returns full channel detail payload (same shape as
GET /api/admin/channels/:id).
Common error scenarios
400: invalididpath param (non-numeric).422:purge_afterprovided with non-soft_deletedstatus.422: channel already soft-deleted and requested status change is notsoft_deleted.404: channel not found.
Restore Channel
Endpoint
POST /api/admin/channels/{id}/restoreAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID (must currently be soft_deleted) |
Raw request body
{}Behavior
- Restores a soft-deleted channel back to
active. - Clears deletion lifecycle fields:
deletedAt,deletedByAdminId,purgeAfter. - Returns full channel detail payload (same shape as
GET /api/admin/channels/:id).
Common error scenarios
400: invalididpath param (non-numeric).422: channel is not insoft_deletedstatus.404: channel not found.
Create Channel Post
Endpoint
POST /api/admin/channels/{id}/postsAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_CREATE
Idempotency
- Header required:
Idempotency-Key: <unique-key> - Same key + same payload => replay same response
- Same key + different payload =>
409 Conflict
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID (must be active) |
What this API accepts
| Field | Type | Required | Allowed values / range | Notes |
|---|---|---|---|---|
type | string | Yes | TEXT, LINK, CHART, IMAGE, FILE | Post type |
body_text | string | null | No | non-empty string or null | Required for TEXT |
link_url | string | null | No | URL string or null | Required for LINK |
chart_payload_json | object | null | No | JSON object or null | Required for CHART |
attachments | object[] | No | max 20 items | Required for IMAGE / FILE; each item is structured upload metadata |
published_at | string | No | ISO datetime | Defaults to now |
is_pinned | boolean | No | true, false | Defaults to false |
Type rules
TEXT: must includebody_text; cannot includelink_url,chart_payload_json, orattachments.LINK: must includelink_url; can include optionalbody_text; cannot includechart_payload_jsonorattachments.CHART: must includechart_payload_jsonobject; can include optionalbody_text; cannot includelink_urlorattachments.IMAGE/FILE: must include at least oneattachmentskey; can include optionalbody_text; cannot includelink_urlorchart_payload_json.
Attachment object fields
storage_key(required): upload storage key returned by Upload APIfile_name(optional): original file namemime_type(optional): MIME typesize_bytes(optional): file size in bytes
Raw Request Body (TEXT)
{
"type": "TEXT",
"body_text": "Morning update: BTC reclaimed key resistance.",
"is_pinned": false
}Raw Request Body (IMAGE)
{
"type": "IMAGE",
"body_text": "Chart snapshot",
"attachments": [
{
"storage_key": "uploads/communication/channels/3/posts/img-001.png",
"file_name": "market-setup.png",
"mime_type": "image/png",
"size_bytes": 183024
}
]
}Raw Request Body (CHART)
{
"type": "CHART",
"body_text": "BTC breakout setup",
"chart_payload_json": {
"symbol": "BTCUSDT",
"timeframe": "4H",
"levels": [95200, 97800]
}
}Response
- Returns created post detail with
attachmentsas structured metadata (id, storageKey, fileName, mimeType, sizeBytes, displayOrder).
Common error scenarios
400: missingIdempotency-Keyheader.400: invalididpath param (non-numeric).409: same idempotency key used with different payload.422: invalid field combination by post type.422: channel is notactive.404: channel not found.
Pin / Unpin Channel Post
Endpoint
POST /api/admin/channels/{id}/posts/{postId}/pinAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
postId | number | Yes | 101 | Post ID in the same channel |
Raw Request Body (Pin)
{
"is_pinned": true
}Raw Request Body (Unpin)
{
"is_pinned": false
}Behavior
is_pinned=truesetsisPinned=trueandpinnedAt=<now>.is_pinned=falsesetsisPinned=falseandpinnedAt=null.- Returns full post detail payload.
Common error scenarios
400: invalid numeric path params.404: post not found for providedid+postId.422: cannot pin/unpin a post that is already soft-deleted.
List Channel Posts (Admin)
Endpoint
GET /api/admin/channels/{id}/posts?after_id=&before_id=&include_deleted=&limit=Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_READ
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
Query params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
after_id | string | No | 10 | Fetch posts where id > after_id |
before_id | string | No | 30 | Fetch posts where id < before_id |
include_deleted | boolean | No | true | Default false; include tombstones |
limit | number | No | 20 | Default 20, max 100 |
Cursor behavior
- Use only one of
after_idorbefore_id. after_idmode returns ascending post-id pages.before_idmode returns descending post-id pages.- Channel posts are ordered by
posts.id(insert order), not bypublishedAt. published_at/publishedAtremains metadata, not the pagination cursor key.- If
has_more=false, cursor is empty ({}) by design.
Example request
GET /api/admin/channels/3/posts?before_id=4&limit=2Example response
{
"message": "Channel posts fetched successfully",
"data": {
"items": [
{
"id": 6,
"channelId": 3,
"publishedAt": "2026-02-25T06:17:19.312Z",
"createdByAdminId": "019c8a2e-ad60-75ae-8543-953d64e93489",
"type": "TEXT",
"bodyText": "Post 3 in channel 3",
"linkUrl": null,
"chartPayloadJson": null,
"isPinned": false,
"pinnedAt": null,
"isDeleted": false,
"deletedAt": null,
"deletedByAdminId": null,
"attachments": [],
"createdAt": "2026-02-25T06:17:19.312Z",
"updatedAt": "2026-02-25T06:17:19.312Z"
},
{
"id": 5,
"channelId": 3,
"publishedAt": "2026-02-25T06:17:08.216Z",
"createdByAdminId": "019c8a2e-ad60-75ae-8543-953d64e93489",
"type": "TEXT",
"bodyText": "Post 2 in channel 3",
"linkUrl": null,
"chartPayloadJson": null,
"isPinned": false,
"pinnedAt": null,
"isDeleted": false,
"deletedAt": null,
"deletedByAdminId": null,
"attachments": [],
"createdAt": "2026-02-25T06:17:08.216Z",
"updatedAt": "2026-02-25T06:17:08.216Z"
}
],
"cursor": {
"next_after_id": "3",
"next_before_id": "2"
},
"limit": 2,
"has_more": true,
"count": 2
}
}Common error scenarios
400: invalid channelid.422: bothafter_idandbefore_idsupplied.422: invalid id cursor format.404: channel not found.
Get Channel Post Detail (Admin)
Endpoint
GET /api/admin/posts/{postId}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_READ
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
postId | number | Yes | 101 | Post ID |
Behavior
- Returns full admin post detail.
- If post is soft-deleted, payload remains tombstone-redacted.
Common error scenarios
400: invalidpostId.404: post not found.
Update Channel Post
Endpoint
PATCH /api/admin/posts/{postId}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
postId | number | Yes | 101 | Post ID |
What this API accepts
| Field | Type | Required | Notes |
|---|---|---|---|
type | string | No | TEXT, LINK, CHART, IMAGE, FILE |
body_text | string | null | No | Optional text body |
link_url | string | null | No | Optional link target |
chart_payload_json | object | null | No | Optional chart payload |
attachments | object[] | No | Structured attachment metadata (same shape as create) |
published_at | string | No | ISO datetime |
is_pinned | boolean | No | Pin state |
Validation rules
- At least one field is required.
- Final merged payload must satisfy type rules (same as create post).
- Soft-deleted posts cannot be updated (restore first).
Raw Request Body (Update text + pin)
{
"body_text": "Updated admin copy",
"is_pinned": true
}Common error scenarios
400: invalidpostId.422: empty patch body.422: invalid type/field combination after merge.422: post is deleted (must restore first).404: post not found.
Delete Channel Post (Soft Delete)
Endpoint
DELETE /api/admin/posts/{postId}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_DELETE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
postId | number | Yes | 101 | Post ID |
Raw request body
{}Behavior
- Marks post as deleted (
isDeleted=true,deletedAt,deletedByAdminId). - Post remains as tombstone for feed continuity.
- Response is redacted for deleted posts (
bodyText,linkUrl,chartPayloadJsonbecomenull;attachmentsbecomes[]). - Repeating delete on an already deleted post returns current deleted state.
Common error scenarios
400: invalidpostIdpath param.404: post not found.
Restore Channel Post
Endpoint
POST /api/admin/posts/{postId}/restoreAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
postId | number | Yes | 101 | Post ID |
Raw request body
{}Behavior
- Restores a soft-deleted post (
isDeleted=false). - Clears
deletedAtanddeletedByAdminId. - If post is already active, returns current post state.
Common error scenarios
400: invalidpostId.404: post not found.
Create Visibility Rule
Endpoint
POST /api/admin/channels/{id}/visibility-rulesAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Path params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
id | number | Yes | 3 | Channel ID |
Raw Request Body (ALLOWLIST)
{
"rule_type": "ALLOWLIST",
"rule_json": {
"user_ids": [
"019c8a2e-ad60-75ae-8543-953d64e93489",
"019c8a2e-ad60-75ae-8543-953d64e93490"
]
},
"is_active": true
}Raw Request Body (DENYLIST / block list)
{
"rule_type": "DENYLIST",
"rule_json": {
"user_ids": [
"019c8a2e-ad60-75ae-8543-953d64e93491"
]
},
"is_active": true
}Raw Request Body (SEGMENT by subscription)
{
"rule_type": "SEGMENT",
"rule_json": {
"module_ids": ["019d1166-6a5c-75d3-866b-bde058d31862"],
"tier_ids": ["019d117a-2f61-74e8-aca8-b71e02dd22e9"],
"subscription_statuses": ["active", "trial"]
},
"is_active": true
}Notes
ALLOWLISTshows the post only to the listed users.DENYLISThides the post from the listed users while allowing everyone else who already passes channel/history checks.ALLOWLISTandDENYLISTonly supportrule_json.user_ids.SEGMENTsupports:module_ids,plan_ids,tier_ids,subscription_statuses.- Segment rules are matched against the user’s active/trial subscription context.
Common error scenarios
400: invalid channelid.422: invalidrule_jsonshape for selectedrule_type.404: channel not found.
List Visibility Rules
Endpoint
GET /api/admin/channels/{id}/visibility-rules?is_active=&cursor=&limit=Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_READ
Query params
| Param | Type | Required | Example | Notes |
|---|---|---|---|---|
is_active | boolean | No | true | Filter by active state |
cursor | string | No | eyJjcmVhdGVkQXQiOiI... | Opaque cursor |
limit | number | No | 20 | Default 20, max 100 |
Update Visibility Rule
Endpoint
PATCH /api/admin/visibility-rules/{ruleId}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Raw Request Body
{
"is_active": false
}or
{
"rule_type": "DENYLIST",
"rule_json": {
"user_ids": ["019c8a2e-ad60-75ae-8543-953d64e93491"]
}
}Notes
- At least one field is required.
- If
rule_typeorrule_jsonchanges, ruleversionincrements.
Delete Visibility Rule
Endpoint
DELETE /api/admin/visibility-rules/{ruleId}Auth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Notes
- Deleting a rule also removes post assignments via cascade.
- Existing post visibility snapshot remains deterministic for already snapped posts.
Replace Post Visibility Rules
Endpoint
PUT /api/admin/posts/{postId}/visibility-rulesAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_UPDATE
Raw Request Body
{
"visibility_rule_ids": [11, 12]
}Request key
- Use the snake_case field name
visibility_rule_ids.
Notes
- This endpoint fully replaces assignments.
- Send
[]to clear all post visibility rules. - Assigned rules must belong to the same channel as the post and must be active.
- This endpoint updates
visibility_snapshot_jsonfor deterministic mobile reads.
Get Post Visibility Rules
Endpoint
GET /api/admin/posts/{postId}/visibility-rulesAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_READ
Response
- Returns:
postId,channelId,rules[]
Mobile Enforcement Note
The following endpoints now enforce post visibility snapshot rules in addition to channel access decisions:
GET /api/mobile/channels/{id}/postsGET /api/mobile/posts/{postId}
Admin Discussion APIs
Create Channel Discussion
Endpoint
POST /api/admin/channels/{id}/discussionAuth
Authorization: Bearer <admin-jwt>- Requires admin permission:
Communications_CREATE
Raw request body
{
"title": "VIP Signals Discussion",
"required_feature_key": "chat.discussion"
}Notes
- Creates the single discussion attached to the channel.
- If
titleis omitted, the API derives a default title from the channel. - A channel can have only one discussion.
- If
required_feature_keyis set, users must have that feature to access discussion.
Get Channel Discussion
Endpoint
GET /api/admin/channels/{id}/discussionUpdate Channel Discussion
Endpoint
PATCH /api/admin/channels/{id}/discussionRaw request body
{
"title": "VIP Signals Live Discussion",
"is_enabled": true,
"required_feature_key": "chat.discussion"
}Notes
is_enabled=falsedisables user access to the discussion.- Omitting
required_feature_keykeeps the existing value.
List Discussion Messages
Endpoint
GET /api/admin/channels/{id}/discussion/messages?before_id=&limit=&include_deleted=Query params
| Param | Type | Required | Notes |
|---|---|---|---|
before_id | string | No | Fetch older messages |
include_deleted | boolean | No | Include soft-deleted messages |
limit | number | No | Default 20, max 100 |
Create Admin Discussion Message
Endpoint
POST /api/admin/channels/{id}/discussion/messagesRaw request body
{
"body": "Focus on close above resistance before entering."
}Update Discussion Message as Admin
Endpoint
PATCH /api/admin/discussion-messages/{id}Raw request body
{
"body": "Updated guidance: wait for close above resistance."
}Delete / Restore Discussion Message as Admin
Endpoints
DELETE /api/admin/discussion-messages/{id}
POST /api/admin/discussion-messages/{id}/restoreAdmin Discussion Moderation APIs
Mute / Unmute / Ban / Unban User
Endpoints
POST /api/admin/channels/{id}/discussion/mute
POST /api/admin/channels/{id}/discussion/unmute
POST /api/admin/channels/{id}/discussion/ban
POST /api/admin/channels/{id}/discussion/unbanRaw request body
{
"user_id": "019ce168-6ca4-74bb-894a-18e5fc3a3817",
"reason": "Spam during market open.",
"muted_until": "2026-12-31T00:00:00.000Z"
}Notes:
muted_untilis used only formute.- Banned users cannot read or post.
- Muted users can read but cannot post/update/delete their own messages.
Update Slow Mode
Endpoint
PATCH /api/admin/channels/{id}/discussion/slow-modeRaw request body
{
"enabled": true,
"interval_seconds": 30,
"reason": "High traffic during market open."
}Notes:
- Slow mode throttles user message creation.
- Admin posting is not rate-limited by slow mode.
Product Note
Communication now uses:
- channels for official admin broadcast
- one optional discussion per channel for conversation
It does not currently expose:
- standalone forums
- topics
- abuse report APIs
- moderation timeline explorer APIs