Skip to content

Invite Links

Together supports shareable invite links that allow users to join a server. Each invite has a unique 8-character alphanumeric code, and can optionally be configured with an expiry time and a maximum number of uses.


Overview

  • Invite codes are 8 random alphanumeric characters (e.g. aB3xK9mZ)
  • Codes are unique across the system (retried up to 3 times on collision)
  • Invites can optionally expire after 1–720 hours (30 days)
  • Invites can optionally have a maximum use count
  • An invite with no expiry and no max uses is valid indefinitely
  • Creating, listing, and deleting invites requires the CREATE_INVITES permission (bit 14, value 16384), or ADMINISTRATOR, or server ownership
  • Previewing and accepting invites requires only authentication (no server membership)

Data Model

ServerInvite

FieldTypeDescription
idUUIDUnique invite identifier
server_idUUIDServer the invite belongs to
codestring8-character alphanumeric invite code
created_byUUID?User who created the invite
max_usesinteger?Maximum number of times the invite can be used
usesintegerNumber of times the invite has been used
expires_atdatetime?UTC expiry timestamp, null for no expiry
created_atdatetimeUTC creation timestamp

InvitePreviewDto

Returned by the preview endpoint for unauthenticated invite viewing.

FieldTypeDescription
codestringThe invite code
server_namestringName of the server
server_icon_urlstring?Server icon URL, nullable
member_countintegerCurrent number of members in the server
expires_atdatetime?UTC expiry timestamp, null for no expiry

CreateInviteRequest

FieldTypeRequiredDescription
max_usesintegernoMust be > 0 if provided
expires_in_hoursintegernoMust be 1–720 (up to 30 days) if provided

The request body uses deny_unknown_fields — extra fields cause a deserialization error.


Endpoints

All endpoints require a valid Bearer token unless otherwise noted.

Create an Invite

POST /servers/:server_id/invites
Authorization: Bearer <token>

Request body:

json
{
  "max_uses": 10,
  "expires_in_hours": 24
}

Both fields are optional. Omitting both creates a permanent, unlimited-use invite.

Response: 201 Created with the full ServerInvite object.

Errors:

ConditionStatusMessage
Not a server member404Server not found
Missing CREATE_INVITES permission403You need the Create Invites permission
max_uses is 0 or negative400max_uses must be greater than 0
expires_in_hours out of range400expires_in_hours must be between 1 and 720

Side effects:

  • Audit log entry with action InviteCreate (target_type: invite, details include code and max_uses)
  • WebSocket INVITE_CREATE event broadcast to all server members

List Invites

GET /servers/:server_id/invites
Authorization: Bearer <token>

Returns all invites for the server (including expired and fully used ones), ordered by created_at DESC.

Response: 200 OK with an array of ServerInvite objects.

Errors:

ConditionStatusMessage
Not a server member404Server not found
Missing CREATE_INVITES permission403You need the Create Invites permission

Delete an Invite

DELETE /servers/:server_id/invites/:invite_id
Authorization: Bearer <token>

Response: 204 No Content

Errors:

ConditionStatusMessage
Not a server member404Server not found
Missing CREATE_INVITES permission403You need the Create Invites permission
Invite not found in this server404Invite not found

Side effects:

  • Audit log entry with action InviteRevoke (target_type: invite)
  • WebSocket INVITE_DELETE event broadcast to all server members with server_id and invite_id

Preview an Invite

GET /invites/:code
Authorization: Bearer <token>

Any authenticated user can preview an invite to see basic server information before joining. The endpoint filters out expired invites and invites that have reached their maximum uses.

Response: 200 OK with an InvitePreviewDto object.

Errors:

ConditionStatusMessage
Invite not found, expired, or maxed out404Invite not found or has expired

Accept an Invite

POST /invites/:code/accept
Authorization: Bearer <token>

Accepts the invite and joins the server. The use count is incremented atomically inside a database transaction to prevent race conditions.

Response: 201 Created

json
{
  "message": "Joined server",
  "server_id": "uuid"
}

Errors:

ConditionStatusMessage
Invite code not found404Invite not found
Invite has expired400This invite has expired
Invite has reached max uses400This invite has reached its maximum uses
User is banned from the server403You are banned from this server
User is already a member409Already a member of this server

Atomic uses counter: The UPDATE server_invites SET uses = uses + 1 WHERE id = $1 AND (max_uses IS NULL OR uses < max_uses) query runs inside the same transaction as the member insert. If a concurrent request exhausts the invite between the initial check and the update, the WHERE clause prevents the increment and the transaction is rejected with a 400 error. This prevents the invite from being used more times than max_uses even under concurrent requests.


Permission Model

Invite management (create, list, delete) requires the CREATE_INVITES permission:

BitValueName
1416384CREATE_INVITES

Access is also granted if the user is the server owner or has the ADMINISTRATOR permission (bit 13, value 8192). This follows the same pattern as all other permission checks in Together.

Previewing and accepting invites does not require any server permission — only a valid authentication token.


WebSocket Events

Both invite events are delivered as DISPATCH messages to all members of the server.

INVITE_CREATE

Broadcast when a new invite is created. Payload is the full ServerInvite object.

json
{
  "id": "uuid",
  "server_id": "uuid",
  "code": "aB3xK9mZ",
  "created_by": "uuid",
  "max_uses": 10,
  "uses": 0,
  "expires_at": "2026-03-23T12:00:00Z",
  "created_at": "2026-03-22T12:00:00Z"
}

INVITE_DELETE

Broadcast when an invite is revoked.

json
{
  "server_id": "uuid",
  "invite_id": "uuid"
}

Audit Logging

Invite operations write to the audit log. Logging is non-blocking — if the write fails, the operation is not rolled back.

Actiontarget_typetarget_iddetails
InviteCreateinviteInvite UUID{ "code": "...", "max_uses": 10 }
InviteRevokeinviteInvite UUID{}