Server overview
The server REST API (OpenAPI) is called by your business server to manage users, friends, groups and chat rooms, send and query messages, upload and manage files, and query presence, calls and more. This page covers the conventions every API shares; read it before the individual APIs.
Use a server SDK
For Go, Python and Node.js you can use the server SDKs directly, which manage the App Token, retries, pagination and callback verification for you.
Request address
Every API address has three parts:
https://{host}/{org_name}/{app_name}/{接口路径}| Part | Description |
|---|---|
host | The service's access domain, provided when the service is opened; for private deployments, the address of your deployment |
org_name | The tenant's unique identifier, generated at sign-up and unchangeable |
app_name | The app name, set when creating the app, unique within the tenant and unchangeable |
org_name and app_name joined with # form the app's AppKey, such as 1100250925#demo, which clients use when signing in.
Request examples in these docs use the following environment variables; set them before pasting into a terminal:
export IM_API="https://api.example.com" # 接入地址,不带末尾的斜杠
export ORG="1100250925" # org_name
export APP="demo" # app_name
export APP_TOKEN="<App Token>" # 见《鉴权与 App Token》Your first request
From getting an App Token to sending your first message. Find $CLIENT_ID and $CLIENT_SECRET under "Access credentials" on the console Overview:
# 1. 用应用的凭据换取 App Token。默认 7 天有效,请缓存复用,不要每次调用前都换取
export APP_TOKEN=$(curl -s -X POST "$IM_API/$ORG/$APP/token" \
-H "Content-Type: application/json" \
-d "{\"grant_type\":\"client_credentials\",\"client_id\":\"$CLIENT_ID\",\"client_secret\":\"$CLIENT_SECRET\"}" \
| jq -r .access_token)
# 2. 创建两个用户
curl -X POST "$IM_API/$ORG/$APP/users" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{ "username": "alice" }'
curl -X POST "$IM_API/$ORG/$APP/users" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{ "username": "bob" }'
# 3. 以 alice 的身份给 bob 发一条文本消息
curl -X POST "$IM_API/$ORG/$APP/messages" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d '{ "from": "alice", "to_user": "bob", "client_msg_id": "first-msg-001", "type": "text", "body": { "text": "你好" } }'From here, pick what your business needs from API overview. The sections below are conventions shared by all APIs.
Request headers
| Header | Description |
|---|---|
Authorization | Bearer <App Token>, except for the API that issues App Tokens, see Authentication and App Token |
Content-Type | Must be application/json when there's a body |
X-Request-ID | Optional. A unique ID you generate for this request, at most 64 characters of letters, digits, -, _ and .; generated by the server if omitted. Responses always include X-Request-ID; provide it when troubleshooting |
Data format
- JSON: request and response bodies are JSON, with lowercase underscored field names (such as
created_at). A request body must be a single JSON object of at most 1 MiB. Unknown fields or wrong field types return400 invalid_argument, so typos surface early. - IDs are strings: all IDs (such as
message_id,group_id,app_id) are returned as strings and should be sent as strings. They're 64-bit integers beyond what JavaScript can represent exactly, so treating them as numbers loses precision. - Times: returned in UTC as RFC 3339 with milliseconds, such as
2026-09-25T08:30:00.000Z; display them in the user's time zone. Durations are in seconds, in fields ending with_seconds. - Users: APIs always identify users by username (
username). Usernames are case-insensitive, converted to lowercase by the server, and always lowercase in responses. - Empty values: optional fields without a value are usually returned as
nullrather than omitted; a few responses (such as per-item results of batch operations) only include fields with values, as documented for each API. - Caching: responses are live data; don't cache them in intermediate layers.
Responses and status codes
Success returns a 2xx status:
| Status | Meaning |
|---|---|
200 OK | Success, with the result in the body |
201 Created | Created, with the created object in the body |
204 No Content | Success with no body (such as deletion) |
Failure returns a 4xx or 5xx status with a common error structure. Programs should branch on code; message is only for display and logging:
{
"error": {
"code": "not_found",
"message": "用户不存在",
"request_id": "QHDK7PQXPV3LFKEFLF37DST5RR"
}
}Some errors also carry a details object with the specific reason. For all error codes, see Error codes.
Pagination
List APIs use cursor pagination:
| Parameter | Where | Description |
|---|---|---|
limit | Query | Items per page, 20 by default, 1 to 100 (unless the API says otherwise) |
cursor | Query | Cursor for the next page; omit for the first page |
In the response, items is this page's data and next_cursor the cursor for the next page, null when there's none. A few APIs (such as listing calls and conversation do-not-disturb) return an empty string "" instead, so treat both null and an empty string as "no next page":
{
"items": [{ "username": "alice" }],
"next_cursor": "40c8blUXcfK_XCDGHVSvyWRqlgXc27iUTSJmxGuuazqyS9K3..."
}Cursors are generated by the server; pass them back unchanged, without parsing or building them. A cursor is only valid for the same query: start from the first page after changing filters. Bounded lists (such as a user's signed-in devices) aren't paginated and only return items.
Some APIs page in their own way (such as message history by message sequence number), as documented for each API.
Concurrent changes and version
Modifiable objects carry a version (such as a user's version or a group's info_version), incremented on every change. When changing one, you can send the version you read:
- With
version, the change only happens if the versions match; otherwise409 version_conflictis returned, meaning another request changed the data, so read it again before deciding whether to change it; - Without
version, the change simply overwrites (a few APIs require it, as documented).
Only fields present in the request change; fields left out stay as they are.
Batch APIs
Batch APIs (such as batch creating users, batch adding friends, batch sending messages) handle at most 100 items per call (unless the API says otherwise). Items are handled separately and don't affect each other: with partial failures the whole request still returns 200 OK, and the response lists each item's result, with failed items carrying an error code and message. The exact format is documented per API; for example batch user creation lists successes and failures separately:
{
"created": [{ "username": "bob", "status": "active" }],
"failed": [{ "username": "alice", "code": "already_exists", "message": "用户名已被占用" }]
}Batch APIs count toward the call quota per item, see Rate limits and app status.
Retries and idempotency
After a network timeout you can't tell whether the previous request succeeded. Before retrying, know the API's idempotency; each API's documentation states what repeated calls do. Common rules:
- Create: a unique name or ID prevents duplicates, and a repeat returns
409 already_exists, which you can treat as the earlier request having succeeded; - Delete: repeated deletes return
404 not_found,204 No Content, or200 OKwithchangedfalse(such as deleting a friend, removing from the blocklist), depending on the API; all can be treated as the earlier request having succeeded; - Set state (such as ban, mute, add to blocklist): repeated calls give the same result;
- Send messages: include a
client_msg_idyou generate; a retry returns the message written the first time without sending it again, see Send messages.
For 429 rate_limited, wait for the Retry-After header before retrying; for 5xx and network errors, retry with exponential backoff.
API overview
| Category | Contents |
|---|---|
| Users | Create, query, change and delete users; passwords and bans; sign-in credentials and devices; global mute |
| Friends and blocklist | Friendships, friend requests, blocklist, how others can add you |
| Groups | Creating and managing groups, members, group blocklist, join requests and invitations |
| Messages | Sending one-to-one and group messages; querying and exporting; recalling, editing and pinning |
| Conversations | Conversation lists, message history, unread counts and read status, deleting conversations |
| Files | Uploading message attachments, avatars and group files; getting download addresses; querying and deleting files; group file lists; storage usage |
| Chat rooms | Creating, querying, changing and dismissing chat rooms; attributes; online members, mutes, bans and allowlists; sending, querying and recalling chat room messages |
| Audio and video | Querying calls, ending calls and removing members, call statistics |
| Offline push | Users' push settings, conversation do-not-disturb and push devices |
| Moderation | Moderation config and word lists, review items and decisions, user violations and statistics, reporting on users' behalf |
| Presence | Whether users are online, on which platforms, and when they were last online |
| AI conversations | Managing AI bots; streaming replies as a bot from your own LLM |
| Event callbacks | Callback overview, verifying callback requests, event callbacks, sync callbacks |
Standalone RTC channel conventions
Standalone RTC channel APIs come in two parts:
| Your server managing channels | Clients taking part | |
|---|---|---|
| Path | /{org_name}/{app_name}/rtc/channels/… | /client/v1/rtc/… |
| Token | App Token | The user's own User Token |
| Docs | Channel management | Client channel sessions |
Your server can't obtain media credentials on behalf of clients. Other conventions:
channel_idis chosen by you and case-sensitive; store session IDs and millisecond totals as strings.- Responses with channel profiles, tickets, members and media authorization aren't cacheable (
no-store), and tickets only go in HTTPS JSON request bodies. - Keep the same key when retrying: the same channel ID when creating a channel, the same
operation_idwhen removing members or revoking tickets, the sameclient_session_idwhen a client joins. The same key can't be reused with different parameters or a different user. - Issuing tickets isn't idempotent, so don't retry it automatically; a lost response doesn't mean nothing was issued.
202from closing a channel or leaving means cleanup is still running asynchronously.
Language negotiation
With Accept-Language: en or Accept-Language: zh-TW in the request, error messages are returned in English or Traditional Chinese. Relevant responses carry Content-Language and Vary: Accept-Language, and error objects may also carry message_key and message_params, see Optional message metadata.
Language only affects human-readable text; code, details, API paths and permissions don't change, so don't parse message for business logic. For the full rules, account language preferences and SDK message catalogs, see Languages and localization.
