Avatars
Users can upload graphical avatars. Avatars are managed via HTTP endpoints, separate from the WebSocket protocol. Avatar hashes (CRC-32, 8 hex characters) are included in contact data across all relevant WebSocket responses to avoid additional requests.
Groups and channels use the same validated image format and size limit. Their bytes are likewise managed over HTTP, while cache invalidation is synchronized through the transport-independent conversation object.
Upload Avatar
Upload or replace the current user's avatar.
PUT /avatars/me
Authorization: Bearer <access_token>
Content-Type: image/jpeg | image/png
<binary image data>
Constraints:
- Maximum size: 512 KB
- Allowed formats: JPEG, PNG
- Requires Bearer token authentication
Success: 200 OK
{"ok": true, "avatar_hash": "a1b2c3d4"}
The response contains the new CRC-32 avatar hash (8 hex characters).
The body is validated against the JPEG and PNG magic bytes and must match the
declared Content-Type; the header alone is not trusted. Avatars are served back
with the type derived from their own bytes and with X-Content-Type-Options: nosniff.
Errors:
401 Unauthorized/not_authenticated— missing or invalid token400 Bad Request/invalid_format— unsupported image format, or a body that is not a valid JPEG or PNG413 Request Entity Too Large/invalid_format— image exceeds 512 KB
Get Avatar
Retrieve a visible user's avatar by their numeric ID. A Bearer access token is
required, and the same block rule as profile.get is applied.
GET /avatars/{user_id}
Authorization: Bearer <access_token>
Success: 200 OK
Content-Type: image/jpeg | image/png
ETag: "<avatar_hash>"
Cache-Control: private, max-age=86400
Vary: Authorization
The response body contains the raw image data. Clients should use ETag for efficient caching by sending If-None-Match on subsequent requests.
Conditional request:
GET /avatars/{user_id}
Authorization: Bearer <access_token>
If-None-Match: "<avatar_hash>"
Returns 304 Not Modified if the avatar hasn't changed.
Errors:
400 Bad Request/invalid_format— malformed user ID401 Unauthorized/not_authenticated— missing or invalid token404 Not Found/not_found— user is hidden, doesn't exist or has no avatar
Delete Avatar
Remove the current user's avatar.
DELETE /avatars/me
Authorization: Bearer <access_token>
Success: 200 OK
{"ok": true}
Errors:
401 Unauthorized/not_authenticated— missing or invalid token
Avatar Hash in Protocol Messages
The avatar_hash field (string, may be empty) is included in the following WebSocket protocol data structures:
| Message Type | Context | Field Location |
|---|---|---|
contact.list response |
Each contact object | data.contacts[].avatar_hash |
contact.list response |
Each request | data.requests[].avatar_hash |
contact.add, contact.rename responses and contact events |
The entry | data.contact.avatar_hash |
profile.get, profile.update responses and profile.updated |
User profile | data.profile.avatar_hash |
user.search response |
Each user object | data.users[].avatar_hash |
member.list, member changes and member events |
Each member object | data.member.avatar_hash, data.members[].avatar_hash |
| Conversation snapshots and search hits | Group/channel object; in a direct conversation, the other side's picture | data.conversation.shared.avatar_hash or the corresponding list item |
Those of a person are all the same picture, as it stood when the message was made: a client keeps one picture per person, which whatever arrives last replaces, and shows it wherever that person appears.
Clients fetch /avatars/{user_id} with the current Bearer token and use the hash
for cache invalidation: when a person's avatar_hash changes, the client
refetches the image. Browser clients normally turn the authenticated response
into an object URL rather than placing the protected endpoint directly in an
img element.
Group and Channel Avatars
Any current member may retrieve the avatar:
GET /conversations/{conversation_id}/avatar
Authorization: Bearer <access_token>
The response uses the same Content-Type, ETag, Cache-Control, Vary,
conditional request and nosniff behavior as a user avatar. Missing avatars,
unknown conversations and conversations hidden from the requester all return
404 not_found.
Owners and admins (the roles with conversation.update) may upload or remove
the avatar:
PUT /conversations/{conversation_id}/avatar
Authorization: Bearer <access_token>
Content-Type: image/jpeg | image/png
<binary image data>
{"ok":true,"avatar_hash":"a1b2c3d4"}
DELETE /conversations/{conversation_id}/avatar
Authorization: Bearer <access_token>
Both changes are a conversation.updated event of the conversation stream.
Re-uploading identical bytes and deleting an already absent avatar are
idempotent and add no event. Unsupported content,
empty content and payloads above 512 KB use the same errors as user avatars;
members without metadata permission receive 403 not_authorized. Direct-chat
IDs are rejected because their visible image is the peer's user avatar.