Data feeds
Read your reviews, and the reviews of people you follow, in a feed reader or your own scripts.
Which feeds exist
- Your feed:
https://rotationmusic.xyz/@username/feed.rssorfeed.json. With your key it includes your drafts, dismissed logs and private lists. Without a key it shows only what is public. - Anyone's public feed: the same address with their username. No key needed.
- Who you follow:
/following.jsonlists everyone you follow whose listening you can see: accounts set to Everyone or Public, and accounts that approved your follow request, with their feed addresses, and/following.opmlis the same list for importing into a feed reader. A key is required. - Everyone you follow, merged:
/feed.rssor/feed.json, for everyone you follow whose listening you can see: accounts set to Everyone or Public, and accounts that approved your follow request. It shows only public items. A key is required.
Older /u/<id>/feed.rss links keep working. If someone renames themselves, their old address redirects to the new one.
Keys
Make a key in the app under Settings, then Data Feed. A key looks like rot_….
- From a script, send it as a header:
Authorization: Bearer rot_…. - For a feed reader that can't send headers, add
?key=rot_…to the address. The key then also appears in the feed's own links (feed_url,next_urland the RSS self link), so use the header from scripts. - Using your key on someone else's feed gives you the public view of it (
viewofpublic), never their private items. - Treat a key like a password. Anyone who has it can read your private items.
- You can revoke a key in the app at any time. It stops working within 5 minutes.
Examples
Replace YOUR_KEY with your key and username with yours.
Shell
curl -H "Authorization: Bearer YOUR_KEY" \
https://rotationmusic.xyz/@username/feed.json
curl -H "Authorization: Bearer YOUR_KEY" \
https://rotationmusic.xyz/following.json
Python: everyone you follow, one feed at a time
import json, urllib.request
KEY = "YOUR_KEY"
def get(url, key=None):
request = urllib.request.Request(url, headers={"User-Agent": "my-rotation-script/1.0"})
if key:
request.add_header("Authorization", "Bearer " + key)
with urllib.request.urlopen(request) as response:
return json.load(response)
following = get("https://rotationmusic.xyz/following.json", KEY)["following"]
for person in following:
url = person["feed_json"] # public feed, no key needed
while url:
feed = get(url)
for item in feed["items"]:
print(person["username"], item["title"], item.get("url", ""))
url = feed.get("next_url") # None on the last page
What is in an item
JSON
Feeds are JSON Feed 1.1, so id, url, title, content_html, content_text, date_published, date_modified, authors and image mean what that standard says. A log's url is its own review page, https://rotationmusic.xyz/@username/review/…; _rotation.rotation_url is the week page with that review's anchor. A week note's url is the week page. Drafts, dismissed logs and lists have no url.
Everything specific to Rotation is under _rotation:
kind:log(one album in a week),week_noteorlist.status: for a log,published,draftordismissed. For a week note or a list,publicorprivate.author:id,usernameanddisplay_name.album(logs):mbid(a MusicBrainz release group id, or a Rotation-made id for albums MusicBrainz doesn't know),title,artistandcover_url.week:id,start_dateandend_date.rotation_url(public logs): the week page,/r/…#log-…, that this review belongs to.rating(logs): 0.5 to 5 in half steps, ornullwhen unrated.is_hearted,favorite_trackandfavorite_track_mbid(logs): the favorite track's title, and its MusicBrainz recording id (https://musicbrainz.org/recording/…) when Rotation knows it, otherwisenull.note_markdown: your note, where*bold*is bold and_italic_is italic.review_text(logs): the same note as plain text, with the*bold*and_italic_markers removed and@mentionskept.nameanditems(lists): each list item hasposition,albumandnote_markdown.
The feed itself has _rotation.version (always 1 for now). A person's feed also has view (author with your key, public without) and owner. The merged following feed has view set to following. /following.json has a top-level version next to following.
RSS
RSS 2.0 with the usual title, link, guid, pubDate, dc:creator and description. The title shows status, such as "Draft · Slowdive — Slowdive", for readers that ignore the rotation: elements below. Scripts should read <rotation:status> instead. For logs with a real MusicBrainz id, there is a <category domain="https://musicbrainz.org/release-group"> with the id, and a MusicBrainz link at the end of the description.
The channel declares xmlns:rotation="https://rotationmusic.xyz/feeds/rss/1" and carries <rotation:version>1</rotation:version>. Each item has rotation:kind and rotation:status. Logs also have rotation:mbid (only for real MusicBrainz ids), rotation:rating (only when rated), rotation:hearted (true or false), rotation:week (the week's start date) and rotation:rotation_url (public logs: the week page with the review's anchor), rotation:review (the note as plain text, when there is one), and rotation:favorite_track (the favorite track's title, with an mbid attribute holding its MusicBrainz recording id when known; left out when there is no favorite track).
Paging, limits and errors
- JSON returns 50 items per page. When there are more,
next_urlis the next page. RSS returns the newest 50 and has no paging. - An edited week note or list moves back to the top of the feed with a new
date_published. - Limit: 60 per key per clock hour. It counts reads of your own feed with your key, plus
/following.json,/following.opml,/feed.rssand/feed.json. Reading someone else's public feed, with or without a key, isn't counted, and reads without a key have no per-reader limit. - Responses are cached for 5 minutes per key and address, so checking more often than every 5 minutes doesn't help, and changes can take a few minutes to show up.
400: the page link (cursor) is invalid.401: a key is missing, wrong or revoked.404: no such feed, or it isn't public.405: only GET and HEAD are supported.429: over the limit, wait the number of seconds in theRetry-Afterheader (the time until the next hour).503: feeds are briefly unavailable, try again shortly.
Our promise: version 1 stays put
This is version 1 of the feeds. Item ids, addresses, field names and what they mean won't change or disappear. We may add new fields, so ignore any you don't know. If we ever have to make a breaking change, it will arrive as a new version, announced ahead, and version 1 will keep running alongside it.
October 2026: a log's url now points at its own review page instead of the week page; the week address moved to rotation_url. Ids and every other field are unchanged.
October 2026: logs gained review_text and favorite_track_mbid in JSON, and rotation:review and rotation:favorite_track in RSS. Nothing was renamed or removed.