API reference
Overview
The station’s API is free, read-only, unauthenticated JSON:API at https://radio.gridcoin.club/api. The Gridcoin chain is the archive: scores are compiled on demand from the frozen gridradio:v1 mapping, and the only things stored server-side are the likes and plays ledgers. Anything the web player does, you can do, but note that these endpoints carry the score and not the performance. A player built on them alone will get the right notes and a station that sounds nothing like this one; see the performance layer below.
Versioning
Every score carries a spec tag (currently gridradio:v1). Spec versions activate by block height, like the chain’s own consensus rules: /status publishes the era table, blocks compile under the spec of their era. If a v2 ever activates, v1 blocks keep returning v1 scores forever, so cached responses never go stale and conformant players never retune history.
Service status
/statusHealth + station context
Version, network, current chain tip, what’s on air, and how many listeners are tuned in. Always answers 200 while the service is up, with height: null if the wallet daemon is unreachable.
{"data": {"type": "status","attributes": {"name": "Grid Radio","version": "1.0.0","network": "mainnet","spec": "gridradio:v1","specEras": [{ "spec": "gridradio:v1", "fromHeight": 0 }],"height": 3265828,"onAirHeight": 3265828,"listeners": 12,"targetSpacingSeconds": 90}}}
Now playing
/airCurrent block, score, weather, tracklist
The current broadcast: the block on air, its full compiled score, the network weather (staking difficulty and net stake weight), and the recent tracklist. Answers 503 Dead Air until the station has heard its first block after boot.
curl https://radio.gridcoin.club/api/air
{"data": {"id": "3265828","type": "air","attributes": {"height": 3265828,"blockHash": "5a102a1e862c2e8b7f7f38a3ef92e54f…","time": 1787597120,"txCount": 2,"difficulty": 16.73,"likes": 4,"startedAt": "2026-08-24T18:45:40.112Z","score": {"spec": "gridradio:v1","hash": "5a102a1e862c2e8b7f7f38a3ef92e54f…","title": "The Veiled and Clear Lagoon Widens","key": "D#","keySemitone": 6,"mode": { "id": 0, "name": "major pentatonic", "mood": "clear" },"tempo": 82,"steps": 48,"melody": [{ "step": 0, "semitone": 9, "velocity": 0.74 },{ "step": 4, "semitone": 14, "velocity": 0.52 },"… 9 more notes"],"bass": ["…"],"bells": ["…"],"drone": { "detuneCents": 7, "fifth": false }},"weather": {"difficulty": 16.73,"netStakeWeight": 8690146724.1,"observedAt": "2026-08-24T18:47:21.601Z"},"recent": [{"height": 3265828,"title": "The Veiled and Clear Lagoon Widens","key": "D#","mode": "major pentatonic","mood": "clear","tempo": 82,"txCount": 2,"time": 1787597120,"blockHash": "5a102a1e…"},"… up to 11 more"]}}}
Replay the archive
/blocks/:idAny block, by height or hash
Every block since 2013, by height or 64-hex block hash: block metadata (time, transactions, difficulty, confirmations) plus its compiled score. Deeply buried blocks are served with long cache lifetimes. Blocks past the tip answer 404, because the chain has not written them yet.
curl https://radio.gridcoin.club/api/blocks/1000000
{"data": {"id": "1000000","type": "blocks","attributes": {"height": 1000000,"blockHash": "744e131b3712e70ecb03ba1af5c56d68…","time": 1503288895,"txCount": 2,"difficulty": 0.42,"confirmations": 2265847,"likes": 3,"score": {"spec": "gridradio:v1","hash": "744e131b3712e70ecb03ba1af5c56d68…","title": "Sleeping Meridian, Op. 214","key": "F#","keySemitone": 9,"mode": { "id": 5, "name": "dorian", "mood": "mist" },"tempo": 62,"steps": 32,"melody": [{ "step": 0, "semitone": 7, "velocity": 0.61 },"… more notes"],"bass": ["…"],"bells": ["…"],"drone": { "detuneCents": 5, "fifth": true }}}}}
Like a block
/blocks/:id/likesOne like per listener per block
/blocks/:id/likesChange your mind
One of the station’s two writes. There is no body and no auth; the block id (height or hash) is the whole request. One like per listener per block per day; a repeat answers 200 with counted: false rather than an error, because loving something twice is not a client mistake. DELETE takes a like back, but only one the server remembers you giving, so nobody can drain a block they did not love.
{"data": {"id": "744e131b3712e70ecb03ba1af5c56d68…","type": "likes","attributes": {"height": 1000000,"likes": 4,"counted": true}}}
Count a play
/blocks/:id/playsA block went on your air
The other write: the attendance ledger. If you build a player on this API, report each block as it starts and the archive remembers it was listened to. Same manners as the likes: no body, no auth, one counted play per listener per block per day, counted: false on repeats. The total comes back on GET /blocks/:id as plays. There is no DELETE; a tune that has been heard has been heard.
The charts
/chartsMost loved tunes, most likes first
Blocks ranked by likes, with their titles, keys and moods compiled from the hash on the way out. ?limit=N up to 100, default 25.
{"data": [{"id": "744e131b3712e70ecb03ba1af5c56d68…","type": "charts","attributes": {"blockHash": "744e131b3712e70ecb03ba1af5c56d68…","height": 1000000,"likes": 4,"title": "Sleeping Meridian, Op. 214","key": "F#","mode": "dorian","mood": "mist","tempo": 62}},{ "…": "one more" }]}
The performance layer
Every endpoint above returns the score: notes, key, mode, tempo, form, title. None of them tells you which instrument plays the melody, how the drone is built, or whether the block is performed outdoors. That is a second mapping, computed in the browser from the same 32-byte hash, and frozen exactly the way the score is.
It lives client-side because it costs nothing to send: the hash is already in the response, so the band is a pure function of data you have. A conformant player can reproduce it exactly, ignore it and pick its own instruments, or do something else entirely. The notes will still be right either way.
band 1 of 13 lead instruments, 1 of 4 basses,1 of 3 bell characters, 1 of 9 drone builds,and whether each is present at allarrangement whether the band walks on stage, and in what orderarp figure how a piano-arpeggio drone phrases itself: how fast,and how much of each chord window it fills before restingweather about 1 droned block in 5 swaps its drone for one of 82CC0 field recordings, at a level and a filter cutoff thehash picks; the cutoff reads as distanceeffects delay time and depth, reverb room sizesky colours, motion, 1-2 beat-synced visual effects,and the dial geometry
The sampled instruments are public-domain audio served as ordinary files, not through the API: /samples/<voice>/<note>.mp3 for notes, and /samples/field/<id>.mp3 for the field recordings. Fetch them or bring your own.
Both mappings freeze together. When a v2 activates, blocks from the v1 era keep their v1 score AND their v1 performance forever, which is what lets a cached response and a re-render years apart still agree.
Embed a tune
If all you want is a block’s tune on your own page, skip the API entirely: every block has an iframe-able mini player at /embed/:id, by height or hash. It performs the block right there with the same synth, instruments and sky, and it asks its visitors for nothing: no account, no cookies, no tracking beyond the station’s own anonymous counters.
<iframesrc="https://radio.gridcoin.club/embed/1000000"width="100%" height="152"style="border:0;border-radius:12px"title="Grid Radio"loading="lazy" allow="autoplay"></iframe>
Every block page also serves a share card at /og/block/:id: the tune’s title, sky and dial as a 1200×630 PNG, deterministic like everything else. Link a block anywhere that unfurls previews and the card comes along on its own.
Errors & limits
Errors follow JSON:API conventions: 404 for blocks that do not exist, 470 for malformed parameters, 503 for dead air (no block heard yet). Reads are rate-limited per IP as an abuse valve, not a budget; the limits are far above anything a player needs. Be a good listener: poll gently, and cache deeply buried /blocks responses, because they never change.
Made with ❤ by @gridcat · Part of Gridcoin Club ↗ · Testnet ↗