streamless.fm
PlayerSupport
For developers · public API

Build with Streamless FM.

The stream, what is playing on it, the schedule, the listener numbers and the collection, all readable without a key or a sign-up. If you are writing a player, a widget, a bot or a car head unit, this is everything you need.

  • Start here
  • Rules of the road
  • The stream
  • Station API
  • Site API
  • Stability

Last updated: 29 August 2026

Start here

Two URLs do most of the work. One plays the radio, the other says what is on it. Everything is plain JSON over HTTPS, with no key and no sign-up. The station API sends Access-Control-Allow-Origin: *, so browser code can call it straight from the page; the site API currently cannot be called cross origin from a browser, which is covered below.

const audio = new Audio("https://live.streamless.fm/listen");
audio.play();

async function poll() {
  const res = await fetch("https://api.streamless.fm/nowplaying");
  const { current, show } = await res.json();
  // current is null while a talk break or station ID is on air.
  if (current) console.log(show, "-", current.artist, "-", current.title);
}
poll();
setInterval(poll, 15000);

That is a working radio. The rest of this page is detail you can add as you need it.

Rules of the road

Short list, and the first two are not style preferences. They are the terms the station itself is licensed under, so an app that breaks them puts the station at risk rather than just annoying us.

Send listeners straight to the stream. Never proxy or re-stream it. Our music licences (APRA AMCOS and PPCA) cover transmission within Australia. Your app must connect the listener’s own device to live.streamless.fm directly. Routing the audio through your own server, re-broadcasting it, piping it into a Discord or Twitch stream, or caching and re-serving it, all break that licence and defeat the geo-restriction at the same time. Link out to the stream, always.

Australia only. The stream is geo-restricted to Australian IP addresses and answers everything else with a 403. Cloud and CI addresses are not in the allowlist, so a server-side fetch or an automated test will be refused even from an Australian data centre. Test from a real consumer connection. The JSON endpoints on this page are open worldwide; only the audio is restricted.

  • One connection per listener, and no reconnect loops. The station counts Icecast connections, and that count decides whether the DJ speaks and what gets reported to the licensing bodies. A client that reconnects in a tight loop costs us real money and pollutes the compliance figures.
  • Identify yourself. Set a descriptive User-Agent on both the stream and the API, something like MyRadioApp/1.2 (contact@example.com). It is how we tell a well behaved app apart from a scraper when traffic spikes.
  • Poll politely. /nowplaying is cached for about five seconds and /stats/public for fifteen, so polling faster returns the same bytes. Every ten to fifteen seconds is plenty, and songs run for minutes.
  • Cache what you are given. The endpoints under streamless.fm/api/public send real Cache-Control headers and sit behind a CDN. Honour them. Where a CDN endpoint answers your question, prefer it over the station API, which runs on one small server that is also making the radio.
  • Credit the station with a visible link back to streamless.fm, and do not present the station as your own or imply we endorse your app.

What is coming up is artist names only, everywhere, forever. Our licence does not allow us to publish a queue: no upcoming titles, no order, no times. That is why upcoming_artists looks so thin, and it is not an oversight we will be fixing. Aired history, by contrast, is unrestricted, and most of this API is built on it.

The stream

One MP3 stream, always on. Metadata rides in band as ICY, so a standard player shows the title without touching the API at all.

https://live.streamless.fm/listen

Aliases (identical audio, for players that key off a file extension):
  /listen.mp3   /live   /live.mp3   /stream

Playlist files:
  https://live.streamless.fm/listen.m3u
  https://live.streamless.fm/listen.pls
  • HTTP/1.1 only. The stream host does not offer HTTP/2 or HTTP/3, on purpose: in band ICY metadata does not survive them. Your client will negotiate 1.1 automatically. Nothing to do, but do not be surprised by it.
  • Ask for the metadata. Send Icy-MetaData: 1 and the title arrives in band, interleaved with the audio at the interval given in the icy-metaint response header. Without that request header you get audio and no titles, which is the usual reason a custom player shows nothing. The ICY title comes from the file’s own tags, so it can occasionally lag a correction made in our library; for display, /nowplaying is the better source.
  • Stream ripping software is refused by user agent, as a condition of our licence.

Station API

Base URL https://api.streamless.fm. This is the station server itself: live, uncached beyond a few seconds, and running on one small box in Sydney alongside the audio engine. Four public endpoints, all GET, all open worldwide. Anything else on that host answers 401 and is not for public use.

GETapi.streamless.fm/nowplaying

What is on air. The endpoint most apps need, and usually the only one. Cached about five seconds. source is "AI" when the picker chose the song and "Manual" when a human queued it. current can be null when something other than a song is on air, such as a talk break or a station ID, so guard for it.

{
  "current": {
    "title": "I Think We're Alone Now",
    "artist": "Tiffany",
    "album": "",
    "year": 1987,
    "source": "AI"
  },
  "upcoming_artists": ["Jimmy Nail", "Scissor Sisters", "Yazoo"],
  "show": "Saturday Sessions",
  "shoutouts_enabled": true
}

GETapi.streamless.fm/schedule

The weekly program guide, plus what is on now and what is next. Hours are whole numbers in Melbourne time on a half open range, so start: 12, end: 18 runs to 5:59 pm. host is null for a show that runs without a presenter. Add ?pools=1 for each seeded show’s unordered, untimed pool of eligible files, which describes what fits a show and never what plays next; it adds about 145 kB, so only ask if you are drawing it.

{
  "timezone": "Australia/Melbourne",
  "generatedAt": "2026-08-29T07:48:54.580Z",
  "now": {
    "name": "Saturday Sessions",
    "start": 12,
    "end": 18,
    "label": "12:00 pm to 6:00 pm",
    "host": "Jeff",
    "criteria": {
      "theme": "easy, feel-good music for a relaxed Saturday afternoon",
      "genres": [], "energies": [], "moods": [], "tags": [],
      "yearMin": 1980, "yearMax": null
    },
    "seedShow": { "seeds": 5, "seedArtists": ["..."], "poolSize": 225 }
  },
  "next": { "...same shape..." },
  "week": [ { "day": "Mon", "shows": [ "...slots..." ] } ]
}

GETapi.streamless.fm/stats/public

Today’s listener numbers, sampled once a minute, on the Melbourne day. Cached fifteen seconds. samples runs from midnight to now, so it grows through the day and resets overnight.

{
  "date": "2026-08-29",
  "listenersNow": 2,
  "peak": 22,
  "peakTime": "16:09:32",
  "average": 2.81,
  "sampleCount": 1068,
  "samples": [ { "time": "00:00:40", "listeners": 1 } ],
  "songsToday": 270
}

GETapi.streamless.fm/health

An uptime probe, not a data source: 200 when the station is on air and 503 when it is not. Useful if your app wants to show an off air state rather than a silent player.

{ "ok": true, "source_connected": true, "nowplaying_fresh": true, "nowplaying_age_secs": 98 }

Site API

Base URL https://streamless.fm/api/public. These are the website’s own read endpoints, served from a CDN with real cache headers, so they are cheap to call and the right place for anything that is not second by second. All GET, no key.

These do not send CORS headers yet. Native apps, servers, scripts and server side rendering can call them freely, because none of those enforce the browser’s same origin rule. Browser JavaScript on your own domain cannot, and will fail with a CORS error. If that is blocking you, mail us and we will turn the headers on. The station API above is already open to browsers.

GETstreamless.fm/api/public/artists

Every artist in the collection with its song count, all time aired plays and last air time. Cached five minutes at the browser and fifteen at the edge.

{
  "artists": [
    {
      "name": "a-ha",
      "slug": "a-ha",
      "songCount": 1,
      "playCount": 47,
      "lastPlayedAt": "2026-08-28T12:40:10.984848+00:00"
    }
  ]
}

GETstreamless.fm/api/public/artist/{slug}

One artist in detail: a verified band photo and album cover, genres, aired history, a thirty day play sparkline, tempo position against the rest of the library, and acoustically similar artists we also play. slug is the value from /artists. Anything we cannot verify comes back null rather than as a guess.

{
  "name": "a-ha",
  "slug": "a-ha",
  "photo": "https://cdn-images.dzcdn.net/...",
  "cover": "https://is1-ssl.mzstatic.com/...",
  "genres": ["pop"],
  "stats": {
    "rank": 162,
    "rankedArtists": 527,
    "playCountAllTime": 47,
    "firstAiredAt": "2026-07-08T03:53:01.661529+00:00",
    "peakListeners": 7,
    "tempoPercentile": 97,
    "medianBpm": 172,
    "yearCohort": { "year": 1985, "count": 34 },
    "daily": [1, 1, 2, 0, 1, "...30 days..."]
  },
  "lastPlayedAt": "2026-08-28T12:40:10.984848+00:00",
  "playCount": 31,
  "windowDays": 30,
  "soundsLike": [ { "name": "Tears For Fears", "slug": "tears-for-fears" } ],
  "tracks": [ { "title": "Take on Me", "bpm": 172, "album": "Hunting High and Low" } ]
}

GETstreamless.fm/api/public/app-stats

The station at a glance: collection size, spins in the last day and the last thirty, the five most played songs of the last month with cover art, and recent supporter first names. Never any dollar amounts.

{
  "songs": 820,
  "spins24h": 368,
  "spins30d": 11148,
  "top": [
    {
      "title": "Jessie's Girl",
      "artist": "Rick Springfield",
      "spins": 56,
      "art": "https://is1-ssl.mzstatic.com/..."
    }
  ],
  "supporters": ["Shawn"]
}

GETstreamless.fm/api/public/soundmap

Every song in the collection as an unlabelled point in two dimensions, each axis 0 to 1, derived from an acoustic fingerprint. Songs that sound alike sit near each other. No titles, no artists, no order. Good for a scatter plot behind a player. Cached an hour at the browser and a day at the edge, because it only moves when the library grows.

{ "pts": [[0.6, 0.492], [0.475, 0.446], [0.828, 0.415]] }

GETstreamless.fm/api/public/highlights

Recent talk breaks from Jeff, our AI host, as playable audio with the script that was read. Add ?host=Jeff to filter. These are voice recordings from the station’s own pipeline, never excerpts of the music stream, and shout outs are never included.

{
  "highlights": [
    {
      "id": 1608,
      "aired_at": "2026-08-29T07:35:26.309845+00:00",
      "presenter": "Jeff",
      "angle": "support-plug/two",
      "show_name": "Saturday Sessions",
      "kind": "break",
      "script": "John Mellencamp's turning up next with Cherry Bomb ...",
      "duration_secs": 21,
      "url": "https://...supabase.co/storage/v1/object/public/dj-highlights/..."
    }
  ]
}

GETstreamless.fm/api/public/shoutout

The shout out feed. recent is what has aired, as first names with no timestamps. upcoming is the waiting queue as occasions only: unmoderated names never go public, so there is no name field to read there. Submitting a shout out is a signed in action on the website and is not part of this API.

{
  "recent": [
    { "from": "Sam", "to": "Alex", "occasion": "Happy Birthday" }
  ],
  "upcoming": [
    { "occasion": "Just Saying G'day" }
  ]
}

GETstreamless.fm/api/public/requests

The last dozen listener requests that have already aired, newest first. who is a first name when the member chose to be named on air, and null otherwise. Pending requests are not published, and requesting a song is a signed in action on the website, not an API call.

{
  "items": [
    { "title": "Rock Me Amadeus", "artist": "Falco", "who": null },
    { "title": "Beautiful Day", "artist": "U2", "who": "Sam" }
  ]
}

What we promise, and what we do not

The endpoints on this page are the supported set. We will add fields to them, but we will not rename or remove one without saying so first on what’s new.

If you found something in the network tab that is not on this page, it is internal. It may change shape or disappear on any deploy, and several of those routes are proxies to third party services running on our quota rather than data of ours to give away. Please do not build on them. The same goes for reading our database directly: it works today and it is not supported.

No rate limits are enforced right now. That is trust, not policy, and it lasts exactly as long as it is not abused.

Building something, stuck on something, or want an endpoint that does not exist yet? Tell us at hello@streamless.fm. We would genuinely like to see what you make.

Listen

PlayerWays to listenSchedule

The music

The collectionThe last hourThe song pickerThe hosts

The station

SupportContactWhat’s newDeveloper API
TermsPrivacy