Download and manage files
Download, look up and delete the app's files from your server. Your server can download every message attachment and group file without acting as a user and without membership checks, for example to review images users sent or sync group files into your system. Avatar public addresses can be opened directly without exchange.
| You want to | API |
|---|---|
| Download message attachments or group files | Get download addresses, then GET the download address |
| Check upload status, or see why a file was blocked or deleted | Get a file |
| Delete attachments along with a recall | Delete a file |
| List a group's files | List group files |
| See how much storage the app uses | Get storage usage |
For example, download an image attachment:
DL=$(curl -s -X POST "$IM_API/$ORG/$APP/media/download-urls" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "urls": ["https://im.example.com/media/v1/f/99606017553203200/5nOzK6E40hgYIKl80dYVLQ"] }' \
| jq -r '.items[0].download_url')
curl -o photo.jpg "$DL" # 不要带 Authorization 请求头For file purposes, addresses and retention, see Files overview; for uploading, see Upload files.
Get download addresses
Exchange file addresses or thumbnail addresses for download addresses, up to 100 at a time. Each address gets its own result, so one failure doesn't affect the others; the whole request counts as one OpenAPI call.
- Downloading: just
GETthe download address, without and never with anAuthorizationheader;Rangeis supported (resuming downloads). - Valid for 60 to 90 minutes: the signing time is aligned to 30 minutes and valid for 90, so the same file exchanged within the same 30 minutes gets the same address and can be cached by address. Don't store download addresses or put them in messages; exchange again after expiry.
- Response headers:
Content-Typeis the detected format;filekinds and all group files also carryContent-Disposition: attachmentwith the uploaded file name (RFC 6266filename*encoding), so browsers download rather than open them. Exchanging a thumbnail address gives the thumbnail's download address, withoutContent-Disposition.
/{org_name}/{app_name}/media/download-urlsPath parameters
For org_name and app_name, see the server overview.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
urls | Array<String> | Yes | File addresses or thumbnail addresses, 1 to 100, repeats allowed. They must exactly match the addresses returned by the APIs, without query parameters |
Request example
curl -X POST "$IM_API/$ORG/$APP/media/download-urls" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://im.example.com/media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA",
"https://im.example.com/media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA/thumb",
"https://im.example.com/media/v1/f/99607013226446848/qVIBwfZmwXMrh_FiZMqI-Q",
"https://example.com/a.jpg"
]
}'Response
On success you get 200 OK. Even if every address failed it's still 200, so check each item:
| Field | Type | Description |
|---|---|---|
items | Array<Object> | Each address's result, matching urls one to one in the same order |
items[].url | String | The address from the request |
items[].download_url | String | The download address. Only on success |
items[].expires_at | String | When the download address expires, 60 to 90 minutes from now. Only on success |
items[].code | String | The error code, see below. Only on failure |
items[].message | String | The error text. Only on failure |
code for failed items:
code | Description |
|---|---|
not_found | Not a file address from this service, such as an avatar public address or an address with query parameters ("Not a file address from this service"); the file doesn't exist, belongs to another app, has the wrong key, is still uploading, or a thumbnail was requested for a non-image ("File not found"); the file was deleted more than about 30 days ago |
file_expired | The file expired or was deleted |
file_blocked | The file was blocked for violating rules, by moderation or the platform, see Blocking violating files |
{
"items": [
{
"url": "https://im.example.com/media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA",
"download_url": "https://storage.example.com/im-private/t99604914749046784/a99604957103128576/m/99606093402996736?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=20261002T203000Z&X-Amz-Expires=5400&X-Amz-SignedHeaders=host&X-Amz-Signature=468e6e29...",
"expires_at": "2026-10-02T22:00:00.000Z"
},
{
"url": "https://im.example.com/media/v1/f/99606093402996736/DC0neNm6HBbxGW9bN6g_iA/thumb",
"download_url": "https://storage.example.com/im-private/t99604914749046784/a99604957103128576/m/99606093402996736_thumb?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=20261002T203000Z&X-Amz-Expires=5400&X-Amz-SignedHeaders=host&X-Amz-Signature=52ca87a0...",
"expires_at": "2026-10-02T22:00:00.000Z"
},
{
"url": "https://im.example.com/media/v1/f/99607013226446848/qVIBwfZmwXMrh_FiZMqI-Q",
"code": "file_expired",
"message": "文件已过期或已被删除"
},
{
"url": "https://example.com/a.jpg",
"code": "not_found",
"message": "不是本服务的文件地址"
}
]
}Download addresses are object storage or CDN addresses whose form depends on the deployment; use them as is without parsing or modifying their parameters.
Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | urls is empty or has more than 100 entries |
Get a file
Get a file's info by file ID, including uploading, blocked and deleted files (deletion records are kept for about 30 days), to check upload status or see why a file was blocked or deleted. The file ID is the first segment after /media/v1/f/ in the file address.
/{org_name}/{app_name}/media/files/{file_id}Path parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
file_id | String | The file ID |
Request example
curl "$IM_API/$ORG/$APP/media/files/99606563727081472" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK with a file object. A group file:
{
"file_id": "99606563727081472",
"purpose": "group_file",
"kind": "file",
"status": "active",
"url": "https://im.example.com/media/v1/f/99606563727081472/irgzYRVvGPU86eyqPiJ8RQ",
"thumbnail_url": null,
"name": "Q3 需求评审.pdf",
"content_type": "application/pdf",
"size": 3145728,
"width": null,
"height": null,
"owner": "bob",
"group_id": "99606031272771584",
"via": "openapi",
"created_at": "2026-10-02T20:40:55.393Z",
"expires_at": null,
"in_use": false,
"sha256": null,
"created_by": "99604957107322880",
"blocked_at": null,
"blocked_by": null,
"block_rule_source": null,
"block_reason": null,
"deleted_at": null,
"delete_reason": null
}A deleted image attachment:
{
"file_id": "99607013226446848",
"purpose": "attachment",
"kind": "image",
"status": "deleted",
"url": "https://im.example.com/media/v1/f/99607013226446848/qVIBwfZmwXMrh_FiZMqI-Q",
"thumbnail_url": null,
"name": null,
"content_type": "image/png",
"size": 1142,
"width": 200,
"height": 200,
"owner": null,
"group_id": null,
"via": "openapi",
"created_at": "2026-10-02T20:42:42.563Z",
"expires_at": "2026-11-01T20:42:42.563Z",
"in_use": false,
"sha256": "199a0ebb10df2101addbe8e3e39dea251149353f7f779245c3e337acfdc4f50c",
"created_by": "99604957107322880",
"blocked_at": null,
"blocked_by": null,
"block_rule_source": null,
"block_reason": null,
"deleted_at": "2026-10-02T20:42:42.838Z",
"delete_reason": "deleted"
}Errors
| HTTP status | code | Description |
|---|---|---|
| 404 | not_found | The file doesn't exist, belongs to another app, or was deleted more than about 30 days ago |
Delete a file
Delete one of the app's files: a message attachment, user avatar, group avatar or group file, including ones uploaded by clients. Deletion can't be undone. Afterwards:
| Area | Result |
|---|---|
| Downloads | Exchanging fails immediately with file_expired, and clients show "File expired" |
| Messages referring to it | Unchanged: the message stays and the address in body is the same; it just can't be downloaded |
| An avatar in use | Profiles aren't updated automatically, so the avatar in the profile stops working; update the profile too |
| Group files | Removed from the group file list |
| Storage | Usage drops right away; content is removed from storage after about 10 minutes, and download addresses exchanged before then may still work; CDN caches of avatar public addresses expire within a day |
Recalling a message doesn't delete its attachments, because the same file address may have been forwarded to other conversations. If your business needs "recall means delete", take the file ID from the attachment address after recalling and call this API.
Deleting an already deleted file succeeds. Blocked files can't be deleted. Files still uploading can't be deleted with this API; cancel the upload instead.
/{org_name}/{app_name}/media/files/{file_id}Path parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
file_id | String | The file ID |
Request example
curl -X DELETE "$IM_API/$ORG/$APP/media/files/99607013226446848" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 204 No Content with no body.
Errors
| HTTP status | code | Description |
|---|---|---|
| 403 | file_blocked | The file was blocked for violating rules (by moderation or the platform) and can't be deleted. You can't lift blocks made under platform rules, see Blocking violating files |
| 404 | not_found | The file doesn't exist, belongs to another app, or is still uploading |
List group files
List a group's usable files, newest first, along with the group's total file count and space used. Only files with status active are listed, with no filtering by kind or uploader and no other sort order. Server queries don't check membership, and banned groups are listed as usual.
/{org_name}/{app_name}/media/groups/{group_id}/filesPath parameters
For org_name and app_name, see the server overview.
| Parameter | Type | Description |
|---|---|---|
group_id | String | The group ID |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | Number | No | Files per page, default 20, 1 to 100 |
cursor | String | No | The previous page's next_cursor; omit for the first page, see Paging |
Request example
curl "$IM_API/$ORG/$APP/media/groups/99606031272771584/files?limit=20" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
file_count | Number | Usable group files in the whole group, at most 1000 |
used_bytes | Number | Space used by the group's files in bytes, images including thumbnails |
items | Array<Object> | This page's group files, each a file object, with owner the uploader (for client uploads) or the user given at upload |
next_cursor | String | The next page's cursor; null when there's no next page |
{
"file_count": 2,
"used_bytes": 3146870,
"items": [
{
"file_id": "99606564578525184",
"purpose": "group_file",
"kind": "image",
"status": "active",
"url": "https://im.example.com/media/v1/f/99606564578525184/d-3evFyEZ9u3Q4YGEFMGDg",
"thumbnail_url": "https://im.example.com/media/v1/f/99606564578525184/d-3evFyEZ9u3Q4YGEFMGDg/thumb",
"name": "示意图.png",
"content_type": "image/png",
"size": 1142,
"width": 200,
"height": 200,
"owner": null,
"group_id": "99606031272771584",
"via": "openapi",
"created_at": "2026-10-02T20:40:55.596Z",
"expires_at": null,
"in_use": false,
"sha256": "199a0ebb10df2101addbe8e3e39dea251149353f7f779245c3e337acfdc4f50c",
"created_by": "99604957107322880",
"blocked_at": null,
"blocked_by": null,
"block_rule_source": null,
"block_reason": null,
"deleted_at": null,
"delete_reason": null
},
{
"file_id": "99606563727081472",
"purpose": "group_file",
"kind": "file",
"status": "active",
"url": "https://im.example.com/media/v1/f/99606563727081472/irgzYRVvGPU86eyqPiJ8RQ",
"thumbnail_url": null,
"name": "Q3 需求评审.pdf",
"content_type": "application/pdf",
"size": 3145728,
"width": null,
"height": null,
"owner": "bob",
"group_id": "99606031272771584",
"via": "openapi",
"created_at": "2026-10-02T20:40:55.393Z",
"expires_at": null,
"in_use": false,
"sha256": null,
"created_by": "99604957107322880",
"blocked_at": null,
"blocked_by": null,
"block_rule_source": null,
"block_reason": null,
"deleted_at": null,
"delete_reason": null
}
],
"next_cursor": null
}When a group is dissolved, its files are deleted in the background, and the list is usually empty within a minute; uploading group files to it afterwards returns 404 not_found.
Errors
| HTTP status | code | Description |
|---|---|---|
| 400 | invalid_argument | limit isn't between 1 and 100, or cursor is invalid |
| 404 | not_found | The group doesn't exist ("Group not found or dissolved"). Dissolved groups are returned as usual, listing group files not yet deleted |
Get storage usage
Get the app's current storage usage and quota, broken down by purpose. Usage counts usable files (status active), with images including thumbnails; uploading, deleted and blocked files don't count, and it updates as soon as files complete, are deleted or are blocked. For the quota, see Storage usage and quota.
/{org_name}/{app_name}/media/usagePath parameters
For org_name and app_name, see the server overview.
Request example
curl "$IM_API/$ORG/$APP/media/usage" \
-H "Authorization: Bearer $APP_TOKEN"Response
On success you get 200 OK:
| Field | Type | Description |
|---|---|---|
used_bytes | Number | Space used in bytes |
file_count | Number | Usable files |
limit_bytes | Number | The app's storage quota in bytes: with file storage limits on, the smaller of the platform's storage quota (1099511627776, i.e. 1024 GB, by default) and the plan's storage capacity; 0 with limits off, meaning unlimited |
by_purpose | Object | Usage by purpose, keyed by purpose, each with used_bytes and file_count. Every purpose is always listed, with 0 when there are no files. chatroom_avatar is chat room covers |
{
"used_bytes": 37064934,
"file_count": 9,
"limit_bytes": 1099511627776,
"by_purpose": {
"attachment": { "used_bytes": 37043998, "file_count": 4 },
"chatroom_avatar": { "used_bytes": 0, "file_count": 0 },
"group_avatar": { "used_bytes": 2026, "file_count": 1 },
"group_file": { "used_bytes": 1193, "file_count": 2 },
"user_avatar": { "used_bytes": 17717, "file_count": 2 }
}
}Download compatibility
Download addresses don't carry response-content-type; the file type written to the object at completion is used, which works with Alibaba Cloud OSS. If an old address returns HTTP 400 with Can not override response header on content-type (0017-00000902), update the server and exchange the download address again; signed addresses can't have parameters removed by hand.
