Classic Radio Network logo
Free station listing

Add your station to Classic Radio Network

Paste your stream URL and we check it instantly: it has to play, and if we can read what's playing, your station is listed as Premium.

1Test your stream 2Submit details 3Quick review, then you're live

Manual submission Recommended

Fill in one short form. We validate your stream as you go and tell you exactly what to fix. Takes about two minutes.

Automated (API)

Submit stations from your own software, check status, and push now-playing data on every track change. JSON over HTTPS.

Listing tiers

Your tier is set automatically from what your stream provides, and it updates on its own. You never have to apply for it.

Tier 1

Standard

Requires a valid, playable stream

  • Listed in the directory & Android app
  • HTTPS relay for http:// streams
  • Station page & search listing
Tier 2

Premium

Your station also lists its currently playing track

  • Everything in Standard
  • Premium badge, ranked above Standard
  • Live track shown on your directory card
  • Track history, artist bios & artwork

To get Premium, use any one of these: in-stream metadata (ICY/StreamTitle, which most encoders send by default), an Icecast or SHOUTcast status page, a JSON now-playing URL, or API pushes. Premium stays active while we've seen a track in the last 48 hours.

Submit your station

Start with Test my stream. It checks playback and now-playing in a few seconds and fills in details from your stream where it can.

Use the direct audio address (Icecast mount, SHOUTcast /; or /stream). We also accept .pls and .m3u files. HLS isn't supported.
AzuraCast: https://YOUR-SERVER/api/nowplaying/SHORTCODE. See the accepted formats.
  After you submit you'll get a station key. Have your playout software call the push endpoint on every track change. Premium turns on with your first push.

Check status

Look up your station's review status, tier and current track using the ID and key you got when you submitted.

Developer API

The same API runs the form above. Requests and responses are JSON over HTTPS, CORS is open, and you don't need an account. Each station gets its own key when it's submitted.

Quick start

  1. Validate your stream with POST ?action=validate. Nothing is saved, and you see your tier right away.
  2. Submit with POST ?action=submit. Store the returned id and station_key securely, because the key is shown once.
  3. Later: change your listing's details with POST ?action=update and the same key.
  4. Optional: push the current track with POST ?action=push_now_playing on each song change to stay Premium.

Base URL: https://classicradio.network/api.php. Send bodies as JSON (Content-Type: application/json). Form-encoded bodies work too.

Authentication: endpoints that act on a station take its key in one of these, in order of preference: Authorization: Bearer KEY, then X-Station-Key: KEY (use this if your host strips Authorization), then a key parameter.

Validate a stream

POST /api.php?action=validate

Connects to the stream, follows redirects and .pls/.m3u playlists, confirms it sends real audio (MP3, AAC, Ogg/Opus or FLAC), and looks for now-playing data. Only stream_url is required. You can add np_method and np_url to test a JSON feed.

curl -X POST "https://classicradio.network/api.php?action=validate" \
  -H "Content-Type: application/json" \
  -d '{"stream_url": "https://stream.example.com/radio.mp3"}'

The response has the same validation object shown under Submit. suggested holds name, genre, description and URL read from your stream's ICY headers, which you can use to pre-fill a form.

Submit a station

POST /api.php?action=submit
FieldRequiredNotes
stream_urlyesDirect audio URL, or a .pls/.m3u that points to one. Must be reachable from the public internet.
nameyes2–80 characters
emailyesContact for the review. Never published.
genrenoUp to 60 characters
descriptionnoUp to 500 characters, plain text
logonoSquare image, https:// recommended
website, country, languagenoShown on your listing
np_methodnoauto (default) · json · push · none
np_urlif jsonYour now-playing JSON feed (see formats)
curl -X POST "https://classicradio.network/api.php?action=submit" \
  -H "Content-Type: application/json" \
  -d '{
    "name":        "Golden Oldies FM",
    "stream_url":  "https://stream.example.com/radio.mp3",
    "email":       "[email protected]",
    "genre":       "Oldies",
    "description": "The best of the 50s and 60s, 24/7.",
    "logo":        "https://example.com/logo.png",
    "website":     "https://example.com",
    "np_method":   "auto"
  }'

Response

HTTP/1.1 201 Created
{
  "success": true,
  "id": "CS1A2B3C4D5E",
  "station_key": "9f2c…40 hex chars…e1",
  "status": "pending",
  "tier": 2,
  "tier_name": "Premium",
  "message": "Your station passed validation and is waiting for a quick review before it goes live.",
  "important": "Save your station_key now — it is shown only once …",
  "validation": {
    "valid": true,
    "errors": {},
    "warnings": [],
    "stream": { "playable": true, "url": "https://stream.example.com/radio.mp3",
                "format": "MP3", "content_type": "audio/mpeg", "bitrate_kbps": 128, "https": true },
    "now_playing": { "artist": "The Platters", "title": "Only You",
                     "text": "The Platters - Only You", "source": "icy" },
    "tier": 2,
    "tier_name": "Premium",
    "suggested": { "name": "Golden Oldies FM", "genre": "Oldies", "description": "", "url": "" }
  }
}

A submission that fails validation returns 422 with validation.errors keyed by field name. If the stream is already listed, you get 409 with the existing id.

Check submission status

GET /api.php?action=submission&id={id}
curl "https://classicradio.network/api.php?action=submission&id=CS1A2B3C4D5E" \
  -H "Authorization: Bearer YOUR_STATION_KEY"

Returns status (pending · approved · rejected with a reject_reason), tier, online, the detected np_source, the current now_playing track, and a listing_url once you're live.

Update a listing

POST /api.php?action=update

Change your station's details after it's submitted, using its key. Send id plus only the fields you want to change; anything you leave out stays as it is. Changes show in the directory straight away and don't send the station back for review.

FieldNotes
idRequired. Your station id.
name2–80 characters
genreUp to 60 characters
descriptionUp to 500 characters, plain text. Send "" to clear it.
logo, websiteFull http(s):// URLs, or "" to clear
emailContact for the review. Never published.
country, languageShown on your listing

The stream URL can't be changed here: submit the new stream as a new station. Now-playing settings follow your pushes (see below). A station that wasn't accepted returns 403.

curl -X POST "https://classicradio.network/api.php?action=update" \
  -H "Authorization: Bearer YOUR_STATION_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id":          "CS1A2B3C4D5E",
    "description": "The best of the 50s and 60s, 24/7, commercial free.",
    "logo":        "https://example.com/new-logo.png"
  }'

Response

HTTP/1.1 200 OK
{
  "success": true,
  "updated": ["description", "logo"],
  "data": { "id": "CS1A2B3C4D5E", "name": "Golden Oldies FM", "status": "approved", … }
}

updated lists the fields that were applied, and data is the same view of your station as Check submission status returns.

Push now-playing

POST /api.php?action=push_now_playing

This is the most reliable way to stay Premium. Call it once each time the track changes. Body fields: id (required), title (required), artist, and artwork (an https image URL). If you only have one string, send text as "Artist - Title" and we split it for you. Your first push switches the station to push mode, and auto-detection stops.

curl -X POST "https://classicradio.network/api.php?action=push_now_playing" \
  -H "Authorization: Bearer YOUR_STATION_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "CS1A2B3C4D5E", "artist": "The Platters", "title": "Only You"}'
import requests

API         = "https://classicradio.network/api.php"
STATION_ID  = "CS1A2B3C4D5E"
STATION_KEY = "your-station-key"   # keep this secret

def push_now_playing(artist: str, title: str, artwork: str = "") -> None:
    """Call this every time the track changes."""
    r = requests.post(
        f"{API}?action=push_now_playing",
        headers={"Authorization": f"Bearer {STATION_KEY}"},
        json={"id": STATION_ID, "artist": artist, "title": title, "artwork": artwork},
        timeout=10,
    )
    r.raise_for_status()

push_now_playing("The Platters", "Only You")
// Node 18+ (built-in fetch)
const API = 'https://classicradio.network/api.php';

async function pushNowPlaying(artist, title, artwork = '') {
  const res = await fetch(`${API}?action=push_now_playing`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CRN_STATION_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ id: process.env.CRN_STATION_ID, artist, title, artwork }),
  });
  if (!res.ok) throw new Error(`Push failed: ${res.status} ${await res.text()}`);
  return res.json();
}

await pushNowPlaying('The Platters', 'Only You');
<?php
function crn_push_now_playing(string $stationId, string $stationKey, string $artist, string $title): array {
    $ch = curl_init('https://classicradio.network/api.php?action=push_now_playing');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'X-Station-Key: ' . $stationKey],
        CURLOPT_POSTFIELDS     => json_encode(['id' => $stationId, 'artist' => $artist, 'title' => $title]),
    ]);
    $body = curl_exec($ch);
    curl_close($ch);
    return json_decode((string)$body, true) ?? [];
}
# Liquidsoap 2.x — push every metadata change
crn_id  = "CS1A2B3C4D5E"
crn_key = "your-station-key"

def crn_push(m) =
  thread.run({
    ignore(http.post(
      "https://classicradio.network/api.php?action=push_now_playing",
      headers=[("Content-Type", "application/json"), ("X-Station-Key", crn_key)],
      data=json.stringify({id=crn_id, artist=m["artist"], title=m["title"]})
    ))
  })
end

radio = source.on_metadata(radio, crn_push)

For playout software that can only call a URL on track change (SAM Broadcaster, RadioDJ, mAirList, Rivendell and similar), use a GET request and swap ARTIST and TITLE for your software's URL-encoded placeholders. The key is visible in the URL, so use this option only from your own studio machine.

https://classicradio.network/api.php?action=push_now_playing&id=CS1A2B3C4D5E&key=YOUR_STATION_KEY&artist=ARTIST&title=TITLE

Now-playing sources

With np_method: "auto", we check these in parallel and keep the first one that returns a real track:

  1. Icecast status: /status-json.xsl on your server, matched to your mount
  2. SHOUTcast v2 stats: /stats?sid=1&json=1
  3. SHOUTcast v1: /7.html
  4. In-stream ICY metadata (StreamTitle): most encoders (BUTT, Mixxx, RadioBOSS, SAM, Liquidsoap, AzuraCast) send this when "update metadata" is on

We ignore placeholder titles like "Unknown", "Live" or your station name, because they don't identify a song. Use np_method: "json" with np_url if you already publish a feed:

// Any of these shapes works for np_method "json" (first match wins):

{ "artist": "The Platters", "title": "Only You" }              // plain
{ "title": "The Platters - Only You" }                         // combined
{ "now_playing": { "song": { "artist": "…", "title": "…", "art": "https://…" } } }   // AzuraCast
{ "data": { "artist": "…", "title": "…", "artwork": "https://…" } }                  // SHOUTcast Widgets
{ "songtitle": "The Platters - Only You" }                     // SHOUTcast v2 stats
{ "song": { … } }  { "current": { … } }  { "track": { … } }    // nested variants

Read the directory

The read endpoints include community stations ("source": "community", with tier and tier_name):

GET /api.php?action=stations

GET /api.php?action=now_playing&id={id}

GET /api.php?action=history&id={id}

GET /api.php?action=community_now_playing
curl "https://classicradio.network/api.php?action=community_now_playing"

{
  "success": true,
  "count": 1,
  "data": [
    {
      "id": "CS1A2B3C4D5E",
      "name": "Golden Oldies FM",
      "tier": 2,
      "tier_name": "Premium",
      "now_playing": {
        "artist": "The Platters", "title": "Only You", "text": "The Platters - Only You",
        "artwork": "", "updated_at": "2026-11-02T18:04:11+00:00"
      }
    }
  ]
}

Errors & limits

Every response includes success. Failed requests also return a machine-readable error and a readable message.

HTTPerrorMeaning
401unauthorizedUnknown station id or wrong key
403rejectedThe station wasn't accepted into the directory
405method_not_allowedThe endpoint needs POST
409already_listedThis stream is already submitted (the response includes its id)
422validation_failed / invalid_track / nothing_to_updateCheck validation.errors (or errors on update) or the message
429too_many_requestsRate limit hit. Wait and retry.