Appearance
ImBoy Channel API Contract (v1)
Last Updated: 2026-03-08
Status: Long-lived protocol contract
Scope: Channel public and admin contract across server, app, and admin frontend
Source of truth:src/imboy_router.erl+src/api/channel_handler.erl
Note:v1refers to the contract version, not the application release version.
Related docs:docs/reference/rest-api.md,docs/reference/websocket-api-2.md,docs/api/envelope.schema.json
1. Scope
This document is the single contract for Channel APIs used by:
- Server:
imboy - App:
imboyapp - Admin:
imboy-admin-frontend(channel management pages)
2. Global Rules
2.1 Envelope
All responses must follow the canonical envelope schema:
json
{
"code": 0,
"msg": "ok",
"sv_ts": 1771481621000,
"payload": {}
}Reference: docs/api/envelope.schema.json.
2.2 ID Rules (TSID)
- Input IDs received from client are TSID integer (as string) in API layer.
- API layer converts to integer before DB operations.
- Output IDs returned to client are TSID integer directly.
- DB stores BIGINT IDs (generated by
elib_tsid).
2.3 Parameter Resolution (Hard Rule)
For Channel APIs with path bindings, server uses:
- Path parameter first.
- Body/query fallback only for compatibility.
Current helpers in channel_handler:
resolve_channel_id/2resolve_message_id/2resolve_user_id_bin/2resolve_reaction_type/2
App/Admin callers should pass canonical path params and avoid body duplicates.
2.4 Channel Content Access Rule (Private/Paid)
For channel content endpoints, server enforces access based on channel type:
- Public channel (
type=0): readable by authenticated users. - Private channel (
type=1): only subscribers/admins/creator can read content. - Paid channel (
type=2): only purchased-or-subscribed users/admins/creator can read content.
Covered endpoints:
GET /api/v1/channel/:channel_id/messagesPOST /api/v1/channel/:channel_id/readPOST /api/v1/channel/:channel_id/message/:message_id/viewPOST /api/v1/channel/:channel_id/message/:message_id/reactionDELETE /api/v1/channel/:channel_id/message/:message_id/reaction/:reaction_type
3. Core API Matrix
| Action | Method | Path | Query | Body | Payload (success) |
|---|---|---|---|---|---|
| Create channel | POST | /api/v1/channel/create | - | name, type, description?, avatar?, custom_id?, tags? | Channel object |
| Get channel | GET | /api/v1/channel/:channel_id | - | - | Channel object |
| Get by custom id | GET | /api/v1/channel/by_custom_id/:custom_id | - | - | Channel object |
| Update channel | PUT/POST | /api/v1/channel/:channel_id/update | - | name?, description?, avatar?, tags? | Channel object |
| Delete channel | POST | /api/v1/channel/:channel_id/delete | - | - | {} |
| Subscribe | POST | /api/v1/channel/:channel_id/subscribe | - | - | {} |
| Unsubscribe | POST | /api/v1/channel/:channel_id/unsubscribe | - | - | {} |
| My subscribed channels | GET | /api/v1/channels/subscribed | cursor?, limit? | - | {list, cursor, limit} |
| My managed channels | GET | /api/v1/channels/managed | - | - | {list} |
| Unread summary | GET | /api/v1/channels/unread/summary | - | - | {total_unread, unread_channels, channels[]} |
| Publish message | POST | /api/v1/channel/:channel_id/message | - | content, msg_type, payload? | Message object |
| Message list | GET | /api/v1/channel/:channel_id/messages | cursor?, limit? | - | {list} |
| Mark read | POST | /api/v1/channel/:channel_id/read | - | message_id | {} |
| Search channels | GET | /api/v1/channels/search | keyword, limit? | - | {list} |
| Discover channels | GET | /api/v1/channels/discover | category?, limit? | - | {list} |
3.1 Unread Sync Contract
Pull
Endpoint:
GET /api/v1/channels/unread/summary
Response payload:
json
{
"total_unread": 23,
"unread_channels": 4,
"channels": [
{
"channel_id": 1838294017982464,
"unread_count": 10
}
]
}Rules:
total_unread: sum of unread counts in active subscriptions.unread_channels: count of channels withunread_count > 0.channels: list of per-channel unread entries sorted by unread desc then channel id asc.
Push
Action: channel_unread_count
Payload:
json
{
"channel_id": 1838294017982464,
"unread_count": 12
}Rules:
- publish message should trigger unread push for affected subscribers.
- mark-read should push unread count
0for the caller channel scope. - push and pull must read from the same authoritative unread source.
- client may use push for immediate UI updates and pull for cold-start resync.
4. Admin/Subscribers/Stats Matrix
| Action | Method | Path | Query | Body | Payload (success) |
|---|---|---|---|---|---|
| Add admin | POST | /api/v1/channel/:channel_id/admin | - | user_id, role | {} |
| Remove admin | DELETE | /api/v1/channel/:channel_id/admin/:user_id | - | - | {} |
| Update admin role | PUT | /api/v1/channel/:channel_id/admin/:user_id/role | - | role | {} |
| Get admins | GET | /api/v1/channel/:channel_id/admins | - | - | {list} |
| Get subscribers | GET | /api/v1/channel/:channel_id/subscribers | cursor?, limit? | - | {list, cursor, limit} |
| Remove subscriber | DELETE | /api/v1/channel/:channel_id/subscriber/:user_id | - | - | {} |
| Channel stats | GET | /api/v1/channel/:channel_id/stats | - | - | Stats object |
| Channel daily stats | GET | /api/v1/channel/:channel_id/stats/daily | days? | - | {list} |
| Record view | POST | /api/v1/channel/:channel_id/message/:message_id/view | - | - | {} |
| Add reaction | POST | /api/v1/channel/:channel_id/message/:message_id/reaction | - | reaction_type? | {} |
| Remove reaction | DELETE | /api/v1/channel/:channel_id/message/:message_id/reaction/:reaction_type | - | - | {} |
5. Invitation Matrix (Private Channel)
| Action | Method | Path | Body | Payload (success) |
|---|---|---|---|---|
| Create invitation | POST | /api/v1/channel/:channel_id/invitation | invitee_uid | Invitation object |
| Accept invitation | POST | /api/v1/channel/invitation/accept | invitation_id | {} |
| Reject invitation | POST | /api/v1/channel/invitation/reject | invitation_id | {} |
| My invitations | GET | /api/v1/channel/invitations/my | - | {list} |
| Sent invitations | GET | /api/v1/channel/invitations/sent | - | {list} |
6. Order Matrix (Paid Channel)
| Action | Method | Path | Body | Payload (success) |
|---|---|---|---|---|
| Create order | POST | /api/v1/channel/:channel_id/order | - | Order object |
| Pay order | POST | /api/v1/channel/order/pay | order_no | {} |
| My orders | GET | /api/v1/channel/orders/my | - | {list} |
| Get order detail | GET | /api/v1/channel/order/:order_no | - | Order object |
7. Error Semantics (Business)
code = 0means success.code != 0means business failure andmsgis authoritative.- Typical channel errors include:
频道ID不能为空用户ID不能为空角色值必须在1-3之间订单号不能为空频道不存在/订单不存在
8. Client Alignment Checklist
8.1 App (imboyapp)
updateAdminRoleuses/api/v1/channel/:channel_id/admin/:user_id/role.- Calls with path IDs no longer duplicate
channel_idin body unless required by contract. - Order endpoints are implemented in
ChannelApi. - Unread badge should prefer websocket push and use unread summary pull for cold-start resync.
8.2 Admin (imboy-admin-frontend)
- Channel list pagination uses
DataTablePagination. onPageSizeChangeimplemented.- Default
size=10. - Search resets
page=1.
9. Change Policy
Any channel API contract change must update all:
src/imboy_router.erlroute definitionsrc/api/channel_handler.erlparameter parsing and validation- App API client and tests
- Admin API/client pages where applicable
- This document
10. Related Docs
docs/reference/rest-api.mddocs/reference/websocket-api-2.mddocs/api/envelope.schema.json(已删除,TSID 迁移后不再使用 hashids)docs/standards/hashid-encoding.mddocs/guides/operations/security.md