- Updated README.md to reflect changes in load testing commands and added usage examples for Bash scripts. - Converted PowerShell load testing scripts to Bash equivalents for better cross-platform compatibility. - Added new Bash scripts: run-loadtests.sh, summarize-artillery.sh, and summarize-k6.sh to mirror existing PowerShell functionality. - Updated admin-api.js and ws-chat.yml for consistency and improved performance metrics reporting. - Enhanced error handling and output formatting in summary scripts for better readability.
8.8 KiB
Linear Void API Reference
Base URLs: Public/admin app https://linearvoid.tv · WebSocket wss://linearvoid.tv/ws · Local fallback http://localhost / ws://localhost/ws
Authentication
Admin REST endpoints use a cookie session:
- Cookie:
linearvoid_admin_session—HttpOnly,SameSite=Strict,Securewhen request is HTTPS (orX-Forwarded-Proto=https) - Session TTL:
ADMIN_SESSION_TTL_HOURS(default 12h)
Chat WebSocket API
Connect: GET /ws?username={name}&room={room} — client/server exchange JSON messages.
| Message (client→server) | Field | Notes |
|---|---|---|
type |
public for room chat, private for DM routing; system for moderation/events |
|
room |
main; private DMs use dm:userA-userB (usernames sorted) |
|
username |
sender | |
text |
message body |
Server→client envelope adds "timestamp" (ISO-8601, e.g. 2026-08-04T12:34:56Z).
{ "room": "main", "username": "viewer123", "text": "hello world", "type": "public" }
Admin Session API
| Method & Path | Auth | Description |
|---|---|---|
POST /api/admin/login |
none | Authenticate |
POST /api/admin/logout |
optional (clears cookie either way) | Logout |
GET /api/admin/session |
cookie required | Session check; 401 if invalid |
Login request:
{ "username": "admin", "password": "your-password" }
Login/logout/session success response (session returns 401 on failure):
{ "authenticated": true, "username": "admin" }
Logout response: { "authenticated": false }
Admin Data API (auth required)
| Method & Path | Returns |
|---|---|
GET /api/admin/overview |
Aggregate counters + runtime/system snapshot |
GET /api/admin/playlist |
Playlist |
PUT /api/admin/playlist |
Set playlist (body: { "content": "file '/app/media/video1.mp4'\nfile '/app/media/video2.mp4'\n" }) |
GET /api/admin/schedule |
Parsed playlist entries + matched media metadata/runtime |
GET /api/admin/moderation/logs?limit=50 |
Moderation logs |
POST /api/admin/moderation/actions |
Perform moderation action |
GET /api/admin/analytics |
Totals (messages, moderation actions, rooms) + top room summary |
GET /api/admin/system |
Health snapshot (chat/playout/redis/database) + config metadata |
GET /api/admin/users |
Presence users (room + username) when Redis available |
Moderation action request:
{ "action": "mute", "target": "username-or-id", "moderator": "admin", "reason": "spam", "room": "main" }
Media Library API
| Method & Path | Description |
|---|---|
GET /api/admin/media |
List media |
POST /api/admin/media |
Multipart upload (legacy), form field name file |
GET /api/admin/media/{id} |
Get item |
PUT /api/admin/media/{id} |
Update item |
DELETE /api/admin/media/{id} |
Delete item |
PUT media body:
{ "status": "ready", "title": "My Clip", "tags": "music,live" }
Resumable Upload API
Max file size: 2GB · Default chunk size: 8MB
| Method & Path | Description |
|---|---|
POST /api/admin/media/uploads |
Initialize or resume session |
GET /api/admin/media/uploads/{uploadId} |
Check upload status |
PUT /api/admin/media/uploads/{uploadId}/chunk?index={chunkIndex} |
Upload chunk (Content-Type: application/octet-stream) |
POST /api/admin/media/uploads/{uploadId}/complete |
Complete upload → returns media listing (files + generatedAt) |
Initialize request:
{
"uploadId": "optional-existing-id",
"fileName": "clip.mp4",
"fileSize": 123456789,
"mimeType": "video/mp4",
"chunkSize": 8388608,
"totalChunks": 15
}
Status envelope (returned by init, status check, and chunk upload):
{
"uploadId": "hex-token",
"fileName": "clip.mp4",
"fileSize": 123456789,
"chunkSize": 8388608,
"totalChunks": 15,
"uploadedChunks": 3,
"progress": 20
}
Metrics & Health
GET /metrics(chat) — Prometheus: connected clients, stored messages, moderation actions, Redis indicators, active admin sessions, build infoGET /health— chat service, playout service, and nginx reverse proxy each expose it
HTTP Status Conventions
| Code | Meaning | When |
|---|---|---|
| 200 | OK | Successful GET, PUT, action |
| 201 | Created | Successful POST (upload init, override created) |
| 204 | No Content | Successful DELETE, CORS preflight |
| 400 | Bad Request | Malformed JSON, missing required fields, invalid input |
| 401 | Unauthorized | Missing/expired admin session |
| 403 | Forbidden | Valid session, insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | Wrong method for endpoint |
| 409 | Conflict | Schedule override overlap, upload ID mismatch |
| 413 | Payload Too Large | Upload exceeds 2 GB limit |
| 422 | Unprocessable | File fails normalization check (codec, fps, resolution) |
| 429 | Too Many Requests | Rate limit exceeded (chat, WS, login) |
| 500 | Internal Server Error | Unexpected server failure |
| 503 | Service Unavailable | Redis unavailable for user actions, playout health degraded |
Rate Limiting
Per-IP (from X-Forwarded-For or RemoteAddr) unless noted.
| Resource | Limit | Window | Response |
|---|---|---|---|
| Chat messages | CHAT_RATE_LIMIT_MAX (default 10) |
CHAT_RATE_LIMIT_WINDOW_S (default 10s) |
429 + {"type":"system","text":"Rate limit exceeded"} |
| WebSocket connections | 5 | 60s | 429 + "too many connections from this IP" |
| Admin login | 5 | 5min | 429 + "too many login attempts, try again later" |
Rate limit headers (applicable responses): X-RateLimit-Limit (max requests/window), Retry-After (seconds until reset). 413 = payload too large; 500 = internal server error.