Duvet DOCS
GitHub

Duvet API Documentation

Duvet is a Discord-like chat application with a REST API and WebSocket gateway for real-time events.

Base URL

https://api.duvet.chat

Architecture

  • duvet-api — REST API server (Go/Gin, PostgreSQL, Redis)
  • duvet-gateway — WebSocket gateway for real-time events and WebRTC voice

Authentication

All API requests (except auth endpoints) require a Bearer token in the Authorization header:

Authorization: Bearer <access_token>

Tokens are obtained via the /api/auth/login endpoint and refreshed via /api/auth/refresh. They last 24 hours; each refresh gives new ones for another 24 hours, up to 30 days after the login, when the user has to log in again.

Users also send their session token (session_token from login or refresh) in the X-Session-Token header. Login and refresh set it as the duvet_session_token cookie too, which the API only reads when there’s no header.

Bots send Authorization: Bot <bot_token> instead, with the token from Create Bot.

Rate Limiting

Rate-limited endpoints return these headers. When several limits apply to a request, they describe the one closest to running out.

HeaderDescription
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when the window resets
X-RateLimit-Reset-AfterSeconds until the window resets

Limits apply per route and per user, or per IP address on the public endpoints below. Routes that act on a room, conversation or space count each one separately: acknowledging messages in two different rooms uses two limits. Those routes also have a ceiling across all rooms, conversations and spaces. A request only counts against a limit when it gets through all of them, so rejected requests don’t use up the ceiling.

EndpointsLimit
All authenticated endpoints together50 requests per second per user
Authenticated endpoints64 requests per minute per route (and per room, conversation or space when the route acts on one); routes that act on one also allow at most 640 per minute in total
POST /api/auth/verify/send, /reset-password/send, /change-email/send3 per hour per account, on top of their per-IP limit below
POST /api/auth/login, /reset-password, /reset-password/send, /forgot-password, /change-email, /change-email/send16 per minute per IP
POST /api/auth/signin, /verify, /verify/send16 per hour per IP
POST /api/auth/refresh, /logout32 per minute per IP
GET /api/applications/:appId/interactions/next600 per minute per bot, instead of the per-route limit
GET /api/proxy/video300 per minute per IP
Webhook execution30 per minute per webhook and IP
GET /api/invites/:code60 per minute per IP

Over the limit, the API responds 429 Too Many Requests with {"error": "too many requests, try again later"} and a Retry-After header (seconds). Wait that long before retrying; a 429 never means the session is invalid.

Two limits work per email address instead, whatever the IP:

  • Failed logins: after 10 POST /api/auth/login attempts for an address within 15 minutes without a successful one, logging in to it answers 429 with {"error": "too many failed login attempts, try again later"} (no Retry-After) until 15 minutes have passed since the first failure. A successful login clears the count.
  • Password reset emails: POST /api/auth/forgot-password sends at most 3 emails per address per hour. Further requests still answer 204, so the response doesn’t reveal whether an account exists, but send nothing.

Typing and recording notices have a limit of the same kind: POST /api/rooms/:roomId/typing and /recording, and their conversation versions, send their event at most once every 2 seconds per user and room or conversation, each kind on its own. Further requests in that time still answer 204, but send nothing, so a client can send them as it likes without flooding everyone else.

Request Size

A request body can be up to 32 MiB: one that says it is bigger is answered 413 with {"error": "the request body is over 32 MiB"}, and one sent without its length is cut off at the limit and fails to read. Uploads have smaller limits of their own, given with each endpoint.

Enums

Room Type

ValueNameDescription
1TextText channel
2VoiceVoice channel
3CategoryChannel category (folder)

User Flags (bitfield)

BitValueNameDescription
01 << 0StaffDuvet developer

User Badges (bitfield)

BitValueNameDescription
01 << 0SillySilly badge

Permission Flags (bitfield)

Used by a role’s permissions and by a room overwrite’s allow_flags / deny_flags. A member’s base permissions are the bitwise OR of all their roles. In a room, overwrites for the roles the member holds are then applied in three steps, each removing its denied bits before adding its allowed bits: the @everyone overwrite (the @everyone role’s ID is the space ID), then the member’s other role overwrites combined, then the member’s own overwrite. The space owner and members with Administrator get every permission, including in rooms with overwrites; Administrator only works when granted by a role, not by an overwrite.

BitValueNameDescription
01 << 0AdministratorFull administrative control over the space
11 << 1ViewChannelsView and access rooms
21 << 2SendMessagesPost messages in rooms
31 << 3ManageMessagesEdit and delete messages in rooms
41 << 4ManageChannelsCreate, edit, and delete rooms
51 << 5ChangeNicknameChange their nickname in the space
61 << 6JoinVoiceJoin voice rooms
71 << 7SpeakInVoiceSpeak while connected to a voice room
81 << 8ManageRolesCreate, edit, and delete roles
91 << 9ManageInvitesRevoke invite links in the space
101 << 10CreateInvitesCreate invite links to the space
111 << 11ManageSpaceEmojisCreate, edit, and delete custom emojis
121 << 12ManageSpaceBucketsManage storage buckets and quotas
131 << 13ManageSpaceStickersCreate, edit, and delete stickers
141 << 14ManageBotsAdd, remove, and configure bot applications
151 << 15ManageSpaceAttachmentsManage file attachments and storage
161 << 16ManageSpaceWebhooksCreate, edit, and delete webhooks
171 << 17PinMessagesPin and unpin messages in rooms

Values with bits outside this table are rejected with 400 and {"error": "permissions contain unknown flags"}.

Message Reference Type

ValueName
0Reply
1Forward