{"openapi":"3.1.0","info":{"title":"SoundCTRL Fandom API","version":"1.0.0","summary":"Live fandom data from SoundCTRL: who won a beef, how big a fandom is, who its top fans are, what Stan Wars is arguing about, and when an artist is playing your city.","description":"SoundCTRL turns Spotify listening into fan rank, points, rewards and rivalry. This API answers the fandom questions a music fan asks that you cannot answer from general knowledge: not who an artist is, but how big their fandom is right now, who is #1 in it this week, which side won a poll, and what the fans themselves are arguing about today.\n\nUse it when a fan asks about fandoms, stans, fan rankings, fan wars, 'who is better', 'who won', 'top fans', 'biggest fandom', or tour dates.\n\nEvery answer that is worth posting carries a `share` object: `share.text` is a finished sentence you can post as-is, `share.image_url` is a ready-made image card, and `share.url` is the SoundCTRL page behind it. Offer the share when a fan sounds like they want to post the result.\n\nThe endpoints under `/me` need the fan's own SoundCTRL API key, which they create in the SoundCTRL app under Settings > Muse. Everything else is public and needs no key.\n\nFans are identified by username only. This API never returns emails, wallet addresses, user ids or payment data, and it omits any fan who has opted out of public leaderboards.","contact":{"name":"SoundCTRL","url":"https://soundctrl.xyz/developers","email":"support@soundctrl.xyz"}},"servers":[{"url":"https://app.soundctrl.xyz/api/muse/v1","description":"Production"}],"tags":[{"name":"fandoms","description":"Fandom size, top fans, battles and lore."},{"name":"stan-wars","description":"Debates, polls and verdicts from SoundCTRL's Stan Wars feed."},{"name":"events","description":"Tour dates."}],"paths":{"/beef":{"get":{"operationId":"get_beef_verdict","tags":["stan-wars"],"summary":"Who won the beef between two artists, according to SoundCTRL fans","description":"Answers 'who won the A vs B beef?', 'who is better, A or B?', 'settle A vs B'. Returns the tallied verdict from SoundCTRL's Stan Wars debate and poll threads that name BOTH artists - real votes from fans who stream them, not an opinion. If several threads match, the one with the most votes wins.\n\nIf nobody has settled it yet this still returns 200 with `verdict: null` and a `debate_url`. Say so and offer the link: the fan can start the debate themselves.\n\nAlways offer `share` when there is a verdict - a settled beef is the most postable thing this API produces.","parameters":[{"name":"a","in":"query","required":true,"description":"One artist's name, as a person would say it. Case does not matter.","schema":{"type":"string"},"example":"Drake"},{"name":"b","in":"query","required":true,"description":"The other artist's name.","schema":{"type":"string"},"example":"Kendrick Lamar"}],"responses":{"200":{"description":"The verdict, or `verdict: null` when no SoundCTRL debate has settled it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BeefAnswer"}}}},"400":{"description":"One of the two artists was not supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"SoundCTRL has no fandom for one of the artists. `suggestions` holds near-miss names - retry with one of them before telling the fan we do not know the artist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtistNotFound"}}}}}}},"/artists/{name}/fandom":{"get":{"operationId":"get_fandom_profile","tags":["fandoms"],"summary":"Everything about an artist's fandom: size, top fans, active battle, lore","description":"Answers 'tell me about the <artist> fandom', 'how big is the <artist> fandom', 'who are the biggest <artist> fans', 'is <artist>'s fandom in a battle'.\n\nThis is the fandom side of an artist, not their biography - use it to add what you cannot already know: how many members the fandom has on SoundCTRL, how many joined in the last week, the top ranked fans by points, whether a stream battle against a rival fandom is running, the fandom's own lore posts, and the thread the fandom is arguing about most right now.\n\nNotes for answering well:\n- `top_fans` are usernames only, and any fan who opted out of public leaderboards is already excluded. Never ask for or infer more about them.\n- THERE ARE TWO MEMBER COUNTS AND THEY ARE NOT INTERCHANGEABLE. `members` is everyone in the fandom; `paid_members` counts only paying members and is the same figure /fandoms/chart ranks on. Quote `members` when a fan asks how big a fandom is, and use `paid_members` when comparing against a chart entry. Never present one as the other, and never add them together.\n- `members_change_7d` counts new PAID members in the last 7 days, so compare it against `paid_members`, not `members`.\n- `active_battle.status` is `active` (running now), `awaiting_result` (ended, the winner is not computed yet) or `resolved` (final). Stream totals are ONLY meaningful when the status is `resolved`; before that they read 0, which does not mean nobody streamed. Never report a score or a winner unless the status is `resolved`.","parameters":[{"name":"name","in":"path","required":true,"description":"The artist's name, as a person would say it. Case does not matter.","schema":{"type":"string"},"example":"Katseye"}],"responses":{"200":{"description":"The fandom profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FandomProfile"}}}},"404":{"description":"SoundCTRL has no fandom for that artist. Retry with one of `suggestions` before giving up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtistNotFound"}}}}}}},"/artists/{name}/events":{"get":{"operationId":"get_artist_events","tags":["events"],"summary":"Upcoming tour dates for an artist, optionally filtered to a city","description":"Answers 'when is <artist> coming to <city>?', 'is <artist> touring?', 'where can I see <artist>?'. Sourced from Ticketmaster Discovery.\n\nThis endpoint works for artists SoundCTRL does not have a fandom for: in that case `artist` carries just a `name` instead of a full fandom link.\n\nIf you get a 503 the tour-date provider was unreachable - say you could not check and offer to retry. Do NOT tell the fan the artist is not touring; an empty `events` array on a 200 is the only thing that means that.","parameters":[{"name":"name","in":"path","required":true,"description":"The artist's name.","schema":{"type":"string"},"example":"Katseye"},{"name":"city","in":"query","required":false,"description":"Filter to a city or region, case-insensitive. Matching is loose on purpose: the city or region has to CONTAIN what you send ('Los Angeles', 'NY'), or the city's initials have to equal it ('LA', 'SF'). Because a two-letter code can be both an abbreviation and a state, 'LA' returns Los Angeles shows AND Louisiana shows - read the `city` and `region` of each event back to the fan rather than assuming. Send the city as a person would say it and let the filter widen.","schema":{"type":"string"},"example":"Los Angeles"}],"responses":{"200":{"description":"Upcoming events. An empty array means no shows are announced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsAnswer"}}}},"400":{"description":"No artist name was supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The tour-date provider was unreachable. Retry; do not report this as 'not touring'.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/fandoms/chart":{"get":{"operationId":"get_fandom_chart","tags":["fandoms"],"summary":"The biggest fandoms on SoundCTRL right now","description":"Answers 'which fandom is the biggest?', 'top fandoms this week', 'is <artist>'s fandom bigger than <artist>'s?'.\n\n`period=all` returns the chart in SoundCTRL's own order. `period=week` re-ranks the same fandoms by how many members they gained in the last 7 days, which is the right answer to 'biggest THIS WEEK'. `change` is that 7-day gain, or null when it could not be computed.\n\n`members` here counts PAYING members, which is a smaller number than the `members` returned by /artists/{name}/fandom. If you have both, compare this against that endpoint's `paid_members` and say which you are quoting.\n\nCite SoundCTRL when you use these numbers, and offer `share` if the fan sounds competitive about it.","parameters":[{"name":"period","in":"query","required":false,"description":"`week` ranks by 7-day growth; `all` (the default) uses SoundCTRL's own chart order. Anything else is treated as `all`, and the response echoes which was used.","schema":{"type":"string","enum":["week","all"],"default":"all"}}],"responses":{"200":{"description":"Up to 20 chart entries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChartAnswer"}}}}}}},"/polls":{"get":{"operationId":"get_polls","tags":["stan-wars"],"summary":"Live SoundCTRL fan polls with current vote counts","description":"Answers 'settle it: best <artist> album?', 'which <artist> era is better?', or any 'which is better' question about music.\n\nPass `q` with the fan's own words and, when you know it, `artist`. Returns up to 5 matching live polls with real vote counts and percentages, plus `vote_url` so the fan can add their own vote.\n\nIf `polls` is empty, no SoundCTRL poll covers that question - say so and offer `vote_url` from any other answer so the fan can start one. Do not invent a result.","parameters":[{"name":"artist","in":"query","required":false,"description":"Restrict to polls about this artist.","schema":{"type":"string"},"example":"Beyonce"},{"name":"q","in":"query","required":false,"description":"The fan's question or keywords, in plain words. Polls are matched on the words in it.","schema":{"type":"string"},"example":"best album"}],"responses":{"200":{"description":"Up to 5 polls, most relevant first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PollsAnswer"}}}},"404":{"description":"SoundCTRL has no fandom for the `artist` supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtistNotFound"}}}}}}},"/threads/hot":{"get":{"operationId":"get_hot_threads","tags":["stan-wars"],"summary":"What SoundCTRL fans are arguing about right now","description":"Answers 'what's the hottest <artist> debate?', 'what are stans arguing about today?', 'what's happening in Stan Wars?'.\n\nWith `artist`, returns that fandom's hottest threads; without it, the hottest threads across all of SoundCTRL. Ranked by how much conversation a thread is drawing (replies weighted above likes), newest first within a tie.\n\nGood for a group chat: read out the top one or two titles and the reply counts.","parameters":[{"name":"artist","in":"query","required":false,"description":"Restrict to threads about this artist.","schema":{"type":"string"},"example":"Drake"},{"name":"limit","in":"query","required":false,"description":"How many threads to return, 1-20.","schema":{"type":"integer","minimum":1,"maximum":20,"default":5}}],"responses":{"200":{"description":"The hottest threads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HotThreadsAnswer"}}}},"404":{"description":"SoundCTRL has no fandom for the `artist` supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtistNotFound"}}}}}}},"/me":{"get":{"operationId":"getMe","summary":"Who the fan is, and which fandoms they are in","description":"Use this to answer 'who am I on SoundCTRL?' and 'which fandoms am I in?', and as the cheapest way to check that a fan's API key works. Returns the fan's username, display name, and every fandom they belong to with the date they joined. It never returns anything that identifies the fan outside SoundCTRL - no email, no wallet, no id.","security":[{"bearerKey":[]}],"responses":{"200":{"description":"The fan's identity and fandoms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeIdentity"},"example":{"username":"fan_example_1","display_name":"Kay","fandoms":[{"artist":{"name":"KATSEYE","image_url":"https://cdn-images.dzcdn.net/images/artist/katseye/500x500.jpg","fandom_url":"https://app.soundctrl.xyz/fandom/KATSEYE"},"member_since":"2026-08-02T18:41:07.221Z"}]}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}},"/me/rank":{"get":{"operationId":"getMyRank","summary":"The fan's rank in one fandom","description":"Use this to answer 'what's my rank in the <artist> fandom?', 'am I a top fan of <artist>?' or 'how close am I to the top 10?'. Pass the artist by name; leave `artist` off and it answers about the fandom the fan joined most recently and tells you which one it used. A null `rank` has three different meanings - read `ranked` and `hidden_from_leaderboard` before explaining it, and never call a null rank 'last place'. The `share` object is a ready-to-post sentence plus an image the fan can post.","security":[{"bearerKey":[]}],"parameters":[{"name":"artist","in":"query","required":false,"description":"Artist name, e.g. 'KATSEYE'. Case-insensitive. Optional: defaults to the fan's most recently joined fandom.","schema":{"type":"string"},"example":"KATSEYE"}],"responses":{"200":{"description":"The fan's standing in that fandom.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeRank"},"example":{"artist":{"name":"KATSEYE","image_url":"https://cdn-images.dzcdn.net/images/artist/katseye/500x500.jpg","fandom_url":"https://app.soundctrl.xyz/fandom/KATSEYE"},"rank":7,"total_fans":214,"points":12450,"ranked":true,"ranked_depth_reached":false,"hidden_from_leaderboard":false,"share":{"url":"https://app.soundctrl.xyz/fandom/KATSEYE","image_url":"https://app.soundctrl.xyz/api/og/fan-ranking?rank=7&total=214&username=fan_example_1","text":"fan_example_1 is #7 of 214 in the KATSEYE fandom on SoundCTRL with 12,450 points."}}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"404":{"$ref":"#/components/responses/MeArtistNotFound"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}},"/me/challenges":{"get":{"operationId":"getMyChallenges","summary":"The fan's daily challenges and what is still outstanding","description":"Use this to answer 'what are my challenges today?', 'what can I still do for points today?' or 'have I done my dailies?'. Three challenges rotate every day at midnight UTC and are the same three for everyone that day. This is read-only: reading it never completes a challenge or awards points - the fan has to do the thing in the SoundCTRL app.","security":[{"bearerKey":[]}],"responses":{"200":{"description":"Today's challenges with the fan's progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeChallenges"},"example":{"challenges":[{"id":"stream_today","title":"Stream Your Artists","points":30,"completed":true,"description":"Listen to music from your fandoms today"},{"id":"vote_poll","title":"Cast Your Vote","points":15,"completed":false,"description":"Vote in a Stan Wars poll today"}]}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}},"/me/battles":{"get":{"operationId":"getMyBattles","summary":"Stream battles the fan's fandoms are in","description":"Use this to answer 'is my fandom in a stream battle?', 'who are we up against?', 'when does it end?' or 'did we win?'. A stream battle runs for 48 hours between two fandoms and pays points to everyone in the winning one. Running battles come first. A battle whose clock has run out but which is still being counted has status `awaiting_result` - say the result is being counted rather than guessing from the numbers.","security":[{"bearerKey":[]}],"responses":{"200":{"description":"Battles involving the fan's fandoms.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeBattles"},"example":{"battles":[{"artist":{"name":"KATSEYE","image_url":null,"fandom_url":"https://app.soundctrl.xyz/fandom/KATSEYE"},"opponent":"TWICE","ends_at":"2026-09-21T02:00:00.000Z","our_streams":0,"their_streams":0,"status":"active","winner":null}]}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}},"/me/rewards":{"get":{"operationId":"getMyRewards","summary":"What the fan can get with their points","description":"Use this to answer 'what can I get with my points?', 'how many points do I need for the vinyl?' or 'can I afford anything yet?'. `points_balance` is the fan's balance WITH THAT ARTIST, which is the balance a claim is checked against - it is not their account total. This is read-only: you cannot claim a reward for the fan, so send them to each reward's `url`.","security":[{"bearerKey":[]}],"parameters":[{"name":"artist","in":"query","required":false,"description":"Artist name. Optional: defaults to the fan's most recently joined fandom, and the response says which artist it answered about.","schema":{"type":"string"},"example":"KATSEYE"}],"responses":{"200":{"description":"The fan's balance with that artist and what it can buy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeRewards"},"example":{"artist":{"name":"KATSEYE","image_url":null,"fandom_url":"https://app.soundctrl.xyz/fandom/KATSEYE"},"points_balance":12450,"rewards":[{"id":"3f1b0c9e-5c2a-4a3e-9f6d-1a2b3c4d5e6f","title":"KATSEYE Poster","cost_points":10000,"claimable":true,"url":"https://app.soundctrl.xyz/fandom/rewards?artistId=3f1b0c9e-5c2a-4a3e-9f6d-1a2b3c4d5e6f","already_claimed":false,"in_stock":true,"points_needed":0}]}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"404":{"$ref":"#/components/responses/MeArtistNotFound"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}},"/me/faves/activity":{"get":{"operationId":"getMyFavesActivity","summary":"What the fan's faves have been up to","description":"Use this to answer 'what are my friends up to?', 'which fandoms have my faves joined?' or 'did anyone pass me on a leaderboard?'. Faves are the people the fan has marked as friends in SoundCTRL. It reports fandoms joined in the last 7 days and any recorded leaderboard moves. It deliberately never reports what anyone listened to, and it identifies people by username only. `rank_changes` is often empty - that means nothing was recorded, not that nobody moved.","security":[{"bearerKey":[]}],"responses":{"200":{"description":"Recent activity for the fan's faves.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeFavesActivity"},"example":{"faves":[{"username":"fan_example_2","display_name":"Rosie","joined_fandoms_7d":["TWICE","NewJeans"],"rank_changes":[{"artist":"KATSEYE","from":11,"to":6}]}]}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}},"/me/summary":{"get":{"operationId":"getMySummary","summary":"The fan's whole SoundCTRL standing in one call","description":"Use this to answer 'how am I doing on SoundCTRL?' and as the FIRST call when a fan opens a conversation about their account - it saves calling /me, /me/rank and /me/battles separately. Returns up to five fandoms with the fan's rank in each, their soonest running stream battle, their total points, and a ready-to-post share about their best rank.","security":[{"bearerKey":[]}],"responses":{"200":{"description":"The fan's standing across SoundCTRL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeSummary"},"example":{"fandoms":[{"artist":{"name":"KATSEYE","image_url":null,"fandom_url":"https://app.soundctrl.xyz/fandom/KATSEYE"},"rank":7,"total_fans":214,"points":12450,"ranked":true}],"next_battle":null,"points_balance":18230,"share":{"url":"https://app.soundctrl.xyz/fandom/KATSEYE","image_url":"https://app.soundctrl.xyz/api/og/fan-ranking?rank=7&total=214&username=fan_example_1","text":"fan_example_1 has 18,230 SoundCTRL points and is #7 of 214 in the KATSEYE fandom."}}}}},"401":{"$ref":"#/components/responses/MeUnauthorized"},"429":{"$ref":"#/components/responses/MeRateLimited"}}}}},"components":{"securitySchemes":{"bearerKey":{"type":"http","scheme":"bearer","bearerFormat":"sck","x-soundctrl-note":"openapi.public.json declares this same scheme (same type, same scheme, same bearerFormat), so whichever fragment the merge applies last, the document is identical in meaning. Kept in both on purpose: each fragment stays a valid description of its own half.","description":"A SoundCTRL fan API key. The fan creates one in the SoundCTRL app under Settings > Muse and pastes it to you; it looks like sck_ followed by 43 characters. Send it as `Authorization: Bearer sck_...`. It is shown to the fan only once, so if they lose it they must create a new one. The key reads that one fan's data and nothing else: it can never spend their points, claim a reward, post, vote, or create or revoke keys. A key that has been revoked, or was never valid, answers 401 with `{\"error\": \"INVALID_KEY\"}` - tell the fan to create a new one in the app rather than retrying."}},"schemas":{"Share":{"type":"object","description":"A ready-to-post result. `text` is a finished sentence; post it as-is.","required":["url","image_url","text"],"properties":{"url":{"type":"string","description":"The SoundCTRL page behind this answer."},"image_url":{"type":"string","description":"A share-card image for this exact result."},"text":{"type":"string","description":"A complete sentence the fan can post."}}},"ArtistRef":{"type":"object","description":"How this API names an artist.","required":["name","image_url","fandom_url"],"properties":{"name":{"type":"string"},"image_url":{"type":["string","null"]},"fandom_url":{"type":"string","description":"The artist's SoundCTRL fandom page, where a fan joins."},"catalogued":{"type":"boolean","description":"Absent for every artist SoundCTRL has catalogued. Present and false only when the artist was resolved from the wider music catalog and has no SoundCTRL fandom yet; then fandom_url is the page where a fan starts the fandom."}}},"TopFan":{"type":"object","description":"A publicly ranked fan. Username and display name only - there is no other identifier, by design.","required":["username","display_name","points","rank"],"properties":{"username":{"type":"string"},"display_name":{"type":["string","null"]},"points":{"type":"integer"},"rank":{"type":"integer"}}},"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"A stable machine code.","examples":["MISSING_PARAM","UPSTREAM_UNAVAILABLE","INTERNAL_ERROR"]},"message":{"type":"string","description":"A human sentence you can relay to the fan."}}},"ArtistNotFound":{"type":"object","required":["error","message","suggestions"],"properties":{"error":{"type":"string","const":"ARTIST_NOT_FOUND"},"message":{"type":"string"},"suggestions":{"type":"array","description":"Near-miss artist names. Retry with one of these before telling the fan SoundCTRL does not know the artist.","items":{"type":"string"}}}},"BeefAnswer":{"type":"object","required":["question","a","b","verdict","debate_url","share"],"properties":{"question":{"type":"string"},"a":{"$ref":"#/components/schemas/ArtistRef"},"b":{"$ref":"#/components/schemas/ArtistRef"},"verdict":{"description":"null means no SoundCTRL debate has settled this yet.","oneOf":[{"type":"null"},{"type":"object","required":["winner","a_pct","b_pct","votes","thread_title","top_argument"],"properties":{"winner":{"type":"string","enum":["a","b","tie"]},"a_pct":{"type":"integer"},"b_pct":{"type":"integer"},"votes":{"type":"integer","description":"Total fan votes behind this verdict."},"thread_title":{"type":"string","description":"The Stan Wars thread the verdict came from."},"top_argument":{"type":["string","null"],"description":"The most-liked comment on that thread, unattributed."}}}]},"debate_url":{"type":"string","description":"Where the fan can start or join the debate."},"share":{"$ref":"#/components/schemas/Share"}}},"FandomProfile":{"type":"object","required":["artist","fandom","share"],"properties":{"artist":{"$ref":"#/components/schemas/ArtistRef"},"fandom":{"type":["object","null"],"required":["members","paid_members","members_change_7d","ranked_fans","top_fans","active_battle","lore","hottest_thread"],"properties":{"members":{"type":"integer","description":"Fans in the fandom from memberships, onboarding picks and verified listening - everyone SoundCTRL counts as a fan of this artist. This is the number to quote for 'how big is the fandom'. It is NOT the same number as a /fandoms/chart entry's `members`, which counts paying members only; use `paid_members` below to compare with the chart."},"paid_members":{"type":"integer","description":"Paying members only - the same figure /fandoms/chart ranks on, so this is the one to compare against a chart entry. Always less than or equal to `members`."},"members_change_7d":{"type":"integer","description":"New PAYING members in the last 7 days. Compare it against `paid_members`, not `members`."},"ranked_fans":{"type":"integer","description":"How many fans have earned points in this fandom. Counted up to 200; a value of 200 means 'at least 200'."},"top_fans":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/TopFan"}},"active_battle":{"oneOf":[{"type":"null"},{"type":"object","required":["opponent","ends_at","our_streams","their_streams","status"],"properties":{"opponent":{"type":"string"},"ends_at":{"type":"string"},"our_streams":{"type":"integer","description":"Only meaningful when `status` is `resolved`. Reads 0 before that, which does not mean nobody streamed."},"their_streams":{"type":"integer","description":"Only meaningful when `status` is `resolved`. Reads 0 before that."},"status":{"type":"string","enum":["active","awaiting_result","resolved"],"description":"`active` = running now, stream totals not counted yet. `awaiting_result` = the battle ended but the winner has not been computed (this can last up to an hour). `resolved` = finished, totals are final. Only report a score or a winner when this is `resolved`. Same three values /me/battles uses."}}}]},"lore":{"type":"array","maxItems":3,"description":"The fandom's own lore posts.","items":{"type":"object","required":["title","excerpt","url"],"properties":{"title":{"type":"string"},"excerpt":{"type":"string","maxLength":240},"url":{"type":"string"}}}},"hottest_thread":{"oneOf":[{"type":"null"},{"type":"object","required":["title","type","comment_count","url"],"properties":{"title":{"type":"string"},"type":{"type":"string","description":"debate, poll, stream_battle, lore or general."},"comment_count":{"type":"integer"},"url":{"type":"string"}}}]}}},"share":{"$ref":"#/components/schemas/Share"},"no_fandom_yet":{"type":"object","description":"Present only when fandom is null.","required":["message","start_url"],"properties":{"message":{"type":"string","description":"A finished sentence to read out, e.g. \"No fandom on SoundCTRL yet for Leon Thomas.\""},"start_url":{"type":"string","description":"Where the fan starts the fandom."}}}},"description":"When the artist is real but has no SoundCTRL fandom yet, fandom is null and no_fandom_yet says so in a finished sentence: tell the fan nobody has started that fandom and offer the start_url. Never describe a null fandom as a fandom with zero members."},"EventsAnswer":{"type":"object","required":["artist","events","source"],"properties":{"artist":{"description":"A full fandom reference when SoundCTRL knows the artist, otherwise just their name.","oneOf":[{"$ref":"#/components/schemas/ArtistRef"},{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}]},"events":{"type":"array","items":{"type":"object","required":["date","venue","city","region","country","tickets_url"],"properties":{"date":{"type":"string","description":"Date and time of the show in the venue's local time, e.g. 2026-11-02T20:00:00, or just the date (2026-11-02) when the time is not announced yet. Not UTC and no timezone suffix: read it out as written."},"venue":{"type":"string"},"city":{"type":"string"},"region":{"type":["string","null"]},"country":{"type":"string"},"tickets_url":{"type":["string","null"]}}}},"source":{"type":"string","const":"ticketmaster"}}},"ChartAnswer":{"type":"object","required":["period","entries","share"],"properties":{"period":{"type":"string","enum":["week","all"]},"entries":{"type":"array","maxItems":20,"items":{"type":"object","required":["rank","artist","members","change"],"properties":{"rank":{"type":"integer"},"artist":{"$ref":"#/components/schemas/ArtistRef"},"members":{"type":"integer","description":"PAYING members of this fandom. This is a narrower number than the `members` on /artists/{name}/fandom, which also counts onboarding picks and verified listening - compare this against that endpoint's `paid_members`, never against its `members`."},"change":{"type":["integer","null"],"description":"Paying members gained in the last 7 days, or null when it could not be computed."}}}},"share":{"$ref":"#/components/schemas/Share"}}},"PollsAnswer":{"type":"object","required":["polls"],"properties":{"polls":{"type":"array","maxItems":5,"items":{"type":"object","required":["title","artist","options","total_votes","vote_url","share"],"properties":{"title":{"type":"string"},"artist":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/ArtistRef"}]},"options":{"type":"array","items":{"type":"object","required":["label","votes","pct"],"properties":{"label":{"type":"string"},"votes":{"type":"integer"},"pct":{"type":"integer"}}}},"total_votes":{"type":"integer"},"vote_url":{"type":"string","description":"Where the fan casts their own vote."},"share":{"$ref":"#/components/schemas/Share"}}}}}},"HotThreadsAnswer":{"type":"object","required":["threads"],"properties":{"threads":{"type":"array","items":{"type":"object","required":["id","title","type","artist","comment_count","likes","url"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","description":"debate, poll, stream_battle, lore or general."},"artist":{"type":["string","null"]},"comment_count":{"type":"integer"},"likes":{"type":"integer"},"url":{"type":"string"}}}}}},"MeError":{"type":"object","description":"Every error this API returns has this shape. `error` is a stable machine code; `message` is a sentence you can say to the fan as-is.","required":["error","message"],"properties":{"error":{"type":"string","description":"INVALID_KEY (the key is wrong, revoked, or account linking is not switched on yet - the fan must create a new key in the app), RATE_LIMITED (more than 120 requests in a minute on one key; wait and retry), ARTIST_NOT_FOUND (SoundCTRL has no fandom for that artist; `suggestions` carries near-miss names to retry with), NO_FANDOMS (the fan has not joined any fandom yet), MUSE_UNAVAILABLE or INTERNAL_ERROR (a SoundCTRL problem, not the fan's; retry in a moment).","examples":["INVALID_KEY"]},"message":{"type":"string"},"suggestions":{"type":"array","description":"Only on ARTIST_NOT_FOUND. Artist names close to the one asked for. Retry the request with one of these before telling the fan SoundCTRL does not know the artist.","items":{"type":"string"}}}},"MeIdentity":{"type":"object","description":"Who the key belongs to, and which fandoms they are in.","required":["username","display_name","fandoms"],"properties":{"username":{"type":["string","null"],"description":"The fan's SoundCTRL username. Null if they have not set one."},"display_name":{"type":["string","null"],"description":"The name the fan chose to be shown by. Null when they never set one - do not substitute the username for it."},"fandoms":{"type":"array","items":{"type":"object","required":["artist","member_since"],"properties":{"artist":{"$ref":"#/components/schemas/ArtistRef"},"member_since":{"type":"string","format":"date-time","description":"When the fan joined this fandom."}}}}}},"MeRank":{"type":"object","description":"Where the fan sits on one fandom's points leaderboard.","required":["artist","rank","total_fans","points","share"],"properties":{"artist":{"$ref":"#/components/schemas/ArtistRef"},"rank":{"type":["integer","null"],"description":"1 is the top fan. NULL means the fan is not on this board - check `ranked` and `hidden_from_leaderboard` before you explain why, and never report a null rank as rank 0 or last place."},"total_fans":{"type":"integer","description":"How many fans are ranked in this fandom, i.e. what the rank is out of."},"points":{"type":"integer","description":"The fan's points in this fandom (not their account total)."},"ranked":{"type":"boolean","description":"False means SoundCTRL could not read the leaderboard on this request, so `rank` is null for a technical reason rather than because the fan is unranked. Say 'I could not read your rank just now', not 'you are unranked'."},"ranked_depth_reached":{"type":"boolean","description":"True means the board was read to its depth limit, so `total_fans` may undercount and a very deep rank may read null."},"hidden_from_leaderboard":{"type":"boolean","description":"True means the fan has turned on 'hide me from leaderboards' in SoundCTRL Settings, which is why they have no rank. Tell them they are opted out, not that they are unranked."},"share":{"$ref":"#/components/schemas/Share"}}},"MeChallenges":{"type":"object","description":"Today's three daily challenges and whether the fan has finished each one. The set rotates daily and is the same for everyone that day.","required":["challenges"],"properties":{"challenges":{"type":"array","items":{"type":"object","required":["id","title","points","completed"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"points":{"type":"integer","description":"Points the fan earns for finishing it."},"completed":{"type":"boolean","description":"True when SoundCTRL can already see the fan has done it today."},"description":{"type":"string","description":"What the fan has to do. Use this when they ask how to finish one."}}}}}},"MeBattle":{"type":"object","description":"A stream battle: two fandoms streaming against each other for 48 hours, with points for everyone in the winning fandom.","required":["artist","opponent","ends_at","our_streams","their_streams","status"],"properties":{"artist":{"$ref":"#/components/schemas/ArtistRef"},"opponent":{"type":"string","description":"The artist the fan's fandom is up against."},"ends_at":{"type":["string","null"],"format":"date-time","description":"When the battle closes."},"our_streams":{"type":"integer","description":"Streams credited to the fan's side, as SoundCTRL displays them. A battle still running shows 0 here: the totals are published when it is settled."},"their_streams":{"type":"integer","description":"The same number for the opposing fandom."},"status":{"type":"string","enum":["active","awaiting_result","resolved"],"description":"active: still running, the fan can still help. awaiting_result: the clock ran out and SoundCTRL is counting - say the result is being counted, do not guess a winner. resolved: settled, see `winner`."},"winner":{"type":["string","null"],"description":"The winning artist's name once the battle is settled, otherwise null."}}},"MeBattles":{"type":"object","required":["battles"],"properties":{"battles":{"type":"array","description":"Running battles first, soonest deadline first; then ones being counted; then settled ones.","items":{"$ref":"#/components/schemas/MeBattle"}}}},"MeRewards":{"type":"object","description":"What the fan can get with their points for one artist.","required":["artist","points_balance","rewards"],"properties":{"artist":{"oneOf":[{"$ref":"#/components/schemas/ArtistRef"},{"type":"null"}],"description":"The artist these rewards belong to. Null only when the fan is in no fandom and named no artist."},"points_balance":{"type":"integer","description":"The fan's points WITH THIS ARTIST - the balance a claim is checked against. It is not their account total."},"rewards":{"type":"array","items":{"type":"object","required":["id","title","cost_points","claimable","url"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"cost_points":{"type":"integer"},"claimable":{"type":"boolean","description":"True when the fan can claim it right now: enough points, still in stock, and not already claimed."},"url":{"type":"string","description":"Where the fan claims it. YOU CANNOT CLAIM IT FOR THEM - this API is read-only; send them to this page."},"already_claimed":{"type":"boolean"},"in_stock":{"type":"boolean"},"points_needed":{"type":"integer","description":"How many more points the fan needs. 0 when they can already afford it."}}}}}},"MeFavesActivity":{"type":"object","description":"What the people the fan has marked as faves have been doing lately.","required":["faves"],"properties":{"faves":{"type":"array","items":{"type":"object","required":["username","display_name","joined_fandoms_7d","rank_changes"],"properties":{"username":{"type":"string"},"display_name":{"type":["string","null"]},"joined_fandoms_7d":{"type":"array","description":"Artist names this fave joined in the last 7 days.","items":{"type":"string"}},"rank_changes":{"type":"array","description":"Leaderboard moves SoundCTRL has a record of. Often empty - SoundCTRL only snapshots ranks occasionally, so an empty list means 'nothing recorded', NOT 'no change'. It is always empty for a fave who has hidden themselves from leaderboards.","items":{"type":"object","required":["artist","from","to"],"properties":{"artist":{"type":"string"},"from":{"type":"integer"},"to":{"type":"integer"}}}}}}}}},"MeSummary":{"type":"object","description":"Everything about the fan's SoundCTRL standing in one call.","required":["fandoms","next_battle","points_balance","share"],"properties":{"fandoms":{"type":"array","description":"Up to five of the fan's fandoms with their rank in each.","items":{"type":"object","required":["artist","rank","total_fans","points"],"properties":{"artist":{"$ref":"#/components/schemas/ArtistRef"},"rank":{"type":["integer","null"]},"total_fans":{"type":"integer"},"points":{"type":"integer"},"ranked":{"type":"boolean","description":"False means the rank could not be read, not that the fan is unranked."}}}},"next_battle":{"oneOf":[{"$ref":"#/components/schemas/MeBattle"},{"type":"null"}],"description":"The fan's soonest stream battle that is still running, or null."},"points_balance":{"type":"integer","description":"The fan's total SoundCTRL points across every fandom."},"share":{"$ref":"#/components/schemas/Share"}}}},"responses":{"MeUnauthorized":{"description":"The API key is missing, wrong, revoked, or SoundCTRL account linking is not switched on yet. Ask the fan to create a new key in the SoundCTRL app under Settings > Muse. Do not retry with the same key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeError"},"example":{"error":"INVALID_KEY","message":"That API key is not valid. Mint a new one in the SoundCTRL app under Settings > Muse."}}}},"MeRateLimited":{"description":"More than 120 requests in one minute on this key. Wait for the number of seconds in the Retry-After header.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeError"}}}},"MeArtistNotFound":{"description":"SoundCTRL has no fandom for that artist. `suggestions` carries near-miss names - retry with one before telling the fan SoundCTRL does not know the artist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeError"}}}}}}}