Files
Messenger/docs/api-reference.md
benya 33e467d2a5
All checks were successful
CI / test (push) Successful in 21s
p1: add forward without author option
2026-03-08 14:11:04 +03:00

16 KiB

REST API Reference

1. Conventions

Base path: /api/v1

Authentication:

  • Use JWT access token in header: Authorization: Bearer <token>
  • Login/refresh endpoints return { access_token, refresh_token, token_type }

Response codes:

  • 200 success
  • 201 created
  • 204 success, no body
  • 400 bad request
  • 401 unauthorized
  • 403 forbidden
  • 404 not found
  • 409 conflict
  • 422 validation/business rule error
  • 429 rate limit
  • 503 external service unavailable (email/storage/readiness)

Common error body:

{
  "detail": "Error text"
}

For /health/ready failure:

{
  "detail": {
    "status": "not_ready",
    "db": false,
    "redis": true
  }
}

2. Enums

ChatType

  • private
  • group
  • channel

ChatMemberRole

  • owner
  • admin
  • member

MessageType

  • text
  • image
  • video
  • audio
  • voice
  • file
  • circle_video

Message status events

  • message_delivered
  • message_read

3. Models (request/response)

3.1 Auth

RegisterRequest

{
  "email": "user@example.com",
  "name": "Benya",
  "username": "benya",
  "password": "strongpassword"
}

LoginRequest

{
  "email": "user@example.com",
  "password": "strongpassword",
  "otp_code": "123456"
}

otp_code is optional and used only when 2FA is enabled.

TokenResponse

{
  "access_token": "jwt",
  "refresh_token": "jwt",
  "token_type": "bearer"
}

AuthUserResponse

{
  "id": 1,
  "email": "user@example.com",
  "name": "Benya",
  "username": "benya",
  "bio": "optional",
  "avatar_url": "https://...",
  "email_verified": true,
  "twofa_enabled": false,
  "created_at": "2026-03-08T10:00:00Z",
  "updated_at": "2026-03-08T10:00:00Z"
}

3.2 Users

UserRead

{
  "id": 1,
  "name": "Benya",
  "username": "benya",
  "email": "user@example.com",
  "avatar_url": "https://...",
  "bio": "optional",
  "email_verified": true,
  "allow_private_messages": true,
  "twofa_enabled": false,
  "created_at": "2026-03-08T10:00:00Z",
  "updated_at": "2026-03-08T10:00:00Z"
}

UserSearchRead

{
  "id": 2,
  "name": "Other User",
  "username": "other",
  "email": "other@example.com",
  "avatar_url": null
}

UserProfileUpdate

{
  "name": "New Name",
  "username": "new_username",
  "bio": "new bio",
  "avatar_url": "https://...",
  "allow_private_messages": true
}

All fields are optional.

3.3 Chats

ChatRead

{
  "id": 10,
  "public_id": "A1B2C3D4E5F6G7H8J9K0L1M2",
  "type": "private",
  "title": null,
  "avatar_url": null,
  "display_title": "Other User",
  "handle": null,
  "description": null,
  "is_public": false,
  "is_saved": false,
  "archived": false,
  "pinned": false,
  "unread_count": 3,
  "unread_mentions_count": 1,
  "pinned_message_id": null,
  "members_count": 2,
  "online_count": 1,
  "subscribers_count": null,
  "counterpart_user_id": 2,
  "counterpart_name": "Other User",
  "counterpart_username": "other",
  "counterpart_avatar_url": "https://...",
  "counterpart_is_online": true,
  "counterpart_last_seen_at": "2026-03-08T10:00:00Z",
  "last_message_text": "Hello",
  "last_message_type": "text",
  "last_message_created_at": "2026-03-08T10:01:00Z",
  "my_role": "member",
  "created_at": "2026-03-08T09:00:00Z"
}

ChatCreateRequest

{
  "type": "group",
  "title": "My Group",
  "handle": "mygroup",
  "description": "optional",
  "is_public": true,
  "member_ids": [2, 3]
}

Rules:

  • private: requires exactly 1 target in member_ids
  • group/channel: require title
  • private cannot be public
  • public chat requires handle

ChatDetailRead

ChatRead + members[], where member item is:

{
  "id": 1,
  "user_id": 2,
  "role": "member",
  "joined_at": "2026-03-08T09:00:00Z"
}

3.4 Messages

MessageRead

{
  "id": 100,
  "chat_id": 10,
  "sender_id": 1,
  "reply_to_message_id": null,
  "forwarded_from_message_id": null,
  "type": "text",
  "text": "Hello",
  "delivery_status": "read",
  "attachment_waveform": [4, 7, 10, 9, 6],
  "created_at": "2026-03-08T10:02:00Z",
  "updated_at": "2026-03-08T10:02:00Z"
}

MessageCreateRequest

{
  "chat_id": 10,
  "type": "text",
  "text": "Hello",
  "client_message_id": "client-msg-0001",
  "reply_to_message_id": null
}

MessageForwardRequest

{
  "target_chat_id": 20,
  "include_author": true
}

MessageForwardBulkRequest

{
  "target_chat_ids": [20, 21],
  "include_author": true
}

MessageStatusUpdateRequest

{
  "chat_id": 10,
  "message_id": 100,
  "status": "message_read"
}

MessageReactionToggleRequest

{
  "emoji": "👍"
}

3.5 Media

UploadUrlRequest

{
  "file_name": "photo.jpg",
  "file_type": "image/jpeg",
  "file_size": 123456
}

UploadUrlResponse

{
  "upload_url": "https://...signed...",
  "file_url": "https://.../bucket/uploads/....jpg",
  "object_key": "uploads/....jpg",
  "expires_in": 900,
  "required_headers": {
    "Content-Type": "image/jpeg"
  }
}

AttachmentCreateRequest

{
  "message_id": 100,
  "file_url": "https://.../bucket/uploads/....jpg",
  "file_type": "image/jpeg",
  "file_size": 123456,
  "waveform_points": [4, 7, 10, 9, 6]
}

AttachmentRead

{
  "id": 1,
  "message_id": 100,
  "file_url": "https://...",
  "file_type": "image/jpeg",
  "file_size": 123456,
  "waveform_points": [4, 7, 10, 9, 6]
}

ChatAttachmentRead

{
  "id": 1,
  "message_id": 100,
  "sender_id": 1,
  "message_type": "image",
  "message_created_at": "2026-03-08T10:10:00Z",
  "file_url": "https://...",
  "file_type": "image/jpeg",
  "file_size": 123456
}

3.6 Search and notifications

GlobalSearchRead

{
  "users": [],
  "chats": [],
  "messages": []
}

NotificationRead

{
  "id": 1,
  "user_id": 1,
  "event_type": "message_created",
  "payload": "{\"chat_id\":10}",
  "created_at": "2026-03-08T10:15:00Z"
}

4. Health endpoints

GET /health

Returns:

{ "status": "ok" }

GET /health/live

Returns:

{ "status": "ok" }

GET /health/ready

Returns:

{ "status": "ready", "db": "ok", "redis": "ok" }

5. Auth endpoints

POST /api/v1/auth/register

Body: RegisterRequest
Response: 201 + MessageResponse

POST /api/v1/auth/login

Body: LoginRequest
Response: 200 + TokenResponse

POST /api/v1/auth/refresh

Body:

{ "refresh_token": "jwt" }

Response: 200 + TokenResponse

POST /api/v1/auth/verify-email

Body:

{ "token": "verification_token" }

Response: 200 + MessageResponse

POST /api/v1/auth/resend-verification

Body:

{ "email": "user@example.com" }

Response: 200 + MessageResponse

POST /api/v1/auth/request-password-reset

Body:

{ "email": "user@example.com" }

Response: 200 + MessageResponse

POST /api/v1/auth/reset-password

Body:

{
  "token": "reset_token",
  "new_password": "newStrongPassword"
}

Response: 200 + MessageResponse

GET /api/v1/auth/me

Auth required.
Response: 200 + AuthUserResponse

GET /api/v1/auth/sessions

Auth required.
Response: 200 + SessionRead[]

DELETE /api/v1/auth/sessions/{jti}

Auth required.
Response: 204

DELETE /api/v1/auth/sessions

Auth required.
Response: 204
Behavior: revokes all refresh sessions and invalidates all access tokens issued before this request.

POST /api/v1/auth/2fa/setup

Auth required.
Response:

{
  "secret": "BASE32SECRET",
  "otpauth_url": "otpauth://..."
}

POST /api/v1/auth/2fa/enable

Auth required.
Body:

{ "code": "123456" }

Response: 200 + MessageResponse

POST /api/v1/auth/2fa/disable

Auth required.
Body:

{ "code": "123456" }

Response: 200 + MessageResponse

6. Users endpoints

GET /api/v1/users/me

Auth required.
Response: 200 + UserRead

GET /api/v1/users/search?query=<text>&limit=20

Auth required.
Response: 200 + UserSearchRead[]
Note: if query shorter than 2 chars (after trimming optional leading @) returns empty list.

PUT /api/v1/users/profile

Auth required.
Body: UserProfileUpdate
Response: 200 + UserRead

GET /api/v1/users/blocked

Auth required.
Response: 200 + UserSearchRead[]

GET /api/v1/users/contacts

Auth required.
Response: 200 + UserSearchRead[]

POST /api/v1/users/{user_id}/contacts

Auth required.
Response: 204

POST /api/v1/users/contacts/by-email

Auth required.
Body:

{ "email": "target@example.com" }

Response: 204

DELETE /api/v1/users/{user_id}/contacts

Auth required.
Response: 204

POST /api/v1/users/{user_id}/block

Auth required.
Response: 204

DELETE /api/v1/users/{user_id}/block

Auth required.
Response: 204

GET /api/v1/users/{user_id}

Auth required.
Response: 200 + UserRead

7. Chats endpoints

GET /api/v1/chats

Query params:

  • limit (default 50)
  • before_id (optional)
  • query (optional search by title/type)
  • archived (default false)

Auth required.
Response: 200 + ChatRead[]

GET /api/v1/chats/saved

Returns or creates personal saved-messages chat.

Auth required.
Response: 200 + ChatRead

GET /api/v1/chats/discover?query=<text>&limit=30

Auth required.
Response: 200 + ChatDiscoverRead[]

POST /api/v1/chats

Auth required.
Body: ChatCreateRequest
Response: 200 + ChatRead

POST /api/v1/chats/{chat_id}/join

Join public group/channel.

Auth required.
Response: 200 + ChatRead

GET /api/v1/chats/{chat_id}

Auth required.
Response: 200 + ChatDetailRead

PATCH /api/v1/chats/{chat_id}/title

Auth required.
Body:

{ "title": "New title" }

Response: 200 + ChatRead

PATCH /api/v1/chats/{chat_id}/profile

Auth required.
Body (all fields optional):

{
  "title": "New title",
  "description": "optional description",
  "avatar_url": "https://..."
}

Rules:

  • only for group/channel
  • only for owner/admin

Response: 200 + ChatRead

GET /api/v1/chats/{chat_id}/members

Auth required.
Response: 200 + ChatMemberRead[]

POST /api/v1/chats/{chat_id}/members

Auth required.
Body:

{ "user_id": 123 }

Response: 201 + ChatMemberRead

PATCH /api/v1/chats/{chat_id}/members/{user_id}/role

Auth required.
Body:

{ "role": "admin" }

Response: 200 + ChatMemberRead

DELETE /api/v1/chats/{chat_id}/members/{user_id}

Auth required.
Response: 204

POST /api/v1/chats/{chat_id}/leave

Auth required.
Response: 204

DELETE /api/v1/chats/{chat_id}?for_all=false

Auth required.
Response: 204

POST /api/v1/chats/{chat_id}/clear

Clear chat history for current user.

Auth required.
Response: 204

POST /api/v1/chats/{chat_id}/pin

Pin chat message.

Body:

{ "message_id": 100 }

or unpin:

{ "message_id": null }

Response: 200 + ChatRead

GET /api/v1/chats/{chat_id}/notifications

Auth required.
Response:

{
  "chat_id": 10,
  "user_id": 1,
  "muted": false
}

PUT /api/v1/chats/{chat_id}/notifications

Auth required.
Body:

{ "muted": true }

Response: 200 + ChatNotificationSettingsRead

Note: mentions (@username) are delivered even when chat is muted.

3.7 Privacy enforcement notes

  • User profile privacy is enforced server-side:
    • privacy_avatar controls whether other users receive avatar_url.
    • privacy_last_seen controls whether private-chat counterpart presence fields are visible:
      • counterpart_is_online
      • counterpart_last_seen_at
  • For contacts mode, visibility is granted only when the viewer is in target user's contacts.
  • Group/channel invite restrictions are enforced by privacy_group_invites:
    • Users with contacts can be added only by users present in their contacts list.
    • Applies to group/channel creation with initial members and admin add-member action.

POST /api/v1/chats/{chat_id}/archive

Auth required.
Response: 200 + ChatRead

POST /api/v1/chats/{chat_id}/unarchive

Auth required.
Response: 200 + ChatRead

POST /api/v1/chats/{chat_id}/pin-chat

Auth required.
Response: 200 + ChatRead

POST /api/v1/chats/{chat_id}/unpin-chat

Auth required.
Response: 200 + ChatRead

POST /api/v1/chats/{chat_id}/invite-link

Auth required.
Response:

{
  "chat_id": 10,
  "token": "invite_token",
  "invite_url": "https://frontend/join?token=invite_token"
}

POST /api/v1/chats/join-by-invite

Auth required.
Body:

{ "token": "invite_token" }

Response: 200 + ChatRead

8. Messages endpoints

POST /api/v1/messages

Auth required.
Body: MessageCreateRequest
Response: 201 + MessageRead

GET /api/v1/messages/search?query=<text>&chat_id=<id>&limit=50

Auth required.
Response: 200 + MessageRead[]

GET /api/v1/messages/{chat_id}?limit=50&before_id=<message_id>

Auth required.
Response: 200 + MessageRead[]

GET /api/v1/messages/{message_id}/thread?limit=100

Auth required.
Returns root message + nested replies (thread subtree, BFS by reply links).
Response: 200 + MessageRead[]

PUT /api/v1/messages/{message_id}

Auth required.
Body:

{ "text": "Edited text" }

Response: 200 + MessageRead

DELETE /api/v1/messages/{message_id}?for_all=false

Auth required.
Response: 204

POST /api/v1/messages/status

Auth required.
Body: MessageStatusUpdateRequest
Response:

{
  "last_delivered_message_id": 123,
  "last_read_message_id": 120
}

POST /api/v1/messages/{message_id}/forward

Auth required.
Body: MessageForwardRequest
Response: 201 + MessageRead

POST /api/v1/messages/{message_id}/forward-bulk

Auth required.
Body: MessageForwardBulkRequest
Response: 201 + MessageRead[]

GET /api/v1/messages/{message_id}/reactions

Auth required.
Response:

[
  { "emoji": "👍", "count": 3, "reacted": true }
]

POST /api/v1/messages/{message_id}/reactions/toggle

Auth required.
Body: MessageReactionToggleRequest
Response: 200 + MessageReactionRead[]

9. Media endpoints

POST /api/v1/media/upload-url

Auth required.
Body: UploadUrlRequest
Response: 200 + UploadUrlResponse

Validation:

  • Allowed MIME: image/jpeg, image/png, image/webp, video/mp4, video/webm, audio/mpeg, audio/ogg, audio/webm, audio/wav, application/pdf, application/zip, text/plain
  • Max size: MAX_UPLOAD_SIZE_BYTES

POST /api/v1/media/attachments

Auth required.
Body: AttachmentCreateRequest
Response: 200 + AttachmentRead

Rules:

  • file_url must point to configured S3 bucket endpoint
  • Only message sender can attach files

GET /api/v1/media/chats/{chat_id}/attachments?limit=100&before_id=<id>

Auth required.
Response: 200 + ChatAttachmentRead[]

10. Notifications

GET /api/v1/notifications?limit=50

Auth required.
Response: 200 + NotificationRead[]

GET /api/v1/search?query=<text>&users_limit=10&chats_limit=10&messages_limit=10

Auth required.
Response: 200 + GlobalSearchRead

12. Rate limits

Configured via env vars:

  • LOGIN_RATE_LIMIT_PER_MINUTE for /auth/login
  • REGISTER_RATE_LIMIT_PER_MINUTE for /auth/register
  • REFRESH_RATE_LIMIT_PER_MINUTE for /auth/refresh
  • RESET_RATE_LIMIT_PER_MINUTE for reset/resend flows
  • MESSAGE_RATE_LIMIT_PER_MINUTE and DUPLICATE_MESSAGE_COOLDOWN_SECONDS for sending messages

429 response example:

{
  "detail": "Rate limit exceeded. Retry in 34 seconds."
}

13. Notes

  • public_id is returned for chats and should be used by clients as stable public identifier.
  • Invite links are generated for group/channel chats.
  • In channels, only users with sufficient role (owner/admin) can post.
  • email router exists in codebase but has no public REST endpoints yet.