Data schemas
The server stores internal user IDs but serializes identities for clients. Unless noted otherwise, user fields in public messages are usernames.
interface PublicUser { username: string; roles: string[]; color: string | null; nickname?: string; status?: { status: 'online' | 'idle' | 'dnd' | 'offline' | 'invisible'; text: string };}The first role with a color determines color; role ordering therefore matters. Administrative responses can include internal account fields such as last_login.
Time units
Section titled “Time units”Handshake, message, poll, status, unread, and signing timestamps are Unix seconds; fractional seconds are possible. user_timeout.duration and slow_mode_cooldown are seconds. A rate_limit.length value is milliseconds. The typing.duration field is forwarded unchanged and has no server-assigned unit.
Channel
Section titled “Channel”interface Channel { name: string; type: 'text' | 'voice' | 'forum' | 'separator'; display_name?: string; description?: string; /** For separators, the UI spacing/padding rendered around the separator. */ size?: number; /** Stored/serialized metadata; not enforced by the current server. */ max_message_age?: number; slow_mode_cooldown?: number; permissions: Record<'view' | 'send' | 'delete' | 'delete_own' | 'edit' | 'edit_own' | 'react' | 'pin' | 'create_thread', string[]>; last_message?: number | null; last_message_id?: string | null; last_message_by_user?: number | null; threads?: Thread[]; voice_state?: Array<{ username: string; muted: boolean }>;}Only fields relevant to the channel type appear. Separators are preserved in visible channel lists for layout.
For separator channels, size is explicitly a client presentation value. It represents the padding or vertical spacing around the separator; it is not a member, message, or storage limit.
Message
Section titled “Message”interface Message { type: 'message'; id: string; user: string; timestamp: number; content: string; thread_id?: string; edited?: true; edited_by?: string; embeds?: unknown[]; attachments?: Attachment[]; reactions?: Record<string, string[]>; pinned?: boolean; pings?: unknown; reply_to?: { user: string; id: string; preview: string }; ping?: boolean; interaction?: { username: string; command: string; args?: unknown[]; author_id?: string; key_id?: string; signature?: string; timestamp?: number; nonce?: string }; author_id?: string; key_id?: string; signature?: string; signed_at?: number;}Replies retain only a 40-character preview. A ping value on a public message reflects whether its reply pings the original author. Polls are messages with an embed whose type is poll; the server hydrates poll details for the viewer.
Thread
Section titled “Thread”interface Thread { id: string; parent_channel: string; name: string; created_by: string; created_at: number; participants: string[]; archived: boolean; locked: boolean; pinned?: boolean; last_message?: number | null; last_message_id?: string | null;}interface Role { id: string; description: string; color: string | null; gradient: unknown[] | null; permissions: string[]; hoisted: boolean; self_assignable: boolean; category: string | null; position: number;}Roles are returned as an object keyed by role name. owner, admin, and user are protected system roles. The default owner role has owner and administrator; the permission helper treats these as broad grants.
Error and rate limit
Section titled “Error and rate limit”type ErrorFrame = { cmd: 'error'; val: string; src?: string; listener?: unknown };type RateLimitFrame = { cmd: 'rate_limit'; reason?: string; length: number };See signing and verification for the exact canonical payload represented by author_id, key_id, signature, and signed_at.
