Open Radio Player Developer API
Public JSON API docs for station health, stream telemetry, system status, and station catalog feeds for apps, dashboards, games, and AI agents.
Live API Status | OpenAPI Schema | LLM Guide | Power Docs API Section
Who This Helps
- Apps that need a smaller set of recent, reliable radio streams without running their own monitor.
- Games and companion experiences that want compatibility flags and low-latency stream selection.
- AI agents that need explicit filters, freshness timestamps, and a stable station catalog.
- Dashboards, scripts, and automations that need pipeline health plus stream-level telemetry.
Quick Start
- Use `/api/v1/stations/programming?ids=...&days=2` for one bounded genre-guide projection with schedule, Now/Next, and live-metadata availability.
- Start with `/api/v1/stations/healthy` when you want reliable defaults and smaller payloads.
- Use `/api/v1/stations/telemetry` when you want the full stream-variant feed, including at-risk rows.
- Check `/api/v1/system/status` before demos, cron jobs, or agent tasks that depend on fresh data.
- Join `/api/v1/stations/index.json` with live telemetry by `streamId` or `defaultStreamId` when you need both catalog metadata and health.
Integration Notes
- Authentication: None. The current public API is read-only and does not require an API key.
- CORS: JSON endpoints send `Access-Control-Allow-Origin: *` and allow `GET, OPTIONS`.
- Formats: API responses are UTF-8 JSON. The `/api` landing page is HTML and intended for humans.
- Freshness: Programming clients must check `sourceObservedAt`, `validFrom`, `validThrough`, `staleAt`, `availability`, and `reason`; CDN freshness never extends source validity.
- Cache behavior: Schedules cache for five minutes, Now/Next and batch for 30 seconds, approved live metadata for 15 seconds, and operational feeds for one hour. Responses include ETag and Last-Modified.
- Selection semantics: The healthy endpoint applies database-side health gates first, then compatibility filters, then trims to `limit`.
Key Resources
- Developer Docs: Search-friendly reference page for apps, games, AI agents, and integrations.
- Live API Status: Human-readable API landing page with live health checks and endpoint links.
- OpenAPI Schema: Machine-readable contract for the public JSON endpoints.
- LLM Guide: Compact endpoint and integration guide for agents and retrieval systems.
Programming guide projection
Path: /api/v1/stations/programming
Canonical provider projection containing factual schedule occurrences, schedule-derived Now/Next, live-metadata availability, provenance, and validity.
Method: GET | Auth: None | Cache: public, s-maxage=30, stale-while-revalidate=60
Ordering: Requested ID order, or canonical catalog order for a genre query.
Highlights
- Every item identifies `open-radio.programming.v1` and carries source observation, validity, staleness, availability, a machine reason, and source provenance.
- Unknown IDs return 404; known unsupported, unavailable, and stale stations remain HTTP 200 with empty factual data.
- Use `/api/v1/stations/{stationId}/schedule`, `/now-next`, or `/now-playing` for individual resources. Schedule requests allow at most 14 days.
Parameters
ids(string, default none): Comma-separated canonical station IDs or published aliases; maximum 50. Required when `genre` is absent.genre(string, default none): Exact canonical `genres[]` value. Required when `ids` is absent.from(date, default today): First projected calendar date in `YYYY-MM-DD` form.days(integer, default 2): Projected days, bounded to `1..7`.
Example Request
curl 'https://openradioplayer.com/api/v1/stations/programming?ids=kdfc,kusc,kdb&from=2026-07-19&days=2'
Response Excerpt
{
"schemaVersion": "open-radio.programming.v1",
"apiVersion": "v1",
"generatedAt": "2026-07-19T12:00:00.000Z",
"availability": "available",
"reason": "partial_or_complete_results_available",
"count": 3,
"items": [
{
"stationId": "kdfc",
"timeZone": "America/Los_Angeles",
"sourceObservedAt": "2026-07-19T08:21:00.000Z",
"validThrough": "2026-08-02T07:00:00.000Z",
"staleAt": "2026-08-02T07:00:00.000Z",
"availability": "available",
"reason": "ok",
"schedule": { "occurrences": [] },
"nowNext": { "current": null, "next": null },
"nowPlaying": {
"availability": "unsupported",
"reason": "approved_live_track_source_not_available",
"track": null
}
}
]
}
Healthy stations
Path: /api/v1/stations/healthy
Recommended starting endpoint for apps, games, AI agents, widgets, and external clients that want recent playable streams.
Method: GET | Auth: None | Cache: public, s-maxage=3600, stale-while-revalidate=21600
Ordering: Sorted by score desc, availabilityPercent desc, averageStartupLatencyMs asc, then streamId asc.
Highlights
- Echoes the effective filters in `filters` so downstream clients can log exactly what was applied.
- Each item includes `stationId`, `defaultStreamId`, `streamId`, scalar `genre`, richer `genres[]` / `countries[]` / `languages[]` / `programmingTags[]`, compatibility fields, recent health metrics, and optional `loudnessProfile` data.
- `count` reflects the final filtered set after compatibility checks and `limit` are applied.
Parameters
status(string, default healthy): Health band to query. Defaults to `healthy`.genre(string, default none): Optional scalar/back-compatible genre filter such as `jazz`, `indie`, `classical`, or a canonical value from `genres[]`.country(string, default none): Optional facet filter matched against `countries[]`.language(string, default none): Optional facet filter matched against `languages[]`.programmingTag(string, default none): Optional facet filter matched against `programmingTags[]`; `tag` is accepted as an alias.stationId(string, default none): Optional parent-station filter when you want all variants for one station.limit(integer, default 50): Maximum rows returned. Clamped to `1..200`.minScore(integer, default 86): Minimum health score required for a row to survive the first filter pass.maxStartupLatencyMs(integer, default 1500): Maximum acceptable average startup latency.minAvailabilityPercent(integer, default 80): Minimum rolling availability percentage.maxConsecutiveFailures(integer, default 0): Maximum allowed consecutive probe failures.maxAgeMinutes(integer, default 720): Rejects telemetry older than this threshold.streamType(string, default none): Compatibility filter such as `hls`, `mp3`, `aac`, or `direct`.streamTransport(string, default none): Transport filter: `direct` or `hls`.audioFormat(string, default none): Optional codec/container hint such as `mp3`, `aac`, `ogg`, `opus`, or `flac`.supportsNativeAudio(boolean, default none): Filter for clients that require direct/native browser audio playback.supportsHlsPlayback(boolean, default none): Filter for clients that specifically want HLS-capable rows.
Example Request
curl 'https://openradioplayer.com/api/v1/stations/healthy?genre=jazz&limit=12&minScore=90&maxStartupLatencyMs=1200&maxAgeMinutes=720&supportsNativeAudio=true'
Response Excerpt
{
"ok": true,
"generatedAt": "2026-03-21T19:11:52.654Z",
"telemetryGeneratedAt": "2026-03-21T19:02:19.000Z",
"filters": {
"status": "healthy",
"genre": "jazz",
"limit": 12,
"minScore": 90,
"maxStartupLatencyMs": 1200,
"maxAgeMinutes": 720,
"supportsNativeAudio": true
},
"count": 12,
"items": [
{
"streamId": "kcsm--hd2-aac",
"stationId": "kcsm",
"defaultStreamId": "kcsm",
"streamLabel": "HD2 AAC+",
"streamType": "aac",
"streamTransport": "direct",
"supportsNativeAudio": true,
"supportsHlsPlayback": false,
"status": "healthy",
"score": 100,
"availabilityPercent": 100,
"averageStartupLatencyMs": 1186
}
]
}
Telemetry feed
Path: /api/v1/stations/telemetry
Full stream-variant telemetry feed used by the site itself, including healthy and at-risk rows plus detailed timing and failure counters.
Method: GET | Auth: None | Cache: public, s-maxage=3600, stale-while-revalidate=21600
Ordering: Sorted by genre asc, stationName asc, then streamId asc.
Highlights
- Returns startup attempts, successes, failures, reconnect counts, buffering events, and recent failure timestamps.
- Useful for ranking, debugging, dashboards, or agent workflows that need the full health window instead of just healthy rows.
- If you only need a filtered shortlist, prefer `/api/v1/stations/healthy` to reduce payload size and client-side work.
Parameters
stationId(string, default none): Optional parent-station filter that returns all known variants for a station.
Example Request
curl 'https://openradioplayer.com/api/v1/stations/telemetry?stationId=krtu'
Response Excerpt
{
"ok": true,
"generatedAt": "2026-03-21T19:11:52.757Z",
"telemetryGeneratedAt": "2026-03-21T19:02:19.000Z",
"filters": {
"stationId": "krtu"
},
"count": 2,
"items": [
{
"streamId": "krtu--mp3",
"stationId": "krtu",
"streamLabel": "MP3 128",
"status": "healthy",
"score": 96,
"startupAttempts": 183,
"averageStartupLatencyMs": 161,
"playbackFailures": 0,
"reconnects": 0,
"lastFailureReason": null
}
]
}
System status
Path: /api/v1/system/status
Machine-readable heartbeat for the full monitor -> database -> production API pipeline.
Method: GET | Auth: None | Cache: public, s-maxage=3600, stale-while-revalidate=21600
Ordering: Not ordered. Single object response.
Highlights
- Includes freshness thresholds, summary metrics, named checks, and the canonical endpoint URLs.
- Returns HTTP 200 when healthy, HTTP 503 when the system is degraded but still able to report status, and HTTP 500 on unexpected failures.
- Useful as a lightweight readiness gate before agent jobs or external fetches.
No query parameters.
Example Request
curl 'https://openradioplayer.com/api/v1/system/status' | jq
Response Excerpt
{
"ok": true,
"status": "ok",
"generatedAt": "2026-03-21T19:11:53.374Z",
"thresholds": {
"freshTelemetryMinutes": 420,
"staleTelemetryMinutes": 720
},
"summary": {
"telemetryGeneratedAt": "2026-03-21T19:02:19.000Z",
"telemetryAgeMinutes": 9.6,
"healthyStations": 63,
"averageScore": 95
},
"checks": {
"database": {
"ok": true,
"level": "ok",
"detail": "Connected and queried successfully."
}
},
"endpoints": {
"landingPage": "https://openradioplayer.com/api",
"healthyStations": "https://openradioplayer.com/api/v1/stations/healthy"
}
}
Station catalog
Path: /api/v1/stations/index.json
Static station catalog published by the production site build, including per-station stream variants, city-level coordinates, and compatibility hints.
Method: GET | Auth: None | Cache: public, max-age=0, must-revalidate
Ordering: Build output order from the generated station catalog.
Highlights
- Includes `version`, `generatedAt`, `count`, `streamVariantCount`, and `items[]`.
- Each station row exposes scalar `genre` for compatibility plus `genres[]`, `countries[]`, `languages[]`, `programmingTags[]`, `city`, city-level `coordinates`, `defaultStreamId`, `streamCount`, `stream`, and `streams[]`.
- Join catalog rows with telemetry by `streamId` to combine stable metadata with live health observations.
No query parameters.
Example Request
curl 'https://openradioplayer.com/api/v1/stations/index.json' | jq '.items[0] | {id, genre, genres, countries, languages, defaultStreamId, streamCount}'
Response Excerpt
{
"version": 1,
"generatedAt": "2026-03-20T17:29:20.836Z",
"count": 99,
"streamVariantCount": 101,
"items": [
{
"id": "cfmz",
"stationId": "cfmz",
"stationName": "CFMZ 96.3 - Toronto, ON",
"genre": "classical",
"genres": ["classical"],
"countries": ["Canada"],
"languages": ["en"],
"programmingTags": ["classical"],
"city": "Toronto, ON",
"coordinates": {
"latitude": 43.6532,
"longitude": -79.3832,
"precision": "city",
"label": "Toronto, ON"
},
"defaultStreamId": "cfmz",
"streamCount": 1,
"streams": [
{
"id": "cfmz",
"label": "Primary",
"transport": "direct",
"supportsNativeAudio": true,
"supportsHlsPlayback": false
}
]
}
]
}
Error Model
- 200: Successful response.
- 400: Programming date, day count, or batch size is outside the documented bound.
- 404: Unknown canonical station ID or alias. Known unsupported stations remain HTTP 200 with explicit availability.
- 405: Method not allowed. Use `GET` for reads and `OPTIONS` for CORS preflight.
- 500: Unexpected server or data source failure.
- 503: The system status endpoint can return degraded-but-readable data with HTTP 503 when the pipeline is unhealthy.