Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.7331.org/llms.txt

Use this file to discover all available pages before exploring further.

v1.12.0

Stranger geo enrichment + session blocklist (2026-05-10)

Stranger identity enrichment

When a stranger match is created, the server now captures the stranger’s country (resolved from their IP via geo lookup) and includes it in the match:found peer object as country_code (ISO 3166-1 alpha-2, or null if unavailable). This gives both parties a lightweight location signal without exposing the raw IP.

Stranger moderation blocklist

Moderators can now block a stranger’s anonymous session from future matchmaking. The block is based on a signature derived from the stranger’s session token — it survives reconnects as long as the session cookie is unchanged. Blocked strangers receive 403 ERR_STRANGER_BLOCKED when they attempt to enqueue.

REST surface changes

  • POST /v1/portal/match — new 403 ERR_STRANGER_BLOCKED for blocked stranger sessions.

v1.11.0

Direct chat archive + multi-interest matching + slot limits (2026-05-06)

Matchmaking overhaul

Multi-interest queuing. The POST /v1/portal/match body now accepts an interests array (1–3 tags) instead of a single interest string. Users are placed into all listed queues simultaneously — the first peer match on any shared tag wins. The matched tags are listed in the opening system message: "Chat started — say hi to {peer}! Matched on: tag1, tag2". Open-ended tags. Interest tags are now free-form alphanumeric strings (1–12 characters). The server no longer maintains an allow-list — any word-like tag is accepted. Concurrent DIRECT slot limits. Each user has a cap on the number of simultaneous active DIRECT chats:
PlanSlots
Free1
Premium3
Attempting to enqueue when all slots are occupied returns 409 ERR_MATCH_SLOT_LIMIT. Per-user ZSET tracking. Active and ended DIRECT chats are now tracked in per-user Redis sorted sets (score = expiry timestamp) instead of a single string key. This enables multi-slot support and lets the slot counter ignore expired entries automatically.

Chat archive lifecycle

When a DIRECT chat ends (explicit leave or grace expiry), it now enters a 24-hour read-only archive rather than being deleted:
  • The chat hash and message stream are preserved with a 24h TTL.
  • The chat is moved to the user’s ended-chat ZSET (score = archive deadline).
  • GET /v1/portal/chats includes archived entries with "archived": true.
  • The socket chat:list event surfaces archived chats on reconnect.
  • Archived chats render read-only and dimmed in the sidebar; they disappear after 24h.

REST surface changes

  • POST /v1/portal/match — body field renamed interestinterests (array). New 409 ERR_MATCH_SLOT_LIMIT.
  • GET /v1/portal/match/status — response field renamed interestinterests (array).
  • GET /v1/portal/chats — response entries now include "archived": boolean.
  • GET /v1/portal/match/statisticsnew endpoint: returns online count and top 5 tags by queue depth. Requires registered session (strangers receive 401).

v1.10.0

Portal repositioning + status-code standardization (2026-04-21) Docs now lead with Portal (shared virtual browsers + E2EE chat + 1-on-1 matchmaking). Bot management is reframed as a community-operator integration surface. New concept pages for chat types, E2EE, and matchmaking. API surface changes:
  • Match endpoints documented: POST /v1/portal/match, GET /v1/portal/match/status, POST /v1/portal/match/dequeue, POST /v1/portal/match/leave. Dequeue was flipped from DELETE to POST this release to align with the v1.9.0 POST+GET-only rule — the old path is gone.
  • DIRECT-chat system messages persisted on explicit match/leave — peer sees ” left the chat” in their timeline and in get_history.
  • match enqueue now returns 409 ERR_MATCH_NO_PEERS_ONLINE when the caller is the only user online (previously accepted and sat alone until the 120s timeout).
  • Notifications grouped into their own nav section with full coverage: unread count, mark-read (409 vs 404 now disambiguated), mark-all-read, admin delete.
Status code corrections across api/routes/portal.py and admin/chats.py:
EndpointBeforeAfterWhy
POST /v1/portal/match/leave (drift)404 or 409 collapsed404 for missing DIRECTDistinct client-drift signal
POST /v1/notifications/{id}/read404 for already-read409 ERR_NOTIFICATION_ALREADY_READNot a 404 when the row exists
Browse private chat400403Permission denial, not validation
Chat start failure (transient)422503Infra unavailable, not bad input
Chat-not-running on moderation404409Wrong chat state, not absent
Invite / clipboard infra failure500503External service, not a server bug
Error-message cleanups in core/constants/errors.py:
  • ERR_ROOM_NOT_RUNNING — “Chat not found or not running” → “Chat is not running”
  • ERR_NOTIFICATION_ALREADY_READ — “Notification not found or already read” → “Notification is already read”
  • Added ERR_ROOM_START_FAILED, ERR_CANNOT_BROWSE_PRIVATE, ERR_MATCH_NO_PEERS_ONLINE.
Admin chat mutations (kick/ban/mute/warn) now persist system messages so late joiners see the moderation trail via get_history.

v1.9.0

POST+GET-Only API (2026-04-20) Breaking changes (no deprecation period). The entire REST surface is now POST+GET-only — no PUT, no DELETE. Every mutation has an explicit verb in its path, and target entity IDs live in the JSON body, not the URL path. Method flips (URL unchanged):
  • PUT /v1/notifications/{id}/readPOST
  • PUT /v1/notifications/read-allPOST
Path flips (verb-in-path):
  • PUT /v1/users/mePOST /v1/users/me/update
  • PUT /v1/bot/mePOST /v1/bot/me/update
  • PUT /v1/bot/users/{user_id}POST /v1/bot/users/{user_id}/update
  • PUT /v1/admin/bots/{bot_id}/updatePOST /v1/admin/bots/{bot_id}/update
  • PUT /v1/admin/users/{user_id}POST /v1/admin/users/{user_id}/update
  • PUT /v1/admin/chats/{room_id}POST /v1/admin/chats/{room_id}/update
  • DELETE /v1/admin/bots/{bot_id}POST /v1/admin/bots/{bot_id}/delete
  • DELETE /v1/admin/notifications/{id}POST /v1/admin/notifications/{id}/delete
  • DELETE /v1/admin/users/{user_id}POST /v1/admin/users/{user_id}/delete
  • DELETE /v1/admin/chats/{room_id}POST /v1/admin/chats/{room_id}/delete
  • DELETE /v1/portal/chats/{room_id}POST /v1/portal/chats/{room_id}/delete
  • PUT /v1/portal/chats/{room_id}/configPOST /v1/portal/chats/{room_id}/config/update
  • DELETE /v1/portal/chats/{room_id}/invites/{code}POST /v1/portal/chats/{room_id}/invites/{code}/delete
Chat moderation (ID moved from path to body):
  • POST /v1/portal/chats/{room_id}/kicksPOST /v1/portal/chats/{room_id}/kick (body: {user_id, ...})
  • POST /v1/portal/chats/{room_id}/bansPOST /v1/portal/chats/{room_id}/ban
  • POST /v1/portal/chats/{room_id}/mutesPOST /v1/portal/chats/{room_id}/mute
  • POST /v1/portal/chats/{room_id}/warningsPOST /v1/portal/chats/{room_id}/warn
  • POST /v1/portal/chats/{room_id}/timeoutsPOST /v1/portal/chats/{room_id}/timeout
  • DELETE /v1/portal/chats/{room_id}/bans/{user_id}POST /v1/portal/chats/{room_id}/unban (body: {user_id, reason})
  • DELETE /v1/portal/chats/{room_id}/mutes/{user_id}POST /v1/portal/chats/{room_id}/unmute (body: {user_id})
  • POST /v1/portal/hard-mute/{user_id}POST /v1/portal/hard-mute (body: {user_id, ...})
  • POST /v1/portal/hard-unmute/{user_id}POST /v1/portal/hard-unmute (body: {user_id})
Message deletion consolidated into three verb-explicit endpoints:
  • DELETE /v1/portal/chats/{room_id}/messages/{message_id}POST /v1/portal/chats/{room_id}/messages/delete (body: {ids: [...]}, 1–100 ids)
  • DELETE /v1/portal/chats/{room_id}/messages/minePOST /v1/portal/chats/{room_id}/messages/delete-mine
  • DELETE /v1/portal/chats/{room_id}/messages/last/{count}POST /v1/portal/chats/{room_id}/messages/delete-last (body: {count})
Admin punishments (verb paths, user_id in body):
  • POST /v1/admin/punishments/user/{user_id}/banPOST /v1/admin/punishments/ban
  • POST /v1/admin/punishments/user/{user_id}/warnPOST /v1/admin/punishments/warn
  • POST /v1/admin/punishments/user/{user_id}/timeoutPOST /v1/admin/punishments/timeout
  • POST /v1/admin/punishments/user/{user_id}/unbanPOST /v1/admin/punishments/unban
Admin subscriptions (grant/trial consolidated, user_id in body):
  • POST /v1/admin/subscriptions/user/{user_id}/grantPOST /v1/admin/subscriptions/grant (body: {user_id, tier, duration_days, reason})
  • POST /v1/admin/subscriptions/user/{user_id}/trial → folded into /grant with tier=TRIAL
  • POST /v1/admin/subscriptions/user/{user_id}/revokePOST /v1/admin/subscriptions/revoke (body: {user_id, reason})
Rationale: single verb family (POST for mutations, GET for reads), predictable path grammar, simpler client tooling, no hidden bodies on DELETE. Clients must update URLs, methods, and — where noted — move user_id / message_id / count from path to JSON body.

v1.8.0

Chat Moderation Route Restructure (2026-04-19) Breaking changes (no deprecation period):
  • Chat moderation create-punishment endpoints now use collection-resource URLs with user_id in the JSON body instead of the path:
    • POST /v1/portal/chats/{room_id}/ban/{user_id}POST /v1/portal/chats/{room_id}/bans (user_id in body)
    • POST /v1/portal/chats/{room_id}/mute/{user_id}POST /v1/portal/chats/{room_id}/mutes (user_id in body)
    • POST /v1/portal/chats/{room_id}/kick/{user_id}POST /v1/portal/chats/{room_id}/kicks (user_id in body)
    • POST /v1/portal/chats/{room_id}/warn/{user_id}POST /v1/portal/chats/{room_id}/warnings (user_id in body)
    • POST /v1/portal/chats/{room_id}/timeout/{user_id}POST /v1/portal/chats/{room_id}/timeouts (user_id in body)
  • Reversal endpoints are now DELETE on the collection resource:
    • POST /v1/portal/chats/{room_id}/unban/{user_id}DELETE /v1/portal/chats/{room_id}/bans/{user_id}
    • POST /v1/portal/chats/{room_id}/unmute/{user_id}DELETE /v1/portal/chats/{room_id}/mutes/{user_id}
Rationale:
  • Aligns the chat moderation surface with REST collection-resource conventions (POST creates, DELETE removes).
  • Request/response payloads and authorization rules are otherwise unchanged.
Clients must update both the URL and the body shape. The previous paths are removed outright.

v1.7.0

Notifications, Membership History, UI Polish New features:
  • Notification system — admins can send targeted or broadcast notifications to users. 5 severity types: success (green), info (blue), warning (amber), danger (red), system (gray). Rich body rendering with bold and links.
  • Notification templates — automated notifications on platform events: welcome, banned, unbanned, timed out, warned, kicked, trial started, subscription upgraded/revoked, permission changed, new IP login. Wired at the service layer so every code path (routes, cron jobs, socket handlers) triggers notifications.
  • Chat membership history — every join, leave, kick, and ban is logged with join method (direct, vanity, invite) and invite code. Full timeline per chat.
  • Universal chat membership — joining any chat (public, private, official) now creates a DB membership record. Previously only private chats via invite created DB members.
New endpoints:
  • GET /v1/notifications — list user notifications (paginated)
  • GET /v1/notifications/unread — unread count for badge
  • PUT /v1/notifications/{id}/read — mark as read
  • PUT /v1/notifications/read-all — mark all as read
  • POST /v1/admin/notifications — create targeted or broadcast notification
  • DELETE /v1/admin/notifications/{id} — delete notification
  • GET /v1/portal/chats/{room_id}/activity — chat membership activity log
New socket events:
  • notification:new — real-time push when admin sends a notification
New audit events:
  • NOTIFICATION_CREATE (801), NOTIFICATION_BROADCAST (802), NOTIFICATION_DELETE (803)
New database tables:
  • notifications — user notifications with type, read state, and sender
  • room_membership_history — join/leave/kick/ban events per chat
Frontend:
  • Mail icon in sidebar footer with unread dot
  • Inbox panel with All/Unread filter tabs
  • Notification detail modal with colored header, type badge, rich body rendering
  • Session history: grouped by day, separate detail popup modal with map
  • Avatar picker: reset glyph to Discord pfp, cleaner grid layout
  • Account section: key-value layout with colored role/plan values
  • Removed logout from sidebar (lives in settings only)

v1.6.0

Bot-Initiated Auth, Bulk Deletion, UI Overhaul New features:
  • Bot-initiated login — new POST /v1/authentication/bot/send-code endpoint lets the Discord bot generate login codes directly via /otp. Skips the web request step for returning users. Separate Redis key pools per login source prevent code collisions.
  • Fizzbuzz web login — after every 5 bot logins, users must log in via the website once. Configurable via BOT_LOGIN_WEB_REQUIRED_EVERY constant.
  • Bulk message deletion — two new endpoints: DELETE /v1/portal/chats/{room_id}/messages/mine (delete all your own messages) and DELETE /v1/portal/chats/{room_id}/messages/last/{count} (mods+ delete last 1–100 messages). Both broadcast message:bulk_deleted socket event.
  • Public health endpoint enrichedGET /v1/health now returns source_hash and uptime_seconds.
New endpoints:
  • POST /v1/authentication/bot/send-code — Bot-initiated login code generation
  • DELETE /v1/portal/chats/{room_id}/messages/mine — Delete all own messages in a chat
  • DELETE /v1/portal/chats/{room_id}/messages/last/{count} — Delete last N messages (mod+)
New socket events:
  • message:bulk_deleted — Payload: {room_id, deleted_ids[], deleted_by}
Security:
  • Removed neko-rooms public exposure from HAProxy
  • Disabled Swagger/OpenAPI in production (docs_url=None)
  • Login source tracking (LoginSource enum: web / bot) with separate Redis key pools
Frontend:
  • Floating window layout (desktop lg+)
  • Auth page: dynamic bot status gating, bot calling card with /otp instructions, invite link, OTP expiry with red expired state
  • Permission badge system (colored star icons replacing emoji crowns)
  • User profile page: avatar picker, login history with Leaflet map, logout confirmation
  • Chat settings redesign: tabbed layout with member grid, auto-wipe modal, invite management
  • API source hash displayed under Portal title with clickable stats popup (status, database, cache, uptime)
  • Vanity URL routing with useParams + window.history.replaceState

v1.6.0

Per-Session E2EE Keypairs (Multi-Device) Replaces the old “one keypair per user” model with per-session keypairs indexed by the psession cookie. Multiple devices now coexist — no sync dance, no session eviction. Changes:
  • Keypair sync endpoints removed (POST /v1/portal/keypair-sync/request|respond|claim). Each device generates and registers its own keypair on login.
  • POST /v1/portal/register-key now associates the pubkey with the current session cookie.
  • Chat key distribution fans out: the sender encrypts the shared chat key once per recipient session using nacl.box.
  • Key ring is now {user_id: {session_id: pubkey}} on the wire (nested).
  • portal:key_added / portal:key_removed carry session_id. Removal with session_id: null means “drop every session of this user” (ban / full disconnect).
  • Presence is session-scoped — portal:presence and room:members emit one entry per active session so multi-device users show on both devices.
  • Session enforcement (MAX_SESSIONS_PER_USER=1) is lifted.
Security:
  • POST /v1/portal/chats/{room_id}/messages continues to enforce server-side key possession (403 ERR_EPOCH_MISMATCH, 403 ERR_NO_ROOM_KEY).

v1.4.1

Chat ID Migration Cleanup Breaking changes:
  • room_join socket event now accepts room_id, vanity, or invite_code (new RoomJoinInput union model). Raw room_name for join is no longer supported.
  • ROOM_ALREADY_EXISTS socket error has been removed. Chat names are non-unique — multiple chats can share the same display name.
Type safety:
  • browser_settings on CreateRoomRequest, UpdateRoomRequest, and RoomDetailResponse is now typed as BrowserConfig (was dict[str, Any]).
  • build_status_payload returns WipeStatusPayload model directly (callers serialize at emit boundary).
Fixes:
  • Neko client cache eviction in stop_room now runs before any I/O, preventing stale clients on partial failure.

v1.4.0

Chat Moderation + Hard Mute
Note: The per-chat mute/unmute paths introduced here were later restructured in v1.8.0 — see the top entry for the current URL shape.
Breaking changes:
  • POST /v1/portal/mute/{user_id} has been replaced by per-chat mute (current path: POST /v1/portal/chats/{room_id}/mutes with user_id in body; see v1.8.0).
  • POST /v1/portal/unmute/{user_id} has been replaced by per-chat unmute (current path: DELETE /v1/portal/chats/{room_id}/mutes/{user_id}; see v1.8.0).
  • user:muted and user:unmuted socket events now include a room_id field and are broadcast to the chat only (not globally).
  • DELETE /v1/portal/chats/{room_id}/messages/{message_id} no longer requires Mod+ globally — it uses chat-based authorization.
  • POST /v1/portal/chats/{room_id}/clear no longer requires Admin+ — chat owners can clear their own chats.
New endpoints:
  • POST /v1/portal/chats/{room_id}/mutes — Mute a user in a specific chat (chat owner, MOD+ for public/official, ADMIN+ for any chat). User ID in JSON body.
  • DELETE /v1/portal/chats/{room_id}/mutes/{user_id} — Unmute a user in a specific chat.
  • POST /v1/portal/hard-mute/{user_id} — Hard mute a user across all chats (ADMIN+ only)
  • POST /v1/portal/hard-unmute/{user_id} — Hard unmute a user (ADMIN+ only)
New socket events:
  • user:hard_muted — Broadcast when a user is hard muted globally
  • user:hard_unmuted — Broadcast when a hard mute is removed
Architecture:
  • Two-tier mute system: per-chat mutes (Redis key per chat+user) and hard mutes (global Redis key). Composite is_muted() checks both in a single pipelined round trip.
  • New can_moderate_room() permission helper — chat owner always, MOD+ for public/official, ADMIN+ for private chats. Distinct from can_manage_room() (which locks MODs out of private chats entirely).
  • New audit events: PORTAL_HARD_MUTE (705), PORTAL_HARD_UNMUTE (706).
  • All path parameters now use Annotated types with StringConstraintsRoomName, MessageId, InviteCode, ValidEntityId — validated at the route level with regex and length constraints.

v1.3.2

Per-IP Pending Login Cap Security:
  • Each IP address is now limited to 5 pending login codes at a time. Exceeding this returns 429 Too Many Requests with LoginError.IP_RATE_LIMITED.
  • Codes are tracked via a Redis sorted set per IP, automatically pruned on expiry. Verified or deleted codes immediately free a slot.
  • Configurable via MAX_PENDING_LOGINS_PER_IP constant (default: 5).

v1.3.1

Simplified Signup Flow Breaking changes:
  • POST /v1/authentication/verify no longer accepts a username field. The request body is now just {"code": "..."}.
  • The LoginError.USERNAME_REQUIRED error has been removed.
  • New users no longer choose a username during signup. The backend generates a random username automatically (user_ + random hex). Users can change their username later via PUT /v1/users/me.

v1.3.0

Groups to Chats Migration Breaking changes:
  • All group endpoints have been removed (/groups/*, /admin/groups/*, /bot/groups/*, /statistics/groups)
  • The Group model, GroupService, and GroupState enum have been deleted
  • GET /users/me/groups has been removed
  • Bot group lookup and update endpoints have been removed
New endpoints:
  • GET /v1/admin/chats — List all chats (admin overview)
  • GET /v1/admin/chats/{room_id} — Get chat detail with members and browser settings
  • PUT /v1/admin/chats/{room_id} — Update chat fields (name, visibility, vanity invite, browser settings)
  • DELETE /v1/admin/chats/{room_id} — Hard-delete a chat (DB + Redis + neko container)
  • POST /v1/admin/chats/{room_id}/transfer — Transfer chat ownership (Owner-only)
Architecture:
  • Chats are now first-class DB entities (chats table) with persistent identity, membership, and browser settings
  • RoomDBService handles all DB chat persistence (CRUD, membership, slot checks)
  • RoomService handles Redis live state and neko container lifecycle
  • Chat creation is slot-checked per subscription tier (BASIC: 0, TRIAL/PREMIUM: 1, ADMIN: 5, OWNER: unlimited)
  • Chat visibility: is_public=true (admin/owner chats, visible to all) vs is_public=false (premium user chats, members only)

v1.2.0

Split Backend + Portal Architecture:
  • REST API and Socket.IO now run as separate processes on dedicated subdomains
  • REST API: portal-api.7331.org (port 8000) — FastAPI only
  • WebSocket: portal-ws.7331.org (port 8001) — Socket.IO only (Starlette + python-socketio)
  • Both share the same Postgres and Redis. Cross-process emit via AsyncRedisManager
  • IPC for commands (disconnect user, destroy chat) via Redis pub/sub
Portal:
  • Per-chat — every chat is its own channel, no global chat
  • Chat system with named chats (DB-persisted, slot-checked per subscription tier)
  • Two-tier invite system: vanity invites (permanent, DB) and ephemeral invites (Redis, TTL)
  • End-to-end encrypted messages with per-chat key distribution
  • Chat lifecycle: create, start, stop, destroy with idle auto-shutdown
  • Virtual browser chats via neko-rooms orchestrator (Brave, Tor)
  • Presence sync via Redis key (WS writes on connect/disconnect, REST reads for /portal/status)
New endpoints:
  • GET /v1/portal/status — Online count and user list (reads from Redis presence key)
  • GET /v1/portal/chats/{room_id}/messages — Chat message history
  • DELETE /v1/portal/chats/{room_id}/messages/{message_id} — Delete message
  • POST /v1/portal/mute/{user_id} — Mute user (replaced in v1.4.0)
  • POST /v1/portal/unmute/{user_id} — Unmute user (replaced in v1.4.0)
  • POST /v1/portal/chats/{room_id}/clear — Clear chat messages
  • GET /v1/portal/chats/{room_id}/screenshot — Chat screenshot
  • GET /v1/portal/vanity/{slug} — Resolve vanity invite

v1.1.0

API Gap Remediation New endpoints:
  • GET /statistics/bots — Bot count statistics by verification status
  • GET /admin/subscriptions/active — List all active subscriptions
  • GET /admin/punishments/search — Cross-entity punishment search with filters
  • GET /admin/punishments/{id} — Get a single punishment by ID
  • GET /admin/punishments/stats — Punishment analytics (counts by type over N days)
  • PUT /admin/bots/{bot_id}/update — Update bot name, owner, rate limit, and bot_type (replaces separate verify/unverify endpoints)
  • POST /admin/bots/{bot_id}/regenerate-key — Rotate a bot’s API key
  • POST /admin/bots/{bot_id}/reset-stats — Reset bot cached statistics
Note: Bot search is available at GET /bots/search (public endpoint with permission-aware responses — admins see extended detail, regular users see public profiles). There is no separate admin-only bot search endpoint. Permission changes:
  • User search, user detail, and audit logs now accessible to moderators
  • Moderators see only users below their permission level
  • Moderators see only warn/timeout events in audit logs
Improvements:
  • Audit logging added to bot-initiated user updates
  • Pagination params normalized to offset/limit across all endpoints
  • All service methods now return typed models (no more dict[str, Any])

v1.0.0

Initial Release
  • User authentication via login codes
  • User profile and subscription management
  • Bot registration and statistics tracking
  • Batch command reporting for bots
  • Admin panel with user/bot management
  • Punishment system (ban, warn, timeout, unban)
  • Subscription granting and revocation
  • Audit logging for all admin actions
  • Rate limiting per bot API key
  • Redis caching layer for bot stats