CLEMA protocol

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:

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:

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:

Delete Avatar

Remove the current user's avatar.

DELETE /avatars/me
Authorization: Bearer <access_token>

Success: 200 OK

{"ok": true}

Errors:

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.