# RtCord API

The API is served from the same host and port as the web application. The
default base URL is `http://localhost:8090`.

## Authentication

Login creates an HttpOnly session cookie named `rtcord_session`. Send that
cookie with authenticated requests. Sessions are stored in memory and expire
when the server stops.

JSON requests should use:

```http
Content-Type: application/json
```

Successful JSON responses use `application/json`. Errors are returned as plain
text with an appropriate HTTP status.

### Account standing

| Standing | Name | Restrictions |
|---:|---|---|
| 0 | All good | No standing restrictions |
| 1 | Limited | Cannot accept invites or join new servers |
| 2 | Very limited | Cannot create group chats or send DM/group-chat messages |
| 3 | At risk | Cannot send messages anywhere |
| 4 | Suspended | Cannot use normal authenticated APIs; account pages remain available |

## Authentication endpoints

### `POST /api/register`

Create an account.

JSON body:

```json
{
  "username": "alice",
  "password": "correct-horse-battery-staple"
}
```

Usernames are 3-32 characters and may contain letters, numbers, `_`, and `-`.
Passwords must be 8-72 characters.

Response: `201 Created`

```json
{
  "user_id": "UUID"
}
```

Browser form submissions redirect to `/?registered=1`.

### `POST /api/login`

Authenticate and create a session.

JSON body:

```json
{
  "username": "alice",
  "password": "correct-horse-battery-staple"
}
```

Response: `200 OK`

```json
{
  "user_id": "UUID",
  "suspended": false
}
```

Browser form submissions redirect to `/?logged_in=1`, or to
`/account/warnings` when suspended.

### `POST /api/logout`

Clear the current session. JSON clients receive `204 No Content`; browser form
submissions redirect to `/`.

## Users and account status

### `GET /api/users/{user_id}`

Return a username by UUID.

Response:

```json
{
  "user_id": "UUID",
  "username": "alice"
}
```

### `GET /api/users/me`

Return the UUID associated with the current session.

### `GET /api/account`

Return the current user's standing and warnings. This endpoint is also
available to suspended users.

Response:

```json
{
  "account_standing": 1,
  "warnings": [
    {
      "id": "UUID",
      "issued_by": "admin",
      "reason": "Reason for warning",
      "created_at": "2026-09-12T10:00:00Z"
    }
  ]
}
```

The response also includes `email` and `email_verified`. Existing accounts can
attach or replace an email with:

### `PUT /api/account/email`

```json
{
  "email": "you@example.com"
}
```

This sends a verification link.

### `GET /api/verify-email?token=TOKEN`

Verify an email address from the link sent by RtCord.

### `POST /api/password-reset/request`

Request a password reset email:

```json
{
  "email": "you@example.com"
}
```

### `POST /api/password-reset`

Set a new password using the token from the reset link:

```json
{
  "token": "TOKEN",
  "password": "new-password"
}
```

## Servers

All server endpoints require authentication. Server IDs and channel IDs are
UUIDs.

### `POST /api/servers`

Create a server.

```json
{
  "name": "My server"
}
```

Response: `201 Created`

### `GET /api/servers`

List servers the current user belongs to.

### `GET /api/servers/{server_id}/channels`

List channels in a server.

### `POST /api/servers/{server_id}/channels`

Create a channel. Requires the owner or a role with
`full_server_admin`.

```json
{
  "name": "general"
}
```

### `GET /api/servers/{server_id}/members`

List server members and their server roles.

### `GET /api/servers/{server_id}/roles`

List server roles.

### `POST /api/servers/{server_id}/roles`

Create a custom role. Only the server owner can create roles.

```json
{
  "name": "moderator",
  "full_server_admin": true
}
```

### `PUT /api/servers/{server_id}/members/{user_id}/role`

Assign a custom role. Only the server owner can assign roles.

```json
{
  "role": "moderator"
}
```

### `POST /api/servers/{server_id}/invites`

Create a seven-day invite. Any server member may create one.

Response: `201 Created`

```json
{
  "token": "invite-token",
  "server_id": "UUID",
  "expires_at": "2026-09-19T10:00:00Z",
  "url": "http://localhost:8090/invites/invite-token"
}
```

### `POST /api/invites/{token}/accept`

Accept an invite. Limited accounts and above cannot join new servers.

Response:

```json
{
  "server_id": "UUID"
}
```

## Server messages

### `GET /api/servers/{server_id}/channels/{channel_id}/messages`

List channel messages.

### `POST /api/servers/{server_id}/channels/{channel_id}/messages`

Create a channel message. At-risk accounts cannot send messages.

JSON body:

```json
{
  "content": "Hello"
}
```

Attachments use `multipart/form-data` instead:

```text
content: Hello
attachments: one or more files
```

Message content may be empty when at least one attachment is supplied. Up to
five attachments are allowed, with a 25 MiB limit per file.

To reply to a message, include its ID:

```json
{
  "content": "I agree",
  "reply_to": "MESSAGE-UUID"
}
```

For multipart requests, send `reply_to` as a regular form field. The referenced
message must be in the same channel.

### `PUT /api/servers/{server_id}/channels/{channel_id}/messages/{message_id}`

Edit a message. Only the author can edit it.

```json
{
  "content": "Updated message"
}
```

### `DELETE /api/servers/{server_id}/channels/{channel_id}/messages/{message_id}`

Delete a message. The author or a full server administrator may delete it.

### `PUT /api/servers/{server_id}/channels/{channel_id}/messages/{message_id}/reactions`

Add an emoji reaction to a channel message. A user can add each emoji only
once.

```json
{
  "emoji": "👍"
}
```

### `DELETE /api/servers/{server_id}/channels/{channel_id}/messages/{message_id}/reactions`

Remove the current user's reaction. Use the same JSON body as the `PUT`
endpoint. Both reaction endpoints return the updated message.

## Legacy server messages

These endpoints are retained for compatibility:

- `GET /api/servers/{server_id}/messages`
- `POST /api/servers/{server_id}/messages`

They use the same `{"content":"..."}` message body and standing restrictions as
channel messages.

## Friends

### `GET /api/friends`

List accepted friends.

### `GET /api/friends/requests`

List pending incoming friend requests.

### `POST /api/friends/requests`

Send a friend request by username.

```json
{
  "username": "bob"
}
```

### `POST /api/friends/requests/{request_id}/accept`

Accept a pending friend request.

### `POST /api/friends/requests/{request_id}/reject`

Reject a pending friend request.

## Direct messages and group chats

### `GET /api/conversations`

List conversations for the current user.

### `POST /api/conversations/dm`

Create or reuse a DM with one friend.

```json
{
  "usernames": ["bob"]
}
```

### `POST /api/conversations/group`

Create a group chat with 1-25 friends.

```json
{
  "name": "Project chat",
  "usernames": ["bob", "carol"]
}
```

Very limited accounts cannot create conversations through this endpoint.

### `GET /api/conversations/{conversation_id}/messages`

List messages in a DM or group chat.

### `POST /api/conversations/{conversation_id}/messages`

Send a DM/group-chat message. Very limited and at-risk accounts cannot send
these messages.

JSON body:

```json
{
  "content": "Hello"
}
```

For attachments, use `multipart/form-data` with a `content` field and one or
more `attachments` fields. Attachment-only messages are supported. Include
`reply_to` to reply to a message in the same conversation.

### `PUT /api/conversations/{conversation_id}/messages/{message_id}/reactions`

Add an emoji reaction to a DM or group-chat message.

### `DELETE /api/conversations/{conversation_id}/messages/{message_id}/reactions`

Remove the current user's reaction. Both reaction endpoints accept:

```json
{
  "emoji": "❤️"
}
```

Message responses include `reactions`, an array of `{ "emoji", "count",
"reacted" }` objects.

## Attachments

### `GET /uploads/{attachment_id}`

Download or stream an attachment. Access requires membership in the related
server or conversation. Images, audio, and video can be rendered directly by
the browser; unsupported files should be downloaded.

Attachment response objects have this shape:

```json
{
  "id": "UUID",
  "name": "example.png",
  "url": "/uploads/UUID",
  "content_type": "image/png",
  "size": 12345,
  "preview_kind": "image"
}
```

`preview_kind` may be `code`, `image`, `audio`, or `video`.

## WebSocket

### `GET /api/channels/{channel_id}/ws`

Open an authenticated WebSocket connection for live channel messages.

New messages are JSON objects matching the server message response. Edit and
delete events include the `event` field:

```json
{
  "event": "deleted",
  "id": "UUID",
  "server_id": "UUID",
  "channel_id": "UUID"
}
```

The client must already be a member of the server containing the channel.

### `GET /api/conversations/{conversation_id}/ws`

Open an authenticated WebSocket connection for live DM or group-chat messages.
New messages and reaction updates are broadcast as conversation message
objects. The client must be a member of the conversation.

## Administrator endpoints

Administrator endpoints require an authenticated user whose `users.is_admin`
value is enabled in the database.

## Reports

### `POST /api/reports`

Submit a report with a free-text reason. Supported target types are `user`,
`server`, `server_message`, and `conversation_message`.

```json
{
  "type": "server_message",
  "target": "UUID",
  "reason": "Why this content should be reviewed"
}
```

Message reports require membership in the related server or conversation.
Reports are created with `pending` status.

### `GET /api/admin/reports`

List reports for administrators, with pending reports first.

### `PUT /api/admin/reports/{report_id}`

Set a report status:

```json
{
  "status": "resolved"
}
```

The status may be `pending` or `resolved`.

### `GET /api/admin/me`

Check administrator access.

### `GET /api/admin/users`

List users, account standings, administrator status, and warnings.

### `GET /api/admin/warnings`

List all account warnings.

### `POST /api/admin/users/{user_id}/warnings`

Issue a warning. The user's standing is recalculated from warning count.

```json
{
  "reason": "Reason for warning"
}
```

### `DELETE /api/admin/warnings/{warning_id}`

Remove a warning and recalculate the affected user's standing.

### `PUT /api/admin/users/{user_id}/standing`

Set a standing directly from `0` through `4`.

```json
{
  "standing": 2
}
```

## Common status codes

| Status | Meaning |
|---:|---|
| 200 | Successful request |
| 201 | Resource created |
| 204 | Successful request with no response body |
| 400 | Invalid method payload, ID, or field |
| 401 | Authentication required |
| 403 | Authenticated but not permitted |
| 404 | Resource not found or inaccessible |
| 409 | Conflict, such as a duplicate username or existing request |
| 500 | Server or database error |
