OwnCord server REST API reference. All endpoints use the base URL `https://{server}:{port}/api/v1`.
---
## Authentication
All authenticated endpoints require a session token delivered via the `Authorization: Bearer {token}` header. Tokens are obtained from `POST /api/v1/auth/login`, `POST /api/v1/auth/register`, or `POST /api/v1/auth/verify-totp` after a partial 2FA challenge.
### Session Lifecycle
- Sessions are created on login/register and stored with a SHA-256 hash of the raw token, the client IP, User-Agent, and an expiry timestamp.
- Each authenticated request updates the session's `last_active` timestamp.
- Banned users are rejected at the middleware level with `403 FORBIDDEN`.
### Middleware Stack (all routes)
1.**RequestID** -- assigns a unique `X-Request-Id` response header.
2.**Recoverer** -- catches panics and returns 500.
3.**Request Logger** -- structured logging of method, path, status, duration.
| `channel_id` | int64 | (all channels) | > 0 | Restrict search to a single channel |
| `limit` | int | 50 | 1-100 | Maximum results to return |
#### Response 200 OK
```json
{
"results":[
{
"message_id":1042,
"channel_id":5,
"channel_name":"general",
"user":{
"id":1,
"username":"alex"
},
"content":"...matched text...",
"timestamp":"2026-03-14T10:30:00Z"
}
]
}
```
---
## Direct Messages
DM channels use participant-based authorization rather than role-based permissions.
### POST /api/v1/dms
Create or retrieve a 1-on-1 DM channel with another user. If a DM channel already exists, it is returned and re-opened.
**Auth:** Required
#### Request
```json
{
"recipient_id":2
}
```
#### Response 200 OK (existing channel) or 201 Created (new channel)
```json
{
"channel_id":100,
"recipient":{
"id":2,
"username":"jordan",
"avatar":"uuid.png",
"status":"online"
},
"created":false
}
```
---
### GET /api/v1/dms
List all open DM channels for the authenticated user, ordered by most recent activity.
**Auth:** Required
#### Response 200 OK
```json
{
"dm_channels":[
{
"channel_id":100,
"recipient":{
"id":2,
"username":"jordan",
"avatar":"uuid.png",
"status":"online"
},
"last_message_id":5042,
"last_message":"Hey, how's it going?",
"last_message_at":"2026-03-28T14:30:00Z",
"unread_count":3
}
]
}
```
---
### DELETE /api/v1/dms/{channelId}
Close a DM channel for the authenticated user (hides it from their sidebar). The channel and messages remain in the database. If the other user sends a new message, the channel is automatically re-opened.
**Auth:** Required
#### Response 204 No Content
---
## Invite Endpoints
All invite endpoints require authentication and the `MANAGE_INVITES` permission.
### POST /api/v1/invites
Create a new invite code.
**Auth:** Required
**Permission:**`MANAGE_INVITES`
#### Request
```json
{
"max_uses":5,
"expires_in_hours":48
}
```
Both fields are optional. An empty body creates an invite with unlimited uses and no expiry.
#### Response 201 Created
```json
{
"id":1,
"code":"abc123def",
"max_uses":5,
"uses":0,
"expires_at":"2026-03-30T10:30:00Z",
"revoked":false,
"created_at":"2026-03-28T10:30:00Z"
}
```
---
### GET /api/v1/invites
List all invites (active, expired, and revoked).
**Auth:** Required
**Permission:**`MANAGE_INVITES`
#### Response 200 OK
Returns a JSON array of invite objects.
---
### DELETE /api/v1/invites/{code}
Revoke an invite by its code string.
**Auth:** Required
**Permission:**`MANAGE_INVITES`
#### Response 204 No Content
---
## File Upload and Serving
### POST /api/v1/uploads
Upload a file as multipart form data.
**Auth:** Required
**Body size limit:** 100 MiB
**Content-Type:**`multipart/form-data`
Files are validated against blocked magic bytes (PE executables, ELF binaries, Mach-O binaries, shell scripts). Files are stored with UUID filenames.
Supports HTTP range requests and conditional requests.
---
## Health Check
### GET /health
### GET /api/v1/health
Public health check endpoint, no authentication required.
```json
{
"status":"ok",
"version":"1.0.0",
"uptime":86400,
"online_users":3
}
```
---
## Server Info
### GET /api/v1/info
Returns the server name and version.
**Auth:** None
```json
{
"name":"My OwnCord Server",
"version":"1.2.0"
}
```
---
## Metrics
### GET /api/v1/metrics
Runtime server metrics. Restricted to admin-allowed CIDRs.
**Auth:** Admin IP restriction (not token-based)
```json
{
"uptime":"2h30m15s",
"uptime_seconds":9015.0,
"goroutines":42,
"heap_alloc_mb":12.5,
"heap_sys_mb":24.0,
"num_gc":156,
"connected_users":8,
"livekit_healthy":true
}
```
---
## LiveKit Endpoints
These endpoints are only registered when LiveKit voice is configured.
### POST /api/v1/livekit/webhook
LiveKit webhook receiver. Uses LiveKit JWT verification. Admin-IP-restricted. Called by the LiveKit server, not by clients.
### GET /api/v1/livekit/health
Check whether the LiveKit server is reachable.
**Auth:** Admin IP restriction
#### Response 200 OK
```json
{
"status":"ok",
"livekit_reachable":true
}
```
#### Response 503 Service Unavailable
```json
{
"status":"degraded",
"livekit_reachable":false,
"error":"connection refused"
}
```
### /livekit/* (Reverse Proxy)
All requests to `/livekit/*` are reverse-proxied to the LiveKit server URL. The `/livekit` prefix is stripped before forwarding. This allows the client to connect to LiveKit through OwnCord's HTTPS server, avoiding mixed-content blocks.
**Auth:** None (LiveKit handles its own JWT-based auth)
**Rate limit:** 30 requests/minute per IP
---
## Diagnostics
### GET /api/v1/diagnostics/connectivity
Returns connectivity diagnostics for debugging voice/network issues.
**Auth:** Required (any authenticated user)
**Rate limit:** 5 requests/minute per user
```json
{
"server":{
"version":"1.0.0",
"uptime_s":3600,
"go_version":"go1.23.0",
"online_users":5
},
"voice":{
"enabled":true,
"livekit_url":"ws://localhost:7880",
"livekit_health":true,
"node_ip":"203.0.113.1",
"proxy_path":"/livekit"
},
"client":{
"remote_addr":"192.168.1.100",
"is_private_network":true
}
}
```
---
## Client Auto-Update
### GET /api/v1/client-update/{target}/{current_version}
Tauri-compatible update endpoint. The desktop client checks this to see if a newer version is available.
WebSocket upgrade endpoint. Authentication is performed in-band (first message must be an `auth` frame with the session token). See [protocol.md](protocol.md) for the full WebSocket message protocol.