{"openapi":"3.1.0","info":{"title":"The Last Radio API","version":"1.0.0","description":"A collaborative web radio. Listen to stations, see what's playing, add songs and downvote, as yourself.\n\nAuthenticate with an **API key** (create one under *Connect your AI*) or an **OAuth 2.1** access token, sent as `Authorization: Bearer …`. AI assistants can also use the MCP server at `/mcp` (Streamable HTTP).\n\nLists are paged: `page` and `pageSize` (default 20, max 100). Errors are `{ \"error\": \"…\" }`, written to be shown to people. Rate limits answer 429 with `Retry-After`."},"servers":[{"url":"https://thelastradio.slic.it"}],"tags":[{"name":"Stations"},{"name":"Songs"},{"name":"Accounts"},{"name":"Profile"},{"name":"Private stations"},{"name":"Feedback"},{"name":"API access"},{"name":"OAuth"},{"name":"Admin"},{"name":"Meta"}],"paths":{"/api/health":{"get":{"summary":"Liveness check","description":"No sign-in needed.","tags":["Meta"],"security":[],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/legal":{"get":{"summary":"Privacy notice version and data controller","description":"No sign-in needed.","tags":["Meta"],"security":[],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"policyVersion":{"type":"string"},"controller":{"anyOf":[{"type":"string"},{"type":"null"}]},"contact":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["policyVersion","controller","contact"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/openapi.json":{"get":{"summary":"This document","description":"No sign-in needed.","tags":["Meta"],"security":[],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/register":{"post":{"summary":"Create an account (the first one becomes admin)","description":"Sets the session cookie. Sends an email confirmation link when mail is configured.\n\nNo sign-in needed.","tags":["Accounts"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":8,"maxLength":200},"displayName":{"type":"string","minLength":2,"maxLength":40},"acceptPrivacy":{"type":"boolean","const":true}},"required":["email","password","displayName","acceptPrivacy"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/login":{"post":{"summary":"Sign in","description":"Sets the session cookie. 5 wrong passwords lock the account for 15 minutes.\n\nNo sign-in needed.","tags":["Accounts"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"password":{"type":"string","minLength":1,"maxLength":200}},"required":["email","password"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/logout":{"post":{"summary":"Sign out","description":"No sign-in needed.","tags":["Accounts"],"security":[],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/me":{"get":{"summary":"Who am I","description":"No sign-in needed.","tags":["Accounts"],"security":[],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"anyOf":[{"$ref":"#/components/schemas/User"},{"type":"null"}]}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/auth/verify-email":{"post":{"summary":"Confirm an email address with the link's token","description":"No sign-in needed.","tags":["Accounts"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios":{"get":{"summary":"List the stations you can see","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"radios":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Radio"},{"type":"object","properties":{"nowPlaying":{"anyOf":[{"$ref":"#/components/schemas/QueueItem"},{"type":"null"}]},"queueLength":{"type":"integer"},"listeners":{"type":"integer"}},"required":["nowPlaying","queueLength","listeners"]}]}}},"required":["radios"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a station","description":"Admins, website session only.","tags":["Stations"],"security":[{"session":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$"},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":500},"isActive":{"type":"boolean"},"isPrivate":{"type":"boolean"},"rateLimitCount":{"type":"integer","minimum":1,"maximum":1000},"rateLimitWindowSec":{"type":"integer","minimum":10,"maximum":604800},"maxTrackSec":{"type":"integer","minimum":30,"maximum":14400},"skipVotePercent":{"type":"integer","minimum":0,"maximum":100},"hoursEnabled":{"type":"boolean"},"hoursDays":{"minItems":1,"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"hoursStart":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"hoursEnd":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string"},"autofillBelowSec":{"type":"integer","minimum":0,"maximum":14400}},"required":["slug","name"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"radio":{"$ref":"#/components/schemas/Radio"}},"required":["radio"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}":{"get":{"summary":"A station: now playing, first page of the queue, your quota","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"radio":{"$ref":"#/components/schemas/Radio"},"nowPlaying":{"anyOf":[{"$ref":"#/components/schemas/QueueItem"},{"type":"null"}]},"skip":{"anyOf":[{"type":"object","properties":{"enabled":{"type":"boolean"},"votes":{"type":"integer"},"needed":{"type":"integer"},"voted":{"type":"boolean"},"canVote":{"type":"boolean"},"isOwner":{"type":"boolean"}},"required":["enabled","votes","needed","voted","canVote","isOwner"]},{"type":"null"}]},"queue":{"type":"array","items":{"$ref":"#/components/schemas/QueueItem"},"description":"First 20; page with /queue"},"queueTotal":{"type":"integer"},"queueDurationSec":{"type":"integer"},"queuedKeys":{"type":"array","items":{"type":"string"}},"quota":{"anyOf":[{"$ref":"#/components/schemas/Quota"},{"type":"null"}]},"listeners":{"type":"integer"},"serverTime":{"type":"string","format":"date-time"}},"required":["radio","nowPlaying","skip","queue","queueTotal","queueDurationSec","queuedKeys","quota","listeners","serverTime"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Edit a station","description":"Admins, website session only.","tags":["Stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":500},"isActive":{"type":"boolean"},"isPrivate":{"type":"boolean"},"rateLimitCount":{"type":"integer","minimum":1,"maximum":1000},"rateLimitWindowSec":{"type":"integer","minimum":10,"maximum":604800},"maxTrackSec":{"type":"integer","minimum":30,"maximum":14400},"skipVotePercent":{"type":"integer","minimum":0,"maximum":100},"hoursEnabled":{"type":"boolean"},"hoursDays":{"minItems":1,"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"hoursStart":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"hoursEnd":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string"},"autofillBelowSec":{"type":"integer","minimum":0,"maximum":14400}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"radio":{"$ref":"#/components/schemas/Radio"}},"required":["radio"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/export":{"get":{"summary":"Export a station: settings, access, songs, history and votes, as one file","description":"Downloaded as <slug>-<date>.lastradio.json. It names the people in the history, with their emails, so they can be matched on import: keep it safe.\n\nAdmins, website session only.","tags":["Stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"format":{"type":"string","const":"the-last-radio.station"},"version":{"type":"number","const":1},"exportedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"station":{"type":"object","properties":{"slug":{"type":"string","maxLength":40},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":500},"isActive":{"type":"boolean"},"isPrivate":{"type":"boolean"},"rateLimitCount":{"type":"integer","minimum":1,"maximum":1000},"rateLimitWindowSec":{"type":"integer","minimum":10,"maximum":604800},"maxTrackSec":{"type":"integer","minimum":30,"maximum":14400},"skipVotePercent":{"type":"integer","minimum":0,"maximum":100},"hoursEnabled":{"type":"boolean"},"hoursDays":{"minItems":1,"maxItems":7,"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"hoursStart":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"hoursEnd":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string","maxLength":64},"autofillBelowSec":{"type":"integer","minimum":0,"maximum":14400},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["slug","name","description","isActive","isPrivate","rateLimitCount","rateLimitWindowSec","maxTrackSec","skipVotePercent","hoursEnabled","hoursDays","hoursStart","hoursEnd","timezone","autofillBelowSec","createdAt"]},"access":{"type":"object","properties":{"domains":{"maxItems":100,"type":"array","items":{"type":"string","pattern":"^[a-z0-9.-]+\\.[a-z]{2,}$"}},"members":{"maxItems":10000,"type":"array","items":{"type":"string"}}},"required":["domains","members"]},"people":{"maxItems":10000,"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","maxLength":20},"email":{"anyOf":[{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"displayName":{"type":"string","minLength":1,"maxLength":80}},"required":["ref","email","displayName"]}},"songs":{"maxItems":100000,"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","maxLength":20},"sourceKey":{"type":"string","minLength":1,"maxLength":300},"sourceUrl":{"type":"string","maxLength":2000,"format":"uri"},"title":{"type":"string","maxLength":500},"artist":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}]},"durationSec":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"thumbnailUrl":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"checkedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"unavailableAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"unavailableReason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}]}},"required":["ref","sourceKey","sourceUrl","title","artist","durationSec","thumbnailUrl","checkedAt","unavailableAt","unavailableReason"]}},"plays":{"maxItems":500000,"type":"array","items":{"type":"object","properties":{"song":{"type":"string"},"by":{"type":["string","null"]},"isFill":{"type":"boolean"},"status":{"type":"string","enum":["queued","playing","played","skipped","removed","failed"]},"skipReason":{"anyOf":[{"type":"string","enum":["admin","owner","votes","interrupted"]},{"type":"null"}]},"error":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"startedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"endedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"downvotes":{"maxItems":10000,"type":"array","items":{"type":"object","properties":{"by":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["by","at"]}}},"required":["song","by","isFill","status","skipReason","error","createdAt","startedAt","endedAt","downvotes"]}},"upvotes":{"maxItems":500000,"type":"array","items":{"type":"object","properties":{"song":{"type":"string"},"by":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["song","by","at"]}}},"required":["format","version","exportedAt","station","access","people","songs","plays","upvotes"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/import":{"post":{"summary":"Recreate a station from an export file","description":"Always creates a new station (409 if the slug is taken). Songs already known here are reused; people are matched to accounts by email, others keep their name on a placeholder that can't sign in. Up to 50 MB.\n\nAdmins, website session only.","tags":["Stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"query","required":false,"description":"Slug for the new station (default: the file's)","schema":{"type":"string"}},{"name":"name","in":"query","required":false,"description":"Name for the new station (default: the file's)","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"format":{"type":"string","const":"the-last-radio.station"},"version":{"type":"number","const":1},"exportedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"station":{"type":"object","properties":{"slug":{"type":"string","maxLength":40},"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":"string","maxLength":500},"isActive":{"type":"boolean"},"isPrivate":{"type":"boolean"},"rateLimitCount":{"type":"integer","minimum":1,"maximum":1000},"rateLimitWindowSec":{"type":"integer","minimum":10,"maximum":604800},"maxTrackSec":{"type":"integer","minimum":30,"maximum":14400},"skipVotePercent":{"type":"integer","minimum":0,"maximum":100},"hoursEnabled":{"type":"boolean"},"hoursDays":{"minItems":1,"maxItems":7,"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"hoursStart":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"hoursEnd":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string","maxLength":64},"autofillBelowSec":{"type":"integer","minimum":0,"maximum":14400},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["slug","name","description","isActive","isPrivate","rateLimitCount","rateLimitWindowSec","maxTrackSec","skipVotePercent","hoursEnabled","hoursDays","hoursStart","hoursEnd","timezone","autofillBelowSec","createdAt"]},"access":{"type":"object","properties":{"domains":{"maxItems":100,"type":"array","items":{"type":"string","pattern":"^[a-z0-9.-]+\\.[a-z]{2,}$"}},"members":{"maxItems":10000,"type":"array","items":{"type":"string"}}},"required":["domains","members"]},"people":{"maxItems":10000,"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","maxLength":20},"email":{"anyOf":[{"type":"string","maxLength":254,"format":"email","pattern":"^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"displayName":{"type":"string","minLength":1,"maxLength":80}},"required":["ref","email","displayName"]}},"songs":{"maxItems":100000,"type":"array","items":{"type":"object","properties":{"ref":{"type":"string","maxLength":20},"sourceKey":{"type":"string","minLength":1,"maxLength":300},"sourceUrl":{"type":"string","maxLength":2000,"format":"uri"},"title":{"type":"string","maxLength":500},"artist":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}]},"durationSec":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"thumbnailUrl":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"checkedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"unavailableAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"unavailableReason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}]}},"required":["ref","sourceKey","sourceUrl","title","artist","durationSec","thumbnailUrl","checkedAt","unavailableAt","unavailableReason"]}},"plays":{"maxItems":500000,"type":"array","items":{"type":"object","properties":{"song":{"type":"string"},"by":{"type":["string","null"]},"isFill":{"type":"boolean"},"status":{"type":"string","enum":["queued","playing","played","skipped","removed","failed"]},"skipReason":{"anyOf":[{"type":"string","enum":["admin","owner","votes","interrupted"]},{"type":"null"}]},"error":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"startedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"endedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]},"downvotes":{"maxItems":10000,"type":"array","items":{"type":"object","properties":{"by":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["by","at"]}}},"required":["song","by","isFill","status","skipReason","error","createdAt","startedAt","endedAt","downvotes"]}},"upvotes":{"maxItems":500000,"type":"array","items":{"type":"object","properties":{"song":{"type":"string"},"by":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["song","by","at"]}}},"required":["format","version","exportedAt","station","access","people","songs","plays","upvotes"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"radio":{"$ref":"#/components/schemas/Radio"},"summary":{"type":"object","properties":{"songs":{"type":"integer"},"newSongs":{"type":"integer"},"plays":{"type":"integer"},"queued":{"type":"integer"},"people":{"type":"object","properties":{"matched":{"type":"integer"},"placeholders":{"type":"integer"}},"required":["matched","placeholders"]},"downvotes":{"type":"integer"},"upvotes":{"type":"integer"}},"required":["songs","newSongs","plays","queued","people","downvotes","upvotes"]}},"required":["radio","summary"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/queue":{"get":{"summary":"What's lined up, in play order","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page (max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/QueueItem"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["items","total","page","pageSize"]},{"type":"object","properties":{"totalDurationSec":{"type":"integer"}},"required":["totalDurationSec"]}]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Add a song (by link or known song id)","description":"429 when the per-station limit is reached; 409 if already in line; 410 if the song is no longer available.\n\nSigned in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri"}},"required":["url"]},{"type":"object","properties":{"trackId":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},"required":["trackId"]}]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"track":{"$ref":"#/components/schemas/Track"},"position":{"type":"integer"},"quota":{"$ref":"#/components/schemas/Quota"}},"required":["id","track","position","quota"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/history":{"get":{"summary":"What has aired, newest first","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page (max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/QueueItem"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["items","total","page","pageSize"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/songs":{"get":{"summary":"Every song the station aired, with its record","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page (max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"sort","in":"query","required":false,"description":"score = what Alfred ranks by","schema":{"enum":["played","score","downvoted","recent"],"default":"played"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SongRecord"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["items","total","page","pageSize"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/stats":{"get":{"summary":"Top players and totals","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"topPlayers":{"type":"array","items":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"}},"required":["id","displayName"]},"plays":{"type":"integer"},"listenedSec":{"type":"integer"}},"required":["user","plays","listenedSec"]}},"totals":{"type":"object","properties":{"plays":{"type":"integer"},"uniqueTracks":{"type":"integer"},"uniquePlayers":{"type":"integer"},"airtimeSec":{"type":"integer"},"downvotes":{"type":"integer"}},"required":["plays","uniqueTracks","uniquePlayers","airtimeSec","downvotes"]}},"required":["topPlayers","totals"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/stream":{"get":{"summary":"Is the stream live; where to play it (HLS)","description":"Anyone; with a token it needs `radio:read`. Private stations need access.","tags":["Stations"],"security":[{},{"session":[]},{"bearer":["radio:read"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"live":{"type":"boolean"},"readySince":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"listeners":{"type":"integer"},"hlsUrl":{"type":"string"}},"required":["live","readySince","listeners","hlsUrl"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/listen":{"post":{"summary":"Listener heartbeat (every ~20 s while playing)","description":"No sign-in needed.","tags":["Stations"],"security":[],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"listenerId":{"type":"string","minLength":8,"maxLength":64}},"required":["listenerId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"listeners":{"type":"integer"}},"required":["listeners"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/queue/{id}":{"delete":{"summary":"Remove a queued song (yours; admins: any)","description":"Signed in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/skip":{"post":{"summary":"Skip the song on air (yours; admins: any)","description":"Signed in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"skipped":{"type":"boolean"}},"required":["skipped"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/votes":{"post":{"summary":"Downvote (vote to skip) the song on air","description":"Only counts while you're listening to the station.\n\nSigned in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"itemId":{"type":"string"}},"required":["itemId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"skipped":{"type":"boolean"}},"required":["skipped"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/votes/{itemId}":{"delete":{"summary":"Take back your downvote","description":"Signed in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/upvotes":{"post":{"summary":"Upvote a song (3 a day): Alfred plays it more","description":"Signed in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"trackId":{"type":"string"}},"required":["trackId"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"already":{"type":"boolean"},"allowance":{"type":"object","properties":{"limit":{"type":"integer"},"remaining":{"type":"integer"},"nextAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["limit","remaining","nextAt"]}},"required":["already","allowance"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/upvotes/{trackId}":{"delete":{"summary":"Take back an upvote","description":"Signed in; tokens need `radio:write`. Acts as the caller, within their limits.","tags":["Songs"],"security":[{"session":[]},{"bearer":["radio:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"trackId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"allowance":{"type":"object"}},"required":["allowance"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/search":{"get":{"summary":"Search YouTube or SoundCloud","description":"Picking a result and posting its sourceUrl to /queue adds it instantly.\n\nAny signed-in caller (session, API key or OAuth token).","tags":["Songs"],"security":[{"session":[]},{"bearer":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":120}},{"name":"source","in":"query","required":false,"schema":{"enum":["youtube","soundcloud"],"default":"youtube"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Track"},{"type":"object","properties":{"videoId":{"type":"string"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}]},"source":{"type":"string"}},"required":["videoId","views","source"]}]}}},"required":["results"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/access":{"get":{"summary":"Who may hear a private station","description":"Admins, website session only.","tags":["Private stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"isPrivate":{"type":"boolean"},"members":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"email":{"type":"string"},"addedAt":{"type":"string","format":"date-time"}},"required":["id","displayName","email","addedAt"]}},"domains":{"type":"array","items":{"type":"string"}},"mailConfigured":{"type":"boolean"}},"required":["isPrivate","members","domains","mailConfigured"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/members":{"post":{"summary":"Add someone by email","description":"Admins, website session only.","tags":["Private stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"}},"required":["email"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/members/{userId}":{"delete":{"summary":"Remove someone","description":"Admins, website session only.","tags":["Private stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/domains":{"post":{"summary":"Allow a verified email domain","description":"Admins, website session only.","tags":["Private stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"}},"required":["domain"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"domain":{"type":"string"}},"required":["ok","domain"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/radios/{slug}/domains/{domain}":{"delete":{"summary":"Remove a domain","description":"Admins, website session only.","tags":["Private stations"],"security":[{"session":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"domain","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me":{"patch":{"summary":"Change your display name or theme","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"displayName":{"type":"string"},"theme":{"enum":["night","light","vintage"]}},"required":[]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete your account (anonymised in station history)","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"password":{"type":"string"}},"required":["password"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me/avatar":{"put":{"summary":"Upload a profile photo (≤ 5 MB; re-encoded to 256×256 WebP)","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove your photo","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/avatars/{userId}":{"get":{"summary":"Someone's profile photo","description":"No sign-in needed.","tags":["Profile"],"security":[],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"image/webp":{"schema":{"type":"string","format":"binary"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me/privacy-ack":{"post":{"summary":"Acknowledge the current privacy notice","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string","example":"2026-09-26.4"}},"required":["version"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}},"required":["user"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me/verify-email":{"post":{"summary":"Send a new email confirmation link","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/me/export":{"get":{"summary":"Download all your data (GDPR access and portability)","description":"Website session only (not API keys or OAuth tokens).","tags":["Profile"],"security":[{"session":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/feedback":{"post":{"summary":"Send feedback (3 a day)","description":"Any signed-in caller (session, API key or OAuth token).","tags":["Feedback"],"security":[{"session":[]},{"bearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"kind":{"default":"idea","type":"string","enum":["idea","bug","other"]},"body":{"type":"string","minLength":10,"maxLength":2000}},"required":["body"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"allowance":{"type":"object","properties":{"limit":{"type":"integer"},"remaining":{"type":"integer"},"nextAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["limit","remaining","nextAt"]}},"required":["id","allowance"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/feedback/mine":{"get":{"summary":"What you've sent and its status","description":"Any signed-in caller (session, API key or OAuth token).","tags":["Feedback"],"security":[{"session":[]},{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"allowance":{"type":"object"}},"required":["items","allowance"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/feedback":{"get":{"summary":"Feedback inbox","description":"Admins, website session only.","tags":["Feedback"],"security":[{"session":[]}],"parameters":[{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page (max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"view","in":"query","required":false,"schema":{"enum":["inbox","archived"],"default":"inbox"}},{"name":"sort","in":"query","required":false,"schema":{"enum":["new","top"],"default":"new"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["items","total","page","pageSize"]},{"type":"object","properties":{"unread":{"type":"integer"}},"required":["unread"]}]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/feedback/{id}":{"patch":{"summary":"Mark read / archive","description":"Admins, website session only.","tags":["Feedback"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"archived":{"type":"boolean"}},"required":[]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/feedback/{id}/vote":{"post":{"summary":"Vote on its priority","description":"Admins, website session only.","tags":["Feedback"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"enum":[1,-1,0]}},"required":["value"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"score":{"type":"integer"},"myVote":{"type":"integer"}},"required":["score","myVote"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/users":{"get":{"summary":"Everyone, with activity","description":"Admins, website session only.","tags":["Admin"],"security":[{"session":[]}],"parameters":[{"name":"page","in":"query","required":false,"description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","required":false,"description":"Items per page (max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/User"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["items","total","page","pageSize"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/users/{id}":{"patch":{"summary":"Change someone's role, or vouch for their email","description":"Admins, website session only.","tags":["Admin"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"enum":["admin","player"]},"emailVerified":{"type":"boolean"}},"required":[]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/access":{"get":{"summary":"Your API keys and connected apps","description":"Website session only (not API keys or OAuth tokens).","tags":["API access"],"security":[{"session":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object"}},"apps":{"type":"array","items":{"type":"object"}}},"required":["keys","apps"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/access/keys":{"post":{"summary":"Create an API key (shown once)","description":"Website session only (not API keys or OAuth tokens).","tags":["API access"],"security":[{"session":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"scopes":{"minItems":1,"type":"array","items":{"type":"string","enum":["radio:read","radio:write"]}}},"required":["name","scopes"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"key":{"type":"object"}},"required":["token","key"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/access/keys/{id}":{"delete":{"summary":"Revoke a key","description":"Website session only (not API keys or OAuth tokens).","tags":["API access"],"security":[{"session":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/access/apps/{clientId}":{"delete":{"summary":"Disconnect an app (revokes its tokens)","description":"Website session only (not API keys or OAuth tokens).","tags":["API access"],"security":[{"session":[]}],"parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/oauth/register":{"post":{"summary":"Dynamic client registration (RFC 7591)","description":"No sign-in needed.","tags":["OAuth"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"client_name":{"type":"string","maxLength":100},"redirect_uris":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","maxLength":2000}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"}},"required":["redirect_uris"]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/oauth/authorize":{"get":{"summary":"Consent page data","description":"Website session only (not API keys or OAuth tokens).","tags":["OAuth"],"security":[{"session":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Approve or deny (the consent page)","description":"Website session only (not API keys or OAuth tokens).","tags":["OAuth"],"security":[{"session":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"redirectTo":{"type":"string"}},"required":["redirectTo"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/oauth/token":{"post":{"summary":"Token endpoint: authorization_code (PKCE S256) or refresh_token","description":"No sign-in needed.","tags":["OAuth"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"grant_type":{"enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string"},"redirect_uri":{"type":"string"},"refresh_token":{"type":"string"}},"required":["grant_type","client_id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"access_token":{"type":"string"},"token_type":{"type":"string"},"expires_in":{"type":"integer"},"refresh_token":{"type":"string"},"scope":{"type":"string"}},"required":["access_token","token_type","expires_in","refresh_token","scope"]}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/oauth/revoke":{"post":{"summary":"Revoke a token (RFC 7009)","description":"No sign-in needed.","tags":["OAuth"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"4XX":{"description":"A problem with the request, explained","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"API key (lr_key_…) or OAuth access token (lr_at_…). Scopes: radio:read (See stations, what's playing, queues, song stats and search); radio:write (Add songs, downvote and skip your own songs)."},"session":{"type":"apiKey","in":"cookie","name":"lr_session","description":"The website's session cookie."},"oauth":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://thelastradio.slic.it/oauth/authorize","tokenUrl":"https://thelastradio.slic.it/api/oauth/token","scopes":{"radio:read":"See stations, what's playing, queues, song stats and search","radio:write":"Add songs, downvote and skip your own songs"}}}}},"schemas":{"Track":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"artist":{"anyOf":[{"type":"string"},{"type":"null"}]},"durationSec":{"anyOf":[{"type":"integer"},{"type":"null"}]},"thumbnailUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"sourceUrl":{"type":"string"},"sourceKey":{"type":"string"},"unavailable":{"type":"boolean"}},"required":["id","title","sourceUrl","sourceKey"]},"QueueItem":{"type":"object","properties":{"id":{"type":"string"},"status":{"enum":["queued","playing","played","skipped","removed","failed"]},"skipReason":{"anyOf":[{"enum":["admin","owner","votes","interrupted"]},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"startedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"endedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"isFill":{"type":"boolean","description":"Queued by Alfred, the fill-in DJ (then pushedBy is null)"},"track":{"$ref":"#/components/schemas/Track"},"pushedBy":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"}},"required":["id","displayName"]},{"type":"null"}]}},"required":["id","status","track","isFill"]},"Radio":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"isActive":{"type":"boolean"},"isPrivate":{"type":"boolean"},"rateLimitCount":{"type":"integer"},"rateLimitWindowSec":{"type":"integer"},"maxTrackSec":{"type":"integer"},"skipVotePercent":{"type":"integer"},"hoursEnabled":{"type":"boolean"},"hoursDays":{"type":"array","items":{"type":"integer"}},"hoursStart":{"type":"string"},"hoursEnd":{"type":"string"},"timezone":{"type":"string"},"autofillBelowSec":{"type":"integer"},"hours":{"$ref":"#/components/schemas/Hours"}},"required":["id","slug","name"]},"Hours":{"type":"object","properties":{"open":{"type":"boolean"},"next":{"anyOf":[{"type":"object","properties":{"inDays":{"type":"integer"},"weekday":{"type":"integer"},"time":{"type":"string"}},"required":["inDays","weekday","time"]},{"type":"null"}]},"closesAt":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["open","next","closesAt"]},"Quota":{"type":"object","properties":{"unlimited":{"type":"boolean"},"limit":{"type":"integer"},"windowSec":{"type":"integer"},"used":{"type":"integer"},"remaining":{"type":"integer"},"nextSlotAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]}},"required":["unlimited","limit","windowSec","used","remaining","nextSlotAt"]},"SongRecord":{"type":"object","properties":{"track":{"$ref":"#/components/schemas/Track"},"plays":{"type":"integer"},"playsByPeople":{"type":"integer"},"playsByAlfred":{"type":"integer"},"airings":{"type":"integer"},"adders":{"type":"integer"},"downvotes":{"type":"integer"},"upvotes":{"type":"integer"},"skips":{"type":"integer"},"lastPlayedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"lastOutcome":{"anyOf":[{"type":"string"},{"type":"null"}]},"score":{"type":"number"},"alfredOk":{"type":"boolean"},"unavailable":{"type":"boolean"}},"required":["track","plays","playsByPeople","playsByAlfred","airings","adders","downvotes","upvotes","skips","lastPlayedAt","lastOutcome","score","alfredOk","unavailable"]},"User":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"displayName":{"type":"string"},"role":{"enum":["admin","player"]},"theme":{"enum":["night","light","vintage"]},"avatarUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"privacyAckRequired":{"type":"boolean"},"emailVerified":{"type":"boolean"}},"required":["id","email","displayName","role","theme","avatarUrl","privacyAckRequired","emailVerified"]},"Error":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, written for people"}},"required":["error"]}}}}