Tuneping API v1

Un API JSON pentru ce difuzează acum posturile de radio online și ce au difuzat. URL de bază https://api.tuneping.com/v1. Doar metadate: serviciul citește linia publică de titlu a fiecărui stream și nu atinge niciodată semnalul audio.

Beta publicăEndpoint-urile în timp real (căutare, batch, now playing pentru un post, directorul de posturi, consultarea pieselor și a artiștilor, căutarea în catalog, topurile, starea și statisticile) funcționează fără cheie, cu limite per adresă IP. Cheile se obțin în self-service pe pagina de înregistrare: Free imediat, Developer și Pro după Stripe Checkout. Business: scrieți la [email protected].

Pornire rapidă

Cine difuzează Madonna chiar acum?

curl "https://api.tuneping.com/v1/now-playing/search?q=madonna&limit=3"

Ce se difuzează acum pe un post al cărui id îl cunoașteți (id-urile sunt UUID-urile posturilor din Radio Browser):

curl "https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/now-playing"

Ce a difuzat un post în ultimele 24 de ore (necesită o cheie cu istoric, planul Developer sau superior):

curl -H "Authorization: Bearer tp_live_…" \
  "https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/history"

Fiecare răspuns este JSON. Erorile sunt application/problem+json (vedeți Erori). Parametrii de query necunoscuți sunt respinși cu 400 în loc să fie ignorați, astfel încât greșelile de tastare ies imediat la iveală.

Obținerea unei chei

În self-service pe tuneping.com/signup: introduceți o adresă de e-mail, deschideți în cel mult 60 de minute linkul primit prin e-mail, iar cheia este afișată o singură dată. Aceiași doi pași sunt disponibili și ca API: POST /v1/signup cu {"email": "…", "plan": "free|developer|pro"} răspunde cu 202 și trimite e-mailul; POST /v1/signup/verify cu {"token": "…"} din link răspunde cu 201 și cheia. Free nu necesită nimic altceva. Developer și Pro continuă spre Stripe Checkout prin linkul din răspuns; planul titularului se schimbă în cel mult un minut de la plată, iar toate cheile titularului îl urmează.

Ați pierdut o cheie? Înregistrați-vă din nou cu aceeași adresă: cheia este rotită, iar cea veche mai funcționează 24 de ore. O cheie live pe Free și Developer, cinci pe Pro, douăzeci pe Business; în plus, o cheie tp_test_ pe fiecare plan, emisă la cerere până la lansarea portalului.

Autentificare

Trimiteți cheia ca bearer token sau în X-Api-Key:

Authorization: Bearer tp_live_a1b2c3…
X-Api-Key: tp_live_a1b2c3…

Cheile încep cu tp_live_ sau tp_test_, urmat de 43 de caractere. O cerere fără cheie este tratată ca anonimă și limitată per adresă IP. O cheie malformată, necunoscută sau revocată primește imediat 401, chiar și pe endpoint-urile care ar fi funcționat anonim. Endpoint-urile marcate mai jos cu cheie răspund fără cheie cu 401 unauthenticated.

Scope-uri. Cheile au toate scope-urile (read, exports, webhooks, wishlists), cu excepția celor emise cu o listă mai restrânsă, de exemplu doar pentru citire; GET /v1/me listează scope-urile unei chei. Wishlist-urile și webhook-urile necesită scope-urile wishlists și webhooks (un wishlist cu livrare webhook le necesită pe amândouă, verificate înaintea planului). O cheie fără scope-ul necesar primește 403 forbidden_scope cu required_scope, iar WWW-Authenticate conține error="insufficient_scope".

Chei de test. O cheie tp_test_ citește aceleași date în timp real, cu limitele Free, și are propriile wishlist-uri, potriviri și endpoint-uri de webhook (livemode: false). Nu le vede și nu le modifică niciodată pe cele live, așa că o suită de teste poate face PUT cu un set gol de wishlist-uri fără să atingă producția. Webhook-urile în modul test pot indica doar spre webhook.site.

Reîncercări. POST, PATCH și DELETE pe endpoint-urile care necesită o cheie sau un token de partener acceptă un header Idempotency-Key (un UUID per cerere logică); endpoint-urile de înregistrare nu îl acceptă. O reîncercare cu aceeași cheie și aceeași cerere primește din nou primul răspuns reușit, marcat cu Idempotent-Replayed: true, în loc să creeze un al doilea wishlist sau endpoint. Cheia este reținută 24 de ore; refolosirea ei pentru o cerere diferită dă 422 idempotency_key_reused, iar o reîncercare în timp ce prima cerere încă rulează dă 409 idempotency_in_progress. Erorile nu sunt reținute, așa că o reîncercare după o eroare rulează din nou. O conexiune întreruptă nu anulează o cerere care are acest header: cererea se încheie și un răspuns reușit este reținut, astfel încât îl primiți la reîncercare. O cerere care încă rulează după 20 de secunde este anulată fără nicio modificare și răspunde cu 503 unavailable și Retry-After: 1; reîncercați cu aceeași cheie.

Aplicațiile partenere (în prezent Tapelark) trimit un entitlement token semnat în loc de o cheie: Authorization: Bearer TPE1-…. Tokenul este verificat offline cu cheile Ed25519 ale partenerului și corespunde planului de partener din tabelul de mai jos; API-ul nu află niciodată cine este utilizatorul final. Un token malformat sau necunoscut primește 401 token_invalid, unul expirat 401 token_expired (reîmprospătați-l), un token pentru altă audiență 401 token_audience, iar un entitlement expirat sau revocat 403 entitlement_revoked.

Limite de rată și cote

PlanRatăCotăId-uri per batchIstoric
Anonim (per IP)2 req/s, burst 51.000 / zi20fără
Free2 req/s, burst 51.000 / zi20fără
Developer10 req/s, burst 20100.000 / lună10010 zile
Pro50 req/s, burst 1001.000.000 / lună10030 de zile
Businesspersonalizatpersonalizat10030 de zile
Tapelark (entitlement token, per licență)5 req/s, burst 102.000 / zi507 zile

Limita pe secundă este un token bucket. Când acesta este gol, primiți 429 rate_limited cu Retry-After. Când cota este epuizată, primiți 429 quota_exceeded cu Retry-After setat la numărul de secunde până la resetarea ferestrei.

Răspunsurile autentificate care nu pot fi stocate într-un cache partajat conțin câmpurile IETF pentru limite de rată:

RateLimit-Policy: "burst";q=10;w=1, "monthly";q=100000;w=2592000
RateLimit: "burst";r=7;t=1

Răspunsurile care pot fi stocate într-un cache partajat (căutare, batch, now playing pentru un post, posturi, piese, artiști, căutare în catalog, topuri, statistici, serii de stare) nu conțin header-e de limite de rată, deoarece edge-ul poate servi același body altui apelant. Bugetul dumneavoastră rămas este disponibil oricând la GET /v1/me.

Caching

Răspunsurile în timp real pot fi păstrate în cache 5 secunde (Cache-Control: public, max-age=5, s-maxage=5) și au un ETag slab. Trimiteți If-None-Match pentru a primi 304 când nu s-a schimbat nimic; un 304 nu se scade din cotă la origine. Directorul de posturi se păstrează în cache 60 de secunde, un singur post 300, o piesă, un artist sau un top o oră, căutarea în catalog 10 minute. Istoricul, schimbările brute de titlu și difuzările unei piese sunt private, max-age=30, raportul de airplay private, max-age=3600. Endpoint-urile de stare și de chei sunt no-store.

Fiecare răspuns conține X-Request-Id. Menționați-l când scrieți la suport.

Erori

Erorile urmează formatul problem details din RFC 9457, cu doi membri de extensie: code, un șir stabil, ușor de procesat automat, și câmpuri suplimentare specifice endpoint-ului, precum errors[] la erorile de validare sau required_tier la 403.

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://tuneping.com/docs/errors/#validation_failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more query parameters are invalid.",
  "instance": "urn:tuneping:request:req_01a18837c239727fbacb40d6c488405a",
  "code": "validation_failed",
  "errors": [ { "field": "q", "code": "length", "message": "`q` is required and must be 2–120 characters." } ]
}

Lista completă a codurilor și semnificația lor se află în catalogul de erori. Cele pe care le veți întâlni primele:

HTTPcodeCând
400invalid_requestParametru de query necunoscut sau cerere malformată
400invalid_cursorCursor malformat, expirat (24 h; 30 de zile pentru un since_cursor din feed-ul de potriviri) sau folosit cu alte filtre
401unauthenticated · invalid_api_key · key_revokedCheie lipsă, necunoscută sau revocată
403forbidden_tierEndpoint-ul sau parametrul necesită un plan superior; required_tier indică care
403forbidden_scopeCheia a fost emisă fără acest scope; required_scope indică care
404not_found · station_opted_outId necunoscut sau postul a cerut să nu fie indexat
422validation_failedErori de câmp în errors[]
429rate_limited · quota_exceededÎncetiniți; Retry-After în secunde
503unavailableBaza de date este indisponibilă sau o cerere cu Idempotency-Key a fost anulată după 20 de secunde; reîncercați după Retry-After

GET/v1/now-playing/search fără cheie public, max-age=5 · ETag

Ce posturi difuzează q chiar acum. Căutare aproximativă (fuzzy), insensibilă la diacritice: gronemeyer manner găsește Grönemeyer – Männer. Rândurile sunt ordonate după similaritate, apoi după cât de recent a apărut titlul. Fără paginare: indexul se schimbă la câteva secunde, așa că paginile ar sări sau ar repeta rânduri. Măriți în schimb limit.

ParametruTipImplicitSemnificație
qșir, 2–120 caractereobligatoriuArtist, titlu sau ambele. De la trei caractere în sus se folosește potrivirea pe trigrame; interogările mai scurte caută un subșir.
countryISO 3166-1 alpha-2, repetabiloricareDoar posturi din aceste țări.
codecșiroricarede ex. MP3, AAC.
min_bitrateîntreg0kbit/s.
classificationenumsongsong, ident, programme, ad_break, empty, junk, unknown sau any.
max_age_seconds30–900180Doar rândurile al căror post a fost interogat cu succes în acest număr de secunde.
limit1–10020Rânduri returnate; truncated: true indică faptul că au existat mai multe.

Răspuns · 200 · application/json

{
  "data": [
    {
      "station": { "id": "159824f0-5475-4263-bcc8-59a48a213896", "name": "Radio 10 80's Hits", "country": "NL",
                   "codec": "MP3", "bitrate": 128, "is_hls": false,
                   "stream_url": "http://…/TLPSTR20.mp3", "homepage": "http://www.radio10.nl/", "recordability": "recordable" },
      "raw_title": "Madonna - Holiday",
      "artist": "Madonna", "title": "Holiday",
      "track": { "id": "trk_c6f", "mbid": "d4708b79-62e7-4ce4-a6ca-247237d4ef4e", "artist_id": "art_fn" },
      "classification": "song", "match_grade": "high", "confidence": "high",
      "started_at": "2026-10-29T15:08:44.883Z", "start_uncertainty_seconds": 32,
      "observed_at": "2026-10-29T15:09:26.631Z", "confirmed_at": "2026-10-29T15:10:30.679Z",
      "method": "icy", "poll_interval_seconds": 60, "similarity": 0.5
    }
  ],
  "truncated": false,
  "query": { "q": "madonna", "normalized": "madonna", "max_age_seconds": 180 }
}

Cum se citește un rând

started_at RFC 3339 · estimare
Începutul estimat al titlului curent, ± start_uncertainty_seconds în ambele direcții.
observed_at RFC 3339
Momentul în care Tuneping a văzut prima dată acest titlu.
confirmed_at RFC 3339
Ultima interogare reușită a postului: „încă se difuza la”. Folosiți-l pentru a evalua cât de actuale sunt datele.
track obiect · opțional
Prezent când titlul a fost legat de o înregistrare MusicBrainz.
similarity 0–1
Aplicați propriul prag.

GET/v1/now-playing fără cheie public, max-age=5 · ETag

Now playing în batch

Titlul curent pentru o listă de posturi într-un singur apel, pentru ecranele de favorite.

ParametruTipImplicitSemnificație
station_idsUUID-uri separate prin virgulăobligatoriuPână la 20 anonim sau pe Free, 100 de la Developer în sus. Id-urile sunt sortate și deduplicate înainte de caching, astfel încât doi clienți cu aceleași favorite împart un singur body din cache.
max_age_seconds30–86400900Rândurile mai vechi de această valoare sunt totuși returnate, dar marcate cu is_stale: true.
{ "data": [ { …same row shape as search, without similarity, plus "is_stale": false… } ],
  "missing": [ "ids that are unknown or opted out" ],
  "max_age_seconds": 900 }

GET/v1/stations/{id}/now-playing fără cheie

Now playing pentru un post

Un rând pentru un post. 404 not_found când încă nu a fost observat niciun titlu, 404 station_opted_out când postul a cerut să nu fie indexat.

GET/v1/stations GET/v1/stations/{id} fără cheie max-age=60 · 300

Posturi

Directorul, construit pe baza directorului Radio Browser, întreținut de comunitate, și completat cu modul în care Tuneping citește fiecare post.

ParametruTipImplicitSemnificație
qșir—Numele postului, căutare aproximativă.
countryalpha-2, repetabiloricare
tagșir, repetabiloricareToate tagurile date trebuie să se potrivească.
codec, min_bitrateCa la căutare.
has_now_playingbooleanfalseDoar posturile de la care Tuneping citește în prezent titluri.
sortenumclickcountclickcount (popularitate, descrescător), name, bitrate.
limit1–20050
cursorșir opac—Din next_cursor al paginii anterioare; valabil 24 de ore și doar cu aceleași filtre.
{ "data": [ {
    "id": "159824f0-5475-4263-bcc8-59a48a213896", "name": "Radio 10 80's Hits", "country": "NL", "state": null,
    "language": "dutch", "tags": ["80s", "hits"], "codec": "MP3", "bitrate": 128, "is_hls": false,
    "stream_url": "http://…", "homepage": "http://www.radio10.nl/", "favicon_url": "https://…/favicon.png",
    "clickcount": 1234, "recordability": "recordable",
    "metadata": { "method": "icy", "poll_interval_seconds": 60, "lag_seconds": 9, "last_ok_at": "2026-10-29T15:10:30Z", "coverage": "live" },
    "created_at": "2026-10-29T10:12:03Z", "updated_at": "2026-10-29T15:10:30Z" } ],
  "next_cursor": "eyJ…" }

favicon_url este transmis ca text, așa cum vine din director; Tuneping nu copiază și nu servește niciodată logo-uri.

GET/v1/stations/{id}/history cheie · developer + private, max-age=30

Istoricul difuzărilor

Melodiile difuzate de un post, de la cea mai nouă, cu început și sfârșit estimate. Adâncimea depinde de plan: 10 zile pe Developer, 30 de zile pe Pro și Business. Indexul este tânăr; pe măsură ce se colectează mai multe date, va fi oferit un istoric de peste 30 de zile. O cerere cu un from mai vechi primește 403 forbidden_tier cu required_tier, niciodată o fereastră scurtată fără avertisment.

ParametruTipImplicitSemnificație
from, toRFC 3339to = acum, from = to − 24 hCel mult 31 de zile per apel.
classificationenumsongCa la căutare.
limit1–20050
cursorșir opac—Fereastra este înghețată în cursor.
{ "data": [ {
    "id": "pe_…", "station": { …station ref… },
    "raw_title": "Hecht - Charlotta", "artist": "Hecht", "title": "Charlotta",
    "track": { "id": "trk_…", "mbid": "…", "artist_id": "art_…" },
    "classification": "song", "match_grade": "high",
    "started_at": "2026-10-29T10:31:12Z", "start_uncertainty_seconds": 10,
    "ended_at": "2026-10-29T10:34:40Z", "duration_seconds": 208,
    "method": "icecast_json", "confidence": "high" } ],
  "next_cursor": null,
  "window": { "from": "2026-10-28T15:00:00Z", "to": "2026-10-29T15:00:00Z" } }

GET/v1/stations/{id}/title-changes cheie · pro + private, max-age=30

Schimbări brute de titlu

Tot ce a citit Tuneping din linia de titlu a unui post, de la cele mai noi: melodii, identuri, junk împreună cu regula care l-a prins, observații empty și linii pe care normalizatorul nu le-a putut citi (classification: unknown). Folosiți-l pentru a depana metadatele unui post sau pentru a vedea ce a rămas în afara istoricului difuzărilor. Doar Pro și Business; cheile Free și Developer primesc 403 forbidden_tier înainte de orice altă verificare. Feed-ul brut acoperă ultimele 7 zile pe orice plan, așa că un from mai vechi primește 422 validation_failed cu codul de câmp out_of_range, nu 403.

ParametruTipImplicitSemnificație
from, toRFC 3339to = acum, from = to − 24 hCel mult 7 zile per apel, iar from nu mai vechi de 7 zile.
limit1–20050
cursorșir opac—Fereastra este înghețată în cursor. O singură interogare poate înregistra două observații în același moment, așa că paginile sunt ordonate după observed_at și un id intern; nicio observație nu este sărită sau repetată.
curl -H "Authorization: Bearer tp_live_…" \
  "https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/title-changes?limit=50"
{ "data": [
    { "observed_at": "2026-11-03T09:12:44Z", "raw_title": "Madonna - Holiday", "classification": "song",
      "method": "icy", "artist": "Madonna", "title": "Holiday", "junk_rule": null },
    { "observed_at": "2026-11-03T09:12:14Z", "raw_title": "www.radio10.nl", "classification": "junk",
      "method": "icy", "artist": null, "title": null, "junk_rule": "url" },
    { "observed_at": "2026-11-03T09:08:02Z", "raw_title": null, "classification": "empty",
      "method": "icy", "artist": null, "title": null, "junk_rule": null } ],
  "next_cursor": "eyJ…",
  "window": { "from": "2026-11-02T09:20:00Z", "to": "2026-11-03T09:20:00Z" } }

observed_at este interogarea care a văzut prima dată linia. raw_title este linia așa cum a trimis-o stream-ul, null pentru observațiile empty și pentru liniile ilizibile. artist și title sunt perechea normalizată când linia a fost interpretată ca melodie, iar junk_rule numește regula doar pentru junk. Rândurile sunt observații, nu difuzări: nu au id, legătură cu o piesă sau oră de sfârșit. Posturile necunoscute și cele care au cerut să nu fie indexate răspund cu 404 și no-store.

GET/v1/tracks/{id} GET/v1/artists/{id} fără cheie public, max-age=5 · s-maxage=3600 · ETag

Piese și artiști

Fiecare titlu pe care indexul îl leagă de o înregistrare MusicBrainz primește un id de piesă (trk_…) și, când înregistrarea are unul, un id de artist (art_…). Ele apar ca track în rândurile în timp real, în istoric, în potriviri și în evenimente și sunt ceea ce acceptă wishlist-urile și filtrele de webhook. Aceste două endpoint-uri transformă un id în înregistrarea din spatele lui. Id-urile sunt citite fără a ține cont de majuscule; un id necunoscut primește 404 not_found, păstrat în cache 60 de secunde.

{ "id": "trk_c6f", "title": "Holiday",
  "artist": { "id": "art_fn", "name": "Madonna" }, "artist_credit": "Madonna",
  "mbid": "d4708b79-62e7-4ce4-a6ca-247237d4ef4e", "isrc": [], "duration_ms": null, "first_release_year": null,
  "first_seen_at": "2026-10-12T06:41:03Z", "last_seen_at": "2026-11-03T09:12:44Z",
  "station_count_7d": 23, "seen_on_radio": true }

artist este null când înregistrarea nu are un rând de artist legat; artist_credit conține întotdeauna textul creditului. first_seen_at, last_seen_at și station_count_7d numără detecțiile pe orice post, inclusiv difuzările în curs. isrc, duration_ms și first_release_year (și sort_name la artiști) nu sunt încă completate și sunt returnate goale sau null.

{ "id": "art_fn", "name": "Madonna", "sort_name": null, "mbid": "79239441-bfd5-4981-a70c-55c3f15c1287",
  "track_count": 61, "seen_on_radio": true }

GET/v1/tracks/{id}/plays cheie · developer + private, max-age=30

Difuzările unei piese

Unde și când a fost difuzată o piesă, pe toate posturile, de la cea mai nouă difuzare. Rândurile au aceeași formă ca în istoricul difuzărilor și aceeași regulă de adâncime: 10 zile pe Developer, 30 de zile pe Pro și Business, iar un from mai vechi primește 403 forbidden_tier cu required_tier. O difuzare încă în curs pe un post este inclusă cu ended_at: null.

ParametruTipImplicitSemnificație
from, toRFC 3339to = acum, from = to − 24 hCel mult 31 de zile per apel. Ambele trebuie să fie între 2000 și 2100, altfel 422 validation_failed cu codul de câmp out_of_range.
countryalpha-2, repetabiloricareDoar difuzări pe posturi din aceste țări.
station_idsUUID-uri separate prin virgulăoricareCel mult 100.
limit1–20050
cursorșir opac—Fereastra și filtrele sunt înghețate în cursor. Două posturi sunt adesea interogate în același tick, așa că paginile sunt ordonate după started_at și post împreună; nicio difuzare nu este sărită sau repetată.
curl -H "Authorization: Bearer tp_live_…" \
  "https://api.tuneping.com/v1/tracks/trk_c6f/plays?country=NL&limit=50"
{ "data": [ {
    "id": "pe_…", "station": { …station ref… },
    "raw_title": "Madonna - Holiday", "artist": "Madonna", "title": "Holiday",
    "track": { "id": "trk_c6f", "mbid": "d4708b79-62e7-4ce4-a6ca-247237d4ef4e", "artist_id": "art_fn" },
    "classification": "song", "match_grade": "high",
    "started_at": "2026-11-03T09:12:44Z", "start_uncertainty_seconds": 30,
    "ended_at": null, "duration_seconds": null,
    "method": "icy", "confidence": "high" } ],
  "next_cursor": "eyJ…",
  "window": { "from": "2026-11-02T09:20:00Z", "to": "2026-11-03T09:20:00Z" } }

GET/v1/artists/{id}/airplay cheie · pro + private, max-age=3600

Raport de airplay

Cât de des au fost difuzate piesele unui artist, grupate pe post, țară sau zi. Doar Pro și Business; o cheie Developer primește 403 forbidden_tier. Raportul numără difuzările încheiate pe posturile vizibile, este calculat la cerere și apoi păstrat o oră, așa că o difuzare tocmai încheiată apare în decurs de o oră. Valorile sunt detecții din metadate, pe baza titlurilor anunțate de posturi, nu airplay identificat prin fingerprinting; răspunsul precizează acest lucru în note.

ParametruTipImplicitSemnificație
group_byenumobligatoriustation, country sau day.
from, toRFC 3339to = ora curentă, from = to − 30 de zilePână la adâncimea istoricului din plan, 30 de zile; un from mai vechi primește 403 forbidden_tier. Fereastra este aliniată la zile UTC întregi pentru day și la minute întregi în celelalte cazuri, și este returnată în răspuns.
countryalpha-2, repetabiloricareZZ selectează posturile fără țară; și în date apar cu cheia ZZ.
limit1–1000100Grupuri returnate. Nu există pagină următoare: mai multe grupuri decât limit primesc 422 validation_failed cu o intrare în errors[] al cărei code este too_many_groups (câmpul limit). Restrângeți fereastra, filtrați după țară, schimbați group_by sau măriți limit.
curl -H "Authorization: Bearer tp_live_…" \
  "https://api.tuneping.com/v1/artists/art_fn/airplay?group_by=country&from=2026-10-04T00:00:00Z"
{ "artist": { "id": "art_fn", "name": "Madonna", "sort_name": null, "mbid": "79239441-bfd5-4981-a70c-55c3f15c1287",
              "track_count": 61, "seen_on_radio": true },
  "from": "2026-10-04T00:00:00Z", "to": "2026-11-03T09:00:00Z", "group_by": "country",
  "data": [ { "key": "NL", "plays": 412, "stations": 37, "distinct_tracks": 9 },
            { "key": "DE", "plays": 58, "stations": 12, "distinct_tracks": 4 } ],
  "generated_at": "2026-11-03T09:00:00Z",
  "note": "Counts are metadata detections from station-announced titles, not fingerprinted airplay." }

key este UUID-ul postului, codul țării sau ziua în formatul YYYY-MM-DD; la gruparea pe post, label conține numele postului.

GET/v1/search/tracks GET/v1/search/artists fără cheie public, max-age=5 · s-maxage=600 · ETag

Găsiți id-ul unei piese sau al unui artist după nume, pentru a-l consulta, a-i lista difuzările sau a-l adăuga într-un wishlist. Căutarea în timp real răspunde la întrebarea ce se difuzează acum; aceasta caută în catalogul de piese și artiști pe care indexul i-a legat de MusicBrainz. Potrivirea se face prin similaritate pe trigrame asupra numelui normalizat: accentele și umlauturile sunt eliminate, majusculele sunt ignorate, iar query.normalized returnează forma căutată. Deocamdată catalogul conține doar ce s-a auzit la radio, așa că seen_on_radio este aproape întotdeauna true; catalogul MusicBrainz mai larg nu poate fi încă căutat.

ParametruTipImplicitSemnificație
qșir, 2–120 caractereobligatoriuTitlu (piese) sau nume (artiști). După normalizare trebuie să rămână cel puțin un cuvânt de două litere sau cifre, altfel 422 validation_failed cu codul de câmp length.
artistșir, 2–120 caractere—Doar pentru piese: păstrează piesele al căror nume de artist se potrivește; aceeași regulă ca la q.
limit1–10020
cursorșir opac—Din next_cursor; continuă același clasament (similaritate, apoi id) și doar cu aceleași q și artist.
curl "https://api.tuneping.com/v1/search/tracks?q=holiday&artist=madonna"
{ "data": [ { "id": "trk_c6f", "title": "Holiday", "artist": { "id": "art_fn", "name": "Madonna" }, "artist_credit": "Madonna",
              "mbid": "d4708b79-62e7-4ce4-a6ca-247237d4ef4e", "seen_on_radio": true } ],
  "next_cursor": null,
  "query": { "q": "holiday", "normalized": "holiday" } }

Rezultatele pentru piese sunt sumare: fără statistici de difuzare; pentru acestea, apelați /v1/tracks/{id}. Rezultatele pentru artiști sunt obiectul artist complet.

GET/v1/charts/tracks GET/v1/charts/artists fără cheie public, max-age=5 · s-maxage=3600 · ETag

Topuri

Piesele și artiștii cei mai difuzați din ultimele 7 sau 30 de zile, pe toate posturile sau într-o singură țară. Topurile sunt calculate în fiecare noapte din difuzările încheiate pe posturile vizibile, așa că acoperă zile UTC întregi și se încheie de regulă cu ziua de ieri; difuzările de ieri sunt adăugate de agregarea de la 03:50 UTC. Citiți fereastra din răspuns, nu din propriul ceas: după o noapte în care agregarea a eșuat, fereastra se încheie cu o zi mai devreme, iar înainte de prima agregare data este gol. Fiecare top este calculat o dată pe oră. Valorile sunt detecții din metadate, pe baza titlurilor anunțate de posturi, nu airplay certificat.

ParametruTipImplicitSemnificație
days7 sau 3030Zile UTC complete din fereastră.
countryalpha-2, un singur codoricareDoar difuzări pe posturi din această țară. Un cod fără niciun post activ primește un top gol, nu o eroare.
limit1–500100Intrări returnate. Nu există pagină următoare; 500 este plafonul.
curl "https://api.tuneping.com/v1/charts/tracks?days=7&country=NL&limit=50"
{ "window": { "from": "2026-10-27", "to": "2026-11-03" }, "days": 7, "country": "NL",
  "data": [ { "rank": 1,
              "track": { "id": "trk_c6f", "title": "Holiday", "artist": { "id": "art_fn", "name": "Madonna" },
                         "artist_credit": "Madonna", "mbid": "d4708b79-62e7-4ce4-a6ca-247237d4ef4e" },
              "plays": 412, "stations": 37, "countries": [ { "country": "NL", "plays": 412 } ] } ],
  "generated_at": "2026-11-03T09:00:00Z" }

window.from este inclusiv, iar window.to exclusiv, ambele fiind date UTC. Intrările sunt ordonate după difuzări, apoi după posturi, apoi după id, iar rank nu are goluri. stations numără posturile distincte. countries listează primele trei țări după numărul de difuzări (cu country, doar țara respectivă); posturile fără țară sunt numărate în plays și stations, dar nu apar niciodată acolo. generated_at arată când a fost calculat clasamentul din această oră. Nu face parte din ETag, așa că If-None-Match continuă să primească 304 pe tot parcursul orei.

Topul artiștilor însumează piesele legate ale unui artist și conține distinct_tracks; piesele fără artist legat nu sunt numărate.

{ "rank": 1, "artist": { "id": "art_fn", "name": "Madonna", "mbid": "79239441-bfd5-4981-a70c-55c3f15c1287" },
  "plays": 470, "stations": 41, "distinct_tracks": 9,
  "countries": [ { "country": "NL", "plays": 412 }, { "country": "DE", "plays": 58 } ] }

GET/v1/me GET/v1/me/usage cheie no-store

Chei și consum

/v1/me returnează cheia, planul și starea titularului ei, limitele care se aplică și consumul de până acum din fereastra curentă. /v1/me/usage?from=&to= returnează cererile pe zi (doar cererile ajunse la origine; răspunsurile servite din cache-ul edge nu sunt numărate).

{ "key":   { "id": "key_…", "hint": "tp_live_a1b2c3…", "scopes": ["read", "exports", "webhooks", "wishlists"], "livemode": true,
             "created_at": "…", "last_used_at": "…", "expires_at": null },
  "owner": { "id": "own_…", "tier": "developer", "status": "active",
             "current_period_start": "…", "current_period_end": "…" },
  "limits": { "requests_per_second": 10, "burst": 20, "requests_per_day": null, "requests_per_month": 100000,
              "history_days": 10, "batch_station_ids": 100, "webhooks": 0, "wishlists": 10,
              "sse_connections": 1, "tightened_stations": 200 },
  "usage":  { "period_requests": 41230, "today_requests": 1810, "resets_at": "…" } }

POST/v1/me/keys/{id}/rotate cheie no-store

Rotirea cheilor

Înlocuiți o cheie fără întreruperi. Răspunsul este o cheie nouă cu același titular, aceleași scope-uri, același nume și același mod; secret-ul ei este afișat o singură dată. Cheia veche mai funcționează 24 de ore. previous_key.expires_at arată până când, la fel ca key.expires_at din /v1/me când îl apelați cu cheia veche (este null pentru o cheie care nu a fost rotită niciodată). Din acel moment, cheia veche primește 401 key_revoked, cu revoked_at setat la expires_at-ul ei.

curl -X POST -H "Authorization: Bearer tp_live_…" -H "Idempotency-Key: $(uuidgen)" \
  https://api.tuneping.com/v1/me/keys/key_3f/rotate
{ "id": "key_3g", "hint": "tp_live_e5f6g7h8", "secret": "tp_live_…",
  "scopes": ["read", "exports", "webhooks", "wishlists"], "livemode": true, "created_at": "2026-11-03T09:20:00Z",
  "previous_key": { "id": "key_3f", "hint": "tp_live_a1b2c3d4", "expires_at": "2026-11-04T09:20:00Z" } }

Trimiteți un Idempotency-Key: astfel, o reîncercare primește înapoi același secret în loc de o a doua cheie nouă. Regulile:

  • Puteți roti cheia cu care faceți apelul sau o altă cheie a aceluiași titular și din același mod (cheile tp_live_ rotesc chei live, cheile tp_test_ chei de test), ale cărei scope-uri le are și cheia apelantă.
  • O cheie se rotește o singură dată prin API. Pentru a o roti din nou, rotiți-i succesoarea.
  • Cheia altui titular, o cheie din celălalt mod, o cheie revocată, expirată sau deja rotită și o cheie cu scope-uri pe care cheia apelantă nu le are primesc toate același 404 not_found.
  • O cheie apelantă care este ea însăși în curs de înlocuire prin rotire primește 409 conflict; apelați cu succesoarea ei.
  • Entitlement token-urile partenerilor nu au o cheie de rotit și primesc 403 forbidden_tier.

GET/v1/health GET/v1/health/series GET/v1/stats fără cheie

Stare și statistici

/v1/health răspunde cu 200 și status ok sau degraded, respectiv cu 503 când baza de date nu funcționează; îndreptați monitorul de uptime spre el. /v1/health/series?minutes=60 (5–1440) returnează valorile poller-ului pe minut: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds și o defalcare by_method. /v1/stats este instantaneul public al acoperirii afișat pe pagina de stare: totalul posturilor, posturi interogate, posturi cu metadate în timp real, schimbări de titlu pe minut, ponderea melodiilor, pe metodă, pe țară.

POST/v1/wishlists PUT/v1/wishlists GET/v1/wishlists/matches cheie · developer + no-store

Wishlist-uri

Indicați o melodie, un artist sau un titlu în text liber și aflați când un post începe să îl difuzeze. Fiecare schimbare de titlu este comparată cu wishlist-urile dumneavoastră chiar în momentul citirii, iar un wishlist nou devine activ în cel mult 30 de secunde. Developer păstrează 10 wishlist-uri, Pro 100, Business 1.000. Potrivirile sunt păstrate 30 de zile.

CâmpTipSemnificație
kindartist · track · queryartist și track primesc un id din căutare (art_…, trk_…) și prind și variantele de scriere pe care normalizatorul le leagă de el. query primește text liber, de regulă Artist - Title.
valueșir, ≤ 240Id-ul sau textul.
matchexact · fuzzyDoar pentru query. Exact compară textul normalizat; fuzzy acceptă o similaritate pe trigrame de 0,6 sau mai mare.
station_filterobiectstation_ids (≤ 500) și/sau countries (alpha-2 cu litere mari sau mici, returnate cu litere mari). Un titlu se potrivește când postul său este listat sau se află într-o țară listată. Fără filtru înseamnă toate posturile.
deliveryarraypoll (implicit), sse, webhook. Livrarea prin webhook este disponibilă de la Pro în sus și necesită webhook_id și o cheie cu scope-ul webhooks.
descriptionșir, ≤ 200Pentru uz propriu.
curl -H "Authorization: Bearer tp_live_…" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"kind":"query","value":"Daft Punk - One More Time","station_filter":{"countries":["FR","BE"]}}' \
  https://api.tuneping.com/v1/wishlists

O listă explicită station_ids face ca Tuneping să citească acele posturi la fiecare 30 de secunde în loc de 60 cât timp wishlist-ul este activ, iar răspunsul indică acest lucru cu tightens: true. Reuniunea posturilor listate are un plafon per plan (limits.tightened_stations în /v1/me); un wishlist care l-ar depăși primește 422 validation_failed cu o intrare în errors[] al cărei code este tightened_stations_exceeded (câmpul station_filter.station_ids sau data la PUT). Filtrele pe țară funcționează în continuare, la intervalul normal al fiecărui post.

GET /v1/wishlists/{id}, PATCH (filtru de posturi, match, livrare, webhook, descriere, status: enabled | paused) și DELETE funcționează cum vă așteptați. PUT /v1/wishlists cu {"data": [ … ]} înlocuiește întregul set într-un singur apel: elementele neschimbate își păstrează id-ul și numărul de potriviri, celelalte sunt create sau șterse. Este cea mai simplă cale de a sincroniza o listă pe care aplicația o păstrează local. Body-ul acestei cereri poate avea până la 1 MiB; celelalte body-uri de cerere sunt limitate la 64 KiB (413 payload_too_large).

Potrivirile sunt un feed de tip tail. GET /v1/wishlists/matches acoperă toate wishlist-urile, iar GET /v1/wishlists/{id}/matches unul singur. Rândurile vin în ordine, de la cel mai vechi. Păstrați next_cursor din fiecare răspuns și trimiteți-l înapoi ca since_cursor: nu este niciodată null, rămâne valabil pe toată perioada de retenție de 30 de zile și supraviețuiește repornirilor. Fără cursor primiți cele mai noi potriviri. limit este 1–500, implicit 100.

{ "data": [ {
    "id": "wm_…", "wishlist_id": "wl_…",
    "station": { "id": "…", "name": "…", "country": "FR", "codec": "MP3", "bitrate": 128, "is_hls": false,
                 "stream_url": "https://…", "homepage": "https://…", "recordability": "recordable" },
    "raw_title": "Daft Punk - One More Time", "track": { "id": "trk_…", "mbid": "…", "artist_id": "art_…" },
    "started_at": "2026-10-30T21:04:10Z", "start_uncertainty_seconds": 10,
    "observed_at": "2026-10-30T21:04:20Z", "matched_at": "2026-10-30T21:04:20Z",
    "confidence": "high", "expected_end_hint_at": "2026-10-30T21:07:52Z" } ],
  "next_cursor": "eyJ0Ijo…" }

started_at este deja corectat în funcție de momentul în care a fost văzut titlul, așa că un recorder poate afișa „a început acum aproximativ N ± U secunde”. expected_end_hint_at este started_at plus 222 de secunde, durata mediană a unei melodii: un indiciu, nu o predicție. confidence este high când potrivirea s-a făcut printr-un id de piesă sau de artist legat, medium pentru artistul și titlul normalizate, low pentru interogările text.

POST/v1/webhooks GET/v1/webhooks/{id}/deliveries cheie · pro + no-store

Webhook-uri

Tuneping trimite evenimente către endpoint-ul HTTPS indicat, pe măsură ce au loc. Pro are 5 endpoint-uri, Business 20.

EvenimentCândNecesar pe Pro
wishlist.matchedUn wishlist care indică acest endpoint în webhook_id a avut o potrivire.—
station.title_changedLinia de titlu a unui post s-a schimbat.filters.station_ids (≤ 200)
track.startedA început difuzarea unei piese legate.filters.track_ids sau artist_ids (≤ 200)
artist.startedA început difuzarea unui artist legat.filters.track_ids sau artist_ids (≤ 200)

Filtrele pot lista și countries și classification. Filtrele doar pe țară și stream-urile nefiltrate de schimbări de titlu sunt disponibile pe Business. Fiecare endpoint are un buget zilnic de 50.000 de evenimente pe Pro și 500.000 pe Business; peste acest buget, endpoint-ul trece în starea paused_budget până în următoarea zi UTC și primiți un e-mail.

curl -H "Authorization: Bearer tp_live_…" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/tuneping","events":["wishlist.matched"]}' \
  https://api.tuneping.com/v1/webhooks

Răspunsul conține o singură dată secret-ul de semnare (whsec_…). Livrările urmează Standard Webhooks: header-ele webhook-id, webhook-timestamp și webhook-signature: v1,<base64>, unde semnătura este HMAC-SHA256 cu octeții secretului peste {webhook-id}.{webhook-timestamp}.{body}. Respingeți timestamp-urile decalate cu mai mult de 5 minute și folosiți webhook-id pentru a elimina duplicatele: rămâne același la reîncercări.

{ "id": "evt_…", "type": "wishlist.matched", "created_at": "2026-10-30T21:04:20Z",
  "data": { "station_id": "…", "station": { …station ref… }, "wishlist_id": "wl_…", "match_id": "wm_…",
            "raw_title": "Daft Punk - One More Time", "artist": "Daft Punk", "title": "One More Time",
            "track_id": "trk_…", "artist_id": "art_…", "classification": "song",
            "started_at": "…", "start_uncertainty_seconds": 10, "observed_at": "…",
            "confidence": "high", "expected_end_hint_at": "…" },
  "links": { "station": "/v1/stations/…", "wishlist": "/v1/wishlists/wl_…" } }

Răspundeți cu orice 2xx în cel mult 10 secunde. Redirecționările nu sunt urmate. Livrările eșuate sunt reîncercate după 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h și 24 h, aproximativ trei zile în total, câte una pe rând pentru fiecare endpoint. Un endpoint care răspunde cu 410 sau eșuează cinci zile la rând este dezactivat și primiți un e-mail; după ce l-ați reparat, setați status înapoi la enabled cu PATCH.

POST /v1/webhooks/{id}/rotate-secret returnează un secret nou; timp de 24 de ore, fiecare livrare are două semnături, câte una pentru fiecare secret. POST /v1/webhooks/{id}/test trimite un ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed listează livrările din ultimele 30 de zile, iar POST …/deliveries/{dl_id}/redeliver retrimite una dintre ele. Cheile de test (tp_test_…) pot indica doar spre webhook.site.

GET/v1/stream cheie · developer + text/event-stream

Stream de evenimente

Aceleași evenimente printr-un singur răspuns HTTP de lungă durată, în formatul server-sent events, pentru servere și aplicații native. Transmiteți fie station_ids (până la 100), fie wishlist_id (all sau un id wl_…).

curl -N -H "Authorization: Bearer tp_live_…" -H "Accept: text/event-stream" \
  "https://api.tuneping.com/v1/stream?station_ids=159824f0-5475-4263-bcc8-59a48a213896"
retry: 3000

id: evt_1m675e1tkfdxtn7axmq6qktz8
event: station.title_changed
data: {"id":"evt_1m675e1tkfdxtn7axmq6qktz8","type":"station.title_changed","created_at":"…","data":{…}}

: ping
ParametruImplicitSemnificație
station_ids—Id-uri de posturi separate prin virgulă, cel mult 100.
wishlist_id—all sau un id de wishlist. Trimite wishlist.matched.
eventsstation.title_changedPentru stream-urile de posturi, oricare dintre station.title_changed, track.started, artist.started.
classificationsongsong sau any, pentru stream-urile de posturi.

data este același JSON pe care îl primește un webhook. Un comentariu : ping sosește după 20 de secunde fără evenimente, așa că setați timeout-ul de inactivitate al clientului la 60 de secunde sau mai mult. Când conexiunea cade, reconectați-vă cu header-ul Last-Event-ID setat la ultimul id primit: evenimentele din ultimele 10 minute sunt retransmise. O poziție mai veche primește un event: gap cu cel mai vechi id disponibil; completați golul din istoric sau din feed-ul de potriviri. Stream-uri deschise per cheie: Developer 1, Pro 5, Business 20, Tapelark 1 per dispozitiv. Încă un stream primește 429 too_many_connections.

Browserele nu pot folosi EventSource aici, deoarece acesta nu poate trimite header-ul Authorization, iar cheile nu sunt acceptate niciodată în URL. Citiți în schimb stream-ul cu fetch().

Câmpuri și enum-uri

classification enum
song · ident (numele sau sloganul postului) · programme · ad_break · empty · junk · unknown. Doar melodiile intră în istoricul difuzărilor; în rândul în timp real se păstrează totul.
match_grade enum
high · medium · none. Cât de bine a fost legat titlul de o înregistrare MusicBrainz.
confidence enum
high · medium · low. Calculat pentru fiecare rând pe baza clasificării, a gradului de potrivire, a cât de recent a fost confirmat postul și a istoricului de fiabilitate al postului. Documentat ca să îl puteți verifica.
method enum
Modul în care Tuneping citește postul: icy (metadate în stream), icecast_json (JSON de stare al serverului), shoutcast_stats (XML de stare), shoutcast_currentsong, seven_html. Identificatori interni, nu recomandări; dacă îi afișați, prezentați-i ca „în stream” și „pagină de stare”.
recordability enum
Dacă stream-ul postului este un stream HTTP simplu, care poate fi înregistrat, din perspectiva unui client de înregistrare. Informativ.
Id-uri
Posturile folosesc UUID-ul lor din Radio Browser. Piesele sunt trk_…, artiștii art_…, evenimentele de difuzare pe_…, cheile key_…, titularii own_…, wishlist-urile wl_…, potrivirile wm_…, webhook-urile wh_…, livrările dl_…, evenimentele evt_…. Tratați-le pe toate ca șiruri opace.
Marcaje de timp
RFC 3339, UTC, cu precizie de milisecundă acolo unde sursa o oferă.

OpenAPI

Descrierea acestui API într-un format citibil de mașină se află la /docs/openapi-v1.yaml (OpenAPI 3.1). Este sursa pentru generarea clienților; operation id-urile sunt stabile.

Pentru agenții AI, același API este disponibil ca server MCP (în limba engleză).

Atribuire și utilizare corectă

Utilizarea pe planul Free și cea anonimă afișează un link vizibil „Data by Tuneping” acolo unde sunt afișate datele (badge). Difuzările sunt detectate din metadatele posturilor și nu reprezintă o monitorizare certificată; scrieți „detectat din metadatele postului” acolo unde le prezentați ca dovadă. Numele posturilor sunt text; Tuneping nu furnizează logo-uri sau grafică. Consultați termenii.