Friends
Manage users' friends from your server. Friendship is mutual: if A is B's friend, B is always A's friend.
There are two ways to become friends:
| Way | Goes through | Good for |
|---|---|---|
| A user sends a friend request from a client | The other user's friend add mode; with the before-add-friend callback on, your server is asked first | Social apps where users add friends themselves |
| Your server calls Add a friend on this page | No request, no friend add mode or block list checks, no callback; they become friends immediately | Relationships already established in your business (such as teammates), importing friends from another IM |
For example, make alice and bob friends, with alice's remark for bob being "产品-Bob":
curl -X PUT "$IM_API/$ORG/$APP/users/alice/friends/bob" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "remark": "产品-Bob" }'In the path, {username} is the owner of the relationship and {friend} is the other user. Remarks and custom attributes belong only to the owner's side; the other user can't see them.
- Friend limit:
friend_limitin the runtime policy (3000 by default, 1 to 100000, see Runtime policy). Both sides are checked when becoming friends and neither can be full; lowering the limit doesn't remove existing friends. - Client notifications: the owner's online devices get
friend.added,friend.updatedandfriend.removedin real time. Adding and removing change both lists, so both sides are notified. - Friends-only messaging: when the app enables
friend_check_enabled, sending a one-to-one message to a non-friend from a client returns403 not_friend. - Bans and deletion: bans don't affect friendships; when a user is deleted, their friendships end and their friends are told.
Friend list versions
Each user's friend list has a version that increases by 1 with every change. Adding, updating and removing a single friend return changed and friend_version: changed true means this request changed the owner's list and friend_version is the new version; false means nothing changed (such as adding again with the same content, or removing someone who isn't a friend) and friend_version is the current version. So repeated calls all return 200 OK and retries are safe.
When the app is read-only
While the app is read-only (for example for unpaid bills, see Rate limits and app status), adding friends and changing remarks or custom attributes return 403 app_unavailable; querying and removing friends still work.
List friends
Get a user's friends page by page, 100 per page by default and up to 500. The list comes in a fixed order (not by remark or nickname), so sort it yourself for display.
/{org_name}/{app_name}/users/{username}/friendsPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The username, case-insensitive |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | Number | No | Page size, default 100, 1 to 500 |
cursor | String | No | The cursor for the next page; omit for the first page, see Paging |
known_version | Number | No | The friend list version you saved, a non-negative integer, only used for the first page. If it equals the current version, only the list header is returned (not_modified true), without items or next_cursor |
Request example
curl "$IM_API/$ORG/$APP/users/alice/friends?limit=100" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
version | Number | The list's current version; 0 for users who never had friends |
not_modified | Boolean | Whether it equals known_version from the request. When true, the response has only version, not_modified, count and max_count |
count | Number | Current number of friends |
max_count | Number | The friend limit, the app's friend_limit |
items | Array<Object> | Friends on this page, each a friend object |
next_cursor | String | The next page's cursor; null when there's no next page |
{
"version": 2,
"not_modified": false,
"count": 2,
"max_count": 3000,
"items": [
{
"username": "bob",
"nickname": "鲍勃",
"avatar_url": "",
"remark": "老鲍",
"attributes": { "group": "同事", "star": "1" },
"add_source": "import",
"created_at": "2026-10-02T19:07:08.944Z"
},
{
"username": "carol",
"nickname": "卡罗尔",
"avatar_url": "",
"remark": "",
"attributes": null,
"add_source": "",
"created_at": "2026-10-02T19:07:08.989Z"
}
],
"next_cursor": null
}With known_version=2 and no changes:
{
"version": 2,
"not_modified": true,
"count": 2,
"max_count": 3000
}- If the list changes while you're paging, later pages have a different
versionfrom the first. For an exact full list, start again from the first page whenversionchanges. - Friends who were deleted but are still being cleaned up don't appear, so for a while a page can have fewer than
limititems andcountcan be higher than what's returned. Rely only onnext_cursorto know whether there's another page.
Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | limit isn't between 1 and 500, known_version isn't a non-negative integer, or cursor is invalid |
| 404 | not_found | The user doesn't exist or was deleted |
Check relationships
Check one user's relationship with several others at once: whether they're friends, whether the user blocked them, and whether there's a pending friend request between them. Often used to show "Add friend" or "Awaiting approval" on your pages.
/{org_name}/{app_name}/users/{username}/friends/checkPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The username |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
usernames | Array<String> | Yes | Usernames to check, 1 to 100 |
Request example
curl -X POST "$IM_API/$ORG/$APP/users/alice/friends/check" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"usernames": ["bob", "erin", "frank", "Carol", "alice", "nobody"]
}'Response
On success you get 200 OK. items follows the request order, with usernames lowercased and deduplicated; nonexistent or deleted users and {username} itself are left out. Each item has:
| Field | Type | Description |
|---|---|---|
username | String | The other user |
is_friend | Boolean | Whether they're friends |
blocked | Boolean | Whether {username} blocked them. Whether they blocked {username} isn't returned |
request | String | A pending, unexpired friend request between them: sent from {username} and not yet handled; received from them and not yet handled by {username}; null if none |
{
"items": [
{ "username": "bob", "is_friend": true, "blocked": false, "request": null },
{ "username": "erin", "is_friend": false, "blocked": false, "request": "received" },
{ "username": "frank", "is_friend": false, "blocked": false, "request": "sent" },
{ "username": "carol", "is_friend": true, "blocked": false, "request": null }
]
}This is a read and counts as one call no matter how many users you check.
Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | usernames is empty or has more than 100 entries |
| 404 | not_found | {username} doesn't exist or was deleted |
Get a friend
/{org_name}/{app_name}/users/{username}/friends/{friend}Path parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The owner of the relationship |
friend | String | The friend's username |
Request example
curl "$IM_API/$ORG/$APP/users/alice/friends/bob" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK with a friend object.
{
"username": "bob",
"nickname": "鲍勃",
"avatar_url": "",
"remark": "老鲍",
"attributes": { "group": "同事", "star": "1" },
"add_source": "import",
"created_at": "2026-10-02T19:07:08.944Z"
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | {friend} and {username} are the same user |
| 404 | not_found | A user doesn't exist or was deleted (message "User not found"), or they aren't friends (message "Not your friend") |
Add a friend
Make two users friends directly, without a request and without checking their friend add modes or block lists; only both friend limits are checked.
- One side only:
remarkandattributesare written only for{username};add_sourceis the same for both. - Remarks preset in requests: a side without
remarktakes the remark preset in their pending request to the other user, or none. Pending, unexpired requests between them become "became friends" (accepted). - Already friends counts as success: this call's
remarkandattributesare written to{username}'s side (others unchanged), andadd_sourceandcreated_atstay;changedistrueif anything changed. When importing, call once for each side to write both remarks. - Notifications: both sides' online devices are told when they newly become friends; if they were already friends and only remarks or attributes changed, only
{username}is told.
/{org_name}/{app_name}/users/{username}/friends/{friend}Path parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The owner of the relationship |
friend | String | The user to add, not {username} |
Request body
The body is optional. It can't contain username; the other user comes from the path.
| Field | Type | Required | Description |
|---|---|---|---|
remark | String | No | {username}'s remark for the friend, up to 64 characters without control characters; leading and trailing whitespace trimmed |
attributes | Object | No | {username}'s custom attributes for the friend, string key-value pairs, see Friend object. When already friends, merged entry by entry as in Update a friend |
add_source | String | No | How they met, such as search, qrcode or group:1839201838123, up to 32 characters of letters, digits and _, -, ., :. The same for both sides and can't be changed afterwards |
created_at | String | No | When they became friends, in RFC 3339, not in the future. Keeps original times when importing from another IM; defaults to now and is ignored when already friends |
Request example
curl -X PUT "$IM_API/$ORG/$APP/users/alice/friends/bob" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"remark": "老鲍",
"attributes": { "star": "1", "group": "同事" },
"add_source": "import"
}'Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
friend | Object | {username}'s side as a friend object |
changed | Boolean | Whether this request changed {username}'s friend list |
friend_version | Number | {username}'s friend list version, see the note at the top of this page |
{
"friend": {
"username": "bob",
"nickname": "鲍勃",
"avatar_url": "",
"remark": "老鲍",
"attributes": { "group": "同事", "star": "1" },
"add_source": "import",
"created_at": "2026-10-02T19:07:08.944Z"
},
"changed": true,
"friend_version": 1
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | {friend} is {username}; the body contains username; remark, attributes or add_source break the rules; created_at is in the future |
| 404 | not_found | {username} or {friend} doesn't exist or was deleted |
| 409 | limit_exceeded | A friend limit is reached: details.reason friend_limit means {username} is full, peer_friend_limit means the other user is |
{
"error": {
"code": "limit_exceeded",
"message": "对方的好友数已达上限",
"details": { "reason": "peer_friend_limit" },
"request_id": "QHDK7PQXPV3LFKEFLF37DST5RR"
}
}Add friends in batches
Add up to 100 friends for one user at once, each under the same rules as Add a friend. Items are handled independently, see Batch APIs. Counts toward the app's quota once per item.
/{org_name}/{app_name}/users/{username}/friends/batchPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The owner of the relationships |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
friends | Array<Object> | Yes | Friends to add, 1 to 100 |
friends[].username | String | Yes | The friend's username |
friends[].remark | String | No | As in Add a friend |
friends[].attributes | Object | No | As in Add a friend |
friends[].add_source | String | No | As in Add a friend |
friends[].created_at | String | No | As in Add a friend |
Request example
curl -X POST "$IM_API/$ORG/$APP/users/carol/friends/batch" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"friends": [
{ "username": "dave", "remark": "戴夫", "add_source": "import", "created_at": "2025-03-01T10:00:00Z" },
{ "username": "erin", "attributes": { "group": "同学" } },
{ "username": "alice" },
{ "username": "nobody" }
]
}'Response
On success you get 200 OK. Repeated usernames aren't deduplicated; each occurrence is processed.
| Field | Type | Description |
|---|---|---|
added | Array<Object> | Items that succeeded, each {username}'s side as a friend object, including existing friends |
failed | Array<Object> | Items that failed, each with username (lowercase), code, message and optional details; errors as in Add a friend |
{
"added": [
{
"username": "dave",
"nickname": "戴夫",
"avatar_url": "",
"remark": "戴夫",
"attributes": null,
"add_source": "import",
"created_at": "2025-03-01T10:00:00.000Z"
},
{
"username": "erin",
"nickname": "艾琳",
"avatar_url": "",
"remark": "",
"attributes": { "group": "同学" },
"add_source": "",
"created_at": "2026-10-02T19:10:22.454Z"
},
{
"username": "alice",
"nickname": "爱丽丝",
"avatar_url": "",
"remark": "",
"attributes": null,
"add_source": "",
"created_at": "2026-10-02T19:07:08.989Z"
}
],
"failed": [
{ "username": "nobody", "code": "not_found", "message": "用户不存在" }
]
}Batch APIs don't return changed or versions.
Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | friends is empty or has more than 100 items |
| 404 | not_found | {username} doesn't exist or was deleted |
Update a friend's remark and custom attributes
Change the remark and custom attributes {username} set for a friend; only {username}'s side is affected. Send at least one of remark and attributes; anything not sent stays. {username}'s online devices are told when something changes; changed is false when the values are the same.
/{org_name}/{app_name}/users/{username}/friends/{friend}Path parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The owner of the relationship |
friend | String | The friend's username |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
remark | String | No | Remark, up to 64 characters without control characters, leading and trailing whitespace trimmed; an empty string clears it |
attributes | Object | No | Custom attributes to change: send only the entries to change, with null deleting an entry; null for the whole field clears everything. The merged result must follow the rules in Friend object |
Request example
Change the remark to "鲍总", delete star and add tag:
curl -X PATCH "$IM_API/$ORG/$APP/users/alice/friends/bob" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"remark": "鲍总",
"attributes": { "star": null, "tag": "vip" }
}'Response
On success you get 200 OK with the same fields as Add a friend.
{
"friend": {
"username": "bob",
"nickname": "鲍勃",
"avatar_url": "",
"remark": "鲍总",
"attributes": { "group": "同事", "tag": "vip" },
"add_source": "import",
"created_at": "2026-10-02T19:07:08.944Z"
},
"changed": true,
"friend_version": 3
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | Neither remark nor attributes was sent; remark or attributes break the rules; {friend} is {username} |
| 404 | not_found | A user doesn't exist or was deleted, or they aren't friends |
Remove a friend
End a friendship for both users at once, deleting both sides' remarks and custom attributes. Both sides' online devices are told, with the reason "removed by the server", so neither thinks the other removed them. Removing a friend doesn't affect block lists, existing conversations or history.
If they weren't friends, you still get 200 OK with changed false.
/{org_name}/{app_name}/users/{username}/friends/{friend}Path parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The owner of the relationship |
friend | String | The friend's username |
Request example
curl -X DELETE "$IM_API/$ORG/$APP/users/carol/friends/erin" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
changed | Boolean | Whether a friendship was actually removed |
friend_version | Number | {username}'s friend list version |
{
"changed": true,
"friend_version": 4
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | {friend} is {username} |
| 404 | not_found | {username} or {friend} doesn't exist or was deleted |
Remove friends in batches
Remove up to 100 friends for one user at once, each under the same rules as Remove a friend; users who weren't friends count as removed. Counts toward the app's quota once per item.
/{org_name}/{app_name}/users/{username}/friends/batch-deletePath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The owner of the relationships |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
usernames | Array<String> | Yes | Friends to remove, 1 to 100 |
Request example
curl -X POST "$IM_API/$ORG/$APP/users/carol/friends/batch-delete" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"usernames": ["dave", "bob", "nobody"]
}'Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
deleted | Array<String> | Usernames removed, including ones who weren't friends |
failed | Array<Object> | Items that failed, each with username (lowercase), code, message and optional details; errors as in Remove a friend |
{
"deleted": ["dave", "bob"],
"failed": [
{ "username": "nobody", "code": "not_found", "message": "用户不存在" }
]
}Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | usernames is empty or has more than 100 entries |
| 404 | not_found | {username} doesn't exist or was deleted |
Remove all friends
Remove all of a user's friends, for example when an employee leaves or to roll back a bad import. Each call removes up to 100, each under the same rules as Remove a friend. Call again while has_more is true.
Each call counts toward the app's quota once per friend taken (once if there are none). If an error occurs midway, the call returns it and removals already done aren't undone; just call again to continue.
/{org_name}/{app_name}/users/{username}/friendsPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
username | String | The username |
Request example
curl -X DELETE "$IM_API/$ORG/$APP/users/alice/friends" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
deleted_count | Number | Friends removed by this call |
has_more | Boolean | Whether any friends remain |
{
"deleted_count": 5,
"has_more": false
}Errors
| HTTP status | code | Description |
|---|---|---|
| 404 | not_found | The user doesn't exist or was deleted |
Data structures
Friend object
One friend from the owner's point of view.
| Field | Type | Description |
|---|---|---|
username | String | The friend's username |
nickname | String | The friend's nickname; empty string if not set |
avatar_url | String | The friend's avatar address; empty string if not set |
remark | String | The owner's remark for the friend; empty string if none |
attributes | Object | The owner's custom attributes for the friend, string key-value pairs; null if none |
add_source | String | How they met; empty string if none |
created_at | String | When they became friends |
{
"username": "bob",
"nickname": "鲍勃",
"avatar_url": "",
"remark": "老鲍",
"attributes": { "group": "同事", "star": "1" },
"add_source": "import",
"created_at": "2026-10-02T19:07:08.944Z"
}nicknameandavatar_urlcome from the friend's current profile. Friends aren't notified when someone changes their profile.- Remark: up to 64 characters without control characters.
- Custom attributes: up to 16 entries and 1 KB of JSON; keys are 1 to 32 characters of letters, digits and
_,-,.; values are strings. Useful for starred friends, friend groups and the like. - Add source: records how they met, with values you define such as
search,qrcodeorgroup:1839201838123(met in a group). Sources submitted from clients in friend requests aren't verified, so use them only for display and statistics, never for permissions. Can't be changed after becoming friends.
