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.
Your tier is set automatically from what your stream provides, and it updates on its own. You never have to apply for it.
Requires a valid, playable stream
Your station also lists its currently playing track
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.
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.
Look up your station's review status, tier and current track using the ID and key you got when you submitted.
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.
POST ?action=validate. Nothing is saved, and you see your tier right away.POST ?action=submit. Store the returned id and station_key securely, because the key is shown once.POST ?action=update and the same key.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.
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.
| Field | Required | Notes |
|---|---|---|
stream_url | yes | Direct audio URL, or a .pls/.m3u that points to one. Must be reachable from the public internet. |
name | yes | 2–80 characters |
email | yes | Contact for the review. Never published. |
genre | no | Up to 60 characters |
description | no | Up to 500 characters, plain text |
logo | no | Square image, https:// recommended |
website, country, language | no | Shown on your listing |
np_method | no | auto (default) · json · push · none |
np_url | if json | Your 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"
}' 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.
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.
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.
| Field | Notes |
|---|---|
id | Required. Your station id. |
name | 2–80 characters |
genre | Up to 60 characters |
description | Up to 500 characters, plain text. Send "" to clear it. |
logo, website | Full http(s):// URLs, or "" to clear |
email | Contact for the review. Never published. |
country, language | Shown 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"
}' 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.
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 With np_method: "auto", we check these in parallel and keep the first one that returns a real track:
/status-json.xsl on your server, matched to your mount/stats?sid=1&json=1/7.htmlStreamTitle): most encoders (BUTT, Mixxx, RadioBOSS, SAM, Liquidsoap, AzuraCast) send this when "update metadata" is onWe 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
The read endpoints include community stations ("source": "community", with tier and tier_name):
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"
}
}
]
}
Every response includes success. Failed requests also return a machine-readable error and a readable message.
| HTTP | error | Meaning |
|---|---|---|
401 | unauthorized | Unknown station id or wrong key |
403 | rejected | The station wasn't accepted into the directory |
405 | method_not_allowed | The endpoint needs POST |
409 | already_listed | This stream is already submitted (the response includes its id) |
422 | validation_failed / invalid_track / nothing_to_update | Check validation.errors (or errors on update) or the message |
429 | too_many_requests | Rate limit hit. Wait and retry. |