{"openapi":"3.0.3","info":{"title":"StreamLink API","version":"0.1.0","description":"Live screen broadcasting + chat. Bearer JWT for mobile/SDK; HttpOnly cookies + CSRF for the web app."},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"cookieAuth":{"type":"apiKey","in":"cookie","name":"sl_at"}},"schemas":{}},"paths":{"/health":{"get":{"summary":"Liveness probe","tags":["system"],"responses":{"200":{"description":"Default Response"}}}},"/health/deep":{"get":{"summary":"Readiness probe (DB + Redis + LiveKit + backup)","tags":["system"],"responses":{"200":{"description":"Default Response"}}}},"/api/version":{"get":{"responses":{"200":{"description":"Default Response"}}}},"/metrics":{"get":{"responses":{"200":{"description":"Default Response"}}}},"/api/auth/signup":{"post":{"summary":"Create an account","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254},"password":{"type":"string","minLength":8,"maxLength":256},"displayName":{"type":"string","minLength":1,"maxLength":60}},"required":["email","password","displayName"],"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response"}}}},"/api/auth/signin":{"post":{"summary":"Sign in with email + password","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254},"password":{"type":"string","minLength":1,"maxLength":256}},"required":["email","password"],"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response"}}}},"/api/auth/refresh":{"post":{"summary":"Rotate the access token using a refresh token","tags":["auth"],"description":"Refresh token is read from the sl_rt cookie if present, else from the request body (SDK / mobile callers).","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","minLength":20,"maxLength":512}},"additionalProperties":false}}}},"responses":{"200":{"description":"Default Response"}}}},"/api/auth/signout":{"post":{"summary":"Sign out (revokes the refresh token)","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","minLength":20,"maxLength":512}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/auth/me":{"get":{"summary":"Get the current user","tags":["auth"],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}},"patch":{"summary":"Update profile (display name — 14-day cooldown)","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"type":"string","minLength":1,"maxLength":60}},"additionalProperties":false}}}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/auth/me/email":{"post":{"summary":"Change email (14-day cooldown, re-verification required)","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254}},"required":["email"],"additionalProperties":false}}},"required":true},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/auth/verify/request":{"post":{"summary":"Send an email-verification OTP to the current user","tags":["auth"],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/auth/verify/confirm":{"post":{"summary":"Confirm the email-verification OTP","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","pattern":"^\\d{6}$"}},"required":["code"],"additionalProperties":false}}},"required":true},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/auth/password-reset/request":{"post":{"summary":"Request a password-reset OTP by email","tags":["auth"],"description":"Always returns 200 — never leaks whether the email exists.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254}},"required":["email"],"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response"}}}},"/api/auth/password-reset/confirm":{"post":{"summary":"Set a new password using the reset OTP","tags":["auth"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254},"code":{"type":"string","pattern":"^\\d{6}$"},"newPassword":{"type":"string","minLength":8,"maxLength":256}},"required":["email","code","newPassword"],"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/":{"post":{"summary":"Create a broadcast room","tags":["rooms"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["public","private","invite_only"]},"password":{"type":"string","minLength":4,"maxLength":64,"nullable":true},"chatEnabled":{"type":"boolean","default":true},"ttlMinutes":{"type":"integer","minimum":5,"maximum":480,"default":480},"record":{"type":"boolean","default":false}},"required":["name","type"],"additionalProperties":false}}},"required":true},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}},"get":{"summary":"List rooms owned by the current user","tags":["rooms"],"parameters":[{"schema":{"type":"string","enum":["lobby","live","offline","ended"]},"in":"query","name":"status","required":false},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"in":"query","name":"limit","required":false}],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/discover":{"get":{"summary":"List currently-live public rooms (no auth)","tags":["rooms"],"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/{roomId}":{"get":{"summary":"Join a room — returns LiveKit + chat-WS credentials","tags":["rooms"],"description":"Auth optional. Private rooms: send X-Room-Password. Invite-only: send X-Invite-Token. Anonymous viewers get a stable identity via the sl_anon cookie (or ?anon fallback).","parameters":[{"schema":{"type":"string"},"in":"query","name":"password","required":false},{"schema":{"type":"string"},"in":"query","name":"inviteToken","required":false},{"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{10}$"},"in":"path","name":"roomId","required":true}],"responses":{"200":{"description":"Default Response"}}},"patch":{"summary":"Update a room you own (name / chat / lock)","tags":["rooms"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"chatEnabled":{"type":"boolean"},"locked":{"type":"boolean"}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{10}$"},"in":"path","name":"roomId","required":true}],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/{roomId}/end":{"post":{"summary":"End a room you own","tags":["rooms"],"parameters":[{"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{10}$"},"in":"path","name":"roomId","required":true}],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/{roomId}/host-token":{"post":{"summary":"Re-mint LiveKit + chat-WS tokens for a room you own","tags":["rooms"],"description":"Used by the web broadcaster to resume after a refresh.","parameters":[{"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{10}$"},"in":"path","name":"roomId","required":true}],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/stats":{"get":{"summary":"Aggregate broadcast stats for the current host","tags":["rooms"],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/rooms/{roomId}/invites":{"post":{"summary":"Issue a signed invite token for an invite-only room","tags":["rooms"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"ttlHours":{"type":"integer","minimum":1,"maximum":168,"default":24},"maxUses":{"type":"integer","minimum":1,"maximum":10000,"nullable":true,"default":null}},"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{10}$"},"in":"path","name":"roomId","required":true}],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/me":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/stats":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}/suspend":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}/unsuspend":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}/role":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}/edit":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}/verify":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/{id}/send-password-reset":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users.csv":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/users/bulk":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/rooms":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/recordings":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/rooms/{id}":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"patch":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}},"delete":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/rooms/{id}/end":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/audit":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/reports":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/reports/{id}/resolve":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/settings":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}},"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/system/backup":{"post":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/analytics":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/admin/system":{"get":{"tags":["admin"],"security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/reports/":{"post":{"summary":"Submit a report about a live stream","tags":["reports"],"description":"Anyone (signed-in or anonymous) can flag a room. Rate-limited to 5 per hour per IP.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"roomId":{"type":"string","minLength":1,"maxLength":40},"reason":{"type":"string","enum":["inappropriate","spam","harassment","copyright","self-harm","other"]},"details":{"type":"string","maxLength":1000}},"required":["roomId","reason"],"additionalProperties":false}}},"required":true},"responses":{"200":{"description":"Default Response"}}}},"/api/turn-credentials":{"get":{"summary":"Get ephemeral TURN credentials","tags":["signaling"],"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/api/livekit/webhook":{"post":{"summary":"LiveKit server webhook (signed; not for client use)","tags":["signaling"],"responses":{"200":{"description":"Default Response"}}}},"/ws":{"get":{"responses":{"200":{"description":"Default Response"}}}}},"servers":[{"url":"https://saviourtool.com"}],"tags":[{"name":"auth","description":"Sign up / in / out, tokens, email + password"},{"name":"rooms","description":"Create / join / end broadcasts, discovery"},{"name":"reports","description":"Viewer-submitted stream reports"},{"name":"admin","description":"Admin + moderator surfaces (role-gated)"},{"name":"signaling","description":"LiveKit webhook + TURN credentials"},{"name":"system","description":"Health + readiness probes"}]}