API Tuneping v1

Une API JSON pour ce que les stations de radio en ligne diffusent en ce moment, et ce qu’elles ont diffusé. URL de base https://api.tuneping.com/v1. Uniquement des métadonnées: le service lit la ligne de titre publique de chaque flux et ne touche jamais à l’audio.

Bêta publiqueLes endpoints en direct (recherche, lot, titre en cours d’une station, annuaire des stations, santé et statistiques) fonctionnent sans clé, avec une limite par adresse IP. Les clés sont en libre-service sur la page d’inscription : Free immédiatement, Developer et Pro après Stripe Checkout. Business : écrivez à [email protected].

Démarrage rapide

Qui passe Madonna en ce moment ?

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

Le titre en cours sur une station dont vous connaissez l’identifiant (les identifiants sont les UUID de station de Radio Browser) :

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

Ce qu’une station a diffusé au cours des dernières 24 heures (exige une clé avec historique, offre Developer ou supérieure) :

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

Chaque réponse est en JSON. Les erreurs sont en application/problem+json (voir Erreurs). Les paramètres de requête inconnus sont rejetés avec un 400 au lieu d’être ignorés, pour que les fautes de frappe se voient tout de suite.

Obtenir une clé

En libre-service sur tuneping.com/signup : saisissez une adresse e-mail, ouvrez le lien reçu dans les 60 minutes, et la clé s’affiche une seule fois. Ces deux étapes existent aussi sous forme d’API : POST /v1/signup avec {"email": "…", "plan": "free|developer|pro"} répond 202 et envoie l’e-mail ; POST /v1/signup/verify avec le {"token": "…"} du lien répond 201 avec la clé. Free n’exige rien de plus. Developer et Pro continuent vers Stripe Checkout depuis le lien de la réponse ; l’offre du titulaire change dans la minute qui suit le paiement et toutes ses clés suivent.

Clé perdue ? Inscrivez-vous à nouveau avec la même adresse : la clé est renouvelée, l’ancienne continue de fonctionner pendant 24 heures. Une clé live sur Free et Developer, cinq sur Pro, vingt sur Business ; plus une clé tp_test_ sur chaque offre, émise sur demande en attendant le portail.

Authentification

Envoyez votre clé comme bearer token, ou dans X-Api-Key :

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

Les clés commencent par tp_live_ ou tp_test_, puis 43 caractères. Une requête sans clé est traitée comme anonyme et limitée par adresse IP. Une clé mal formée, inconnue ou révoquée répond immédiatement 401, même sur les endpoints qui auraient fonctionné en anonyme. Les endpoints marqués clé ci-dessous répondent 401 unauthenticated sans clé.

Scopes. Les clés portent tous les scopes (read, exports, webhooks, wishlists), sauf si elles ont été émises avec une liste plus restreinte, par exemple en lecture seule ; GET /v1/me liste les scopes d’une clé. Les listes de souhaits et les webhooks exigent les scopes wishlists et webhooks (une liste de souhaits avec livraison webhook exige les deux, vérifiés avant l’offre). Une clé sans le scope répond 403 forbidden_scope avec required_scope, et WWW-Authenticate contient error="insufficient_scope".

Clés de test. Une clé tp_test_ lit les mêmes données en direct avec les limites de Free, et possède ses propres listes de souhaits, correspondances et endpoints webhook (livemode: false). Elle ne voit ni ne modifie jamais ceux du mode live : une suite de tests peut donc faire un PUT d’un ensemble vide de listes de souhaits sans toucher à la production. Les webhooks en mode test ne peuvent pointer que vers webhook.site.

Nouvelles tentatives. POST, PATCH et DELETE sur les endpoints qui exigent une clé ou un jeton partenaire acceptent un en-tête Idempotency-Key (un UUID par requête logique) ; les endpoints d’inscription ne l’acceptent pas. Une nouvelle tentative avec la même clé et la même requête reçoit à nouveau la première réponse réussie, marquée Idempotent-Replayed: true, au lieu de créer une seconde liste de souhaits ou un second endpoint. La clé est mémorisée pendant 24 heures ; la réutiliser pour une autre requête donne 422 idempotency_key_reused, et une nouvelle tentative pendant que la première requête est encore en cours donne 409 idempotency_in_progress. Les erreurs ne sont pas mémorisées : une nouvelle tentative après une erreur s’exécute donc à nouveau. Une connexion interrompue n’annule pas une requête qui porte l’en-tête : elle se termine et une réponse réussie est mémorisée, que votre nouvelle tentative recevra. Une requête encore en cours après 20 secondes est annulée sans rien modifier et répond 503 unavailable avec Retry-After: 1 ; réessayez avec la même clé.

Les applications partenaires (actuellement Tapelark) envoient un jeton de droits signé au lieu d’une clé : Authorization: Bearer TPE1-…. Le jeton est vérifié hors ligne avec les clés Ed25519 du partenaire et correspond à l’offre partenaire du tableau ci-dessous ; l’API n’apprend jamais qui est l’utilisateur final. Un jeton mal formé ou inconnu répond 401 token_invalid, un jeton expiré 401 token_expired (rafraîchissez-le), un jeton destiné à une autre audience 401 token_audience, et un droit expiré ou révoqué 403 entitlement_revoked.

Limites de débit et quotas

NiveauDébitQuotaIdentifiants par lotHistorique
Anonyme (par IP)2 req/s, rafale de 51 000 / jour20aucun
Free2 req/s, rafale de 51 000 / jour20aucun
Developer10 req/s, rafale de 20100 000 / mois1007 jours
Pro50 req/s, rafale de 1001 000 000 / mois10012 mois
Businesssur mesuresur mesure10012 mois
Tapelark (jeton de droits, par licence)5 req/s, rafale de 102 000 / jour507 jours

La limite par seconde est un seau à jetons. Quand il est vide, vous recevez 429 rate_limited avec Retry-After. Quand le quota est épuisé, vous recevez 429 quota_exceeded avec Retry-After réglé sur le nombre de secondes avant la réinitialisation de la fenêtre.

Les réponses authentifiées qui ne peuvent pas être mises en cache partagé portent les champs de limite de débit de l’IETF :

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

Les réponses pouvant être mises en cache partagé (recherche, lot, titre en cours d’une station, stations, statistiques, séries de santé) ne portent pas d’en-têtes de limite de débit, car le cache en périphérie peut servir le même corps à un autre appelant. Votre budget restant est toujours disponible sur GET /v1/me.

Cache

Les réponses en direct peuvent être mises en cache 5 secondes (Cache-Control: public, max-age=5, s-maxage=5) et portent un ETag faible. Envoyez If-None-Match pour recevoir un 304 quand rien n’a changé ; un 304 n’est pas décompté de votre quota à l’origine. L’annuaire des stations est mis en cache 60 secondes, une station seule 300. L’historique est private, max-age=30. Les endpoints de santé et de clé sont no-store.

Chaque réponse porte X-Request-Id. Indiquez-le quand vous écrivez au support.

Erreurs

Les erreurs suivent les problem details de la RFC 9457, avec deux membres d’extension : code, une chaîne stable lisible par machine, et des compléments propres à l’endpoint, comme errors[] pour les échecs de validation ou required_tier pour un 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." } ]
}

La liste complète des codes et de leur signification se trouve dans le catalogue des erreurs. Ceux que vous rencontrerez en premier :

HTTPcodeQuand
400invalid_requestParamètre de requête inconnu ou requête mal formée
400invalid_cursorCurseur mal formé, expiré (24 h ; 30 jours pour un since_cursor du flux de correspondances) ou utilisé avec d’autres filtres
401unauthenticated · invalid_api_key · key_revokedClé absente, inconnue ou révoquée
403forbidden_tierL’endpoint ou le paramètre exige une offre supérieure ; required_tier indique laquelle
403forbidden_scopeLa clé a été émise sans le scope ; required_scope indique lequel
404not_found · station_opted_outIdentifiant inconnu, ou la station a demandé à ne pas être indexée
422validation_failedErreurs de champ dans errors[]
429rate_limited · quota_exceededRalentissez ; Retry-After en secondes
503unavailableBase de données indisponible, ou requête avec un Idempotency-Key annulée après 20 secondes ; réessayez après Retry-After

GET/v1/now-playing/search sans clé public, max-age=5 · ETag

Quelles stations diffusent q en ce moment. Recherche approximative et insensible aux accents : gronemeyer manner trouve Grönemeyer – Männer. Les lignes sont triées par similarité, puis par date d’apparition du titre, la plus récente d’abord. Pas de pagination : l’index change toutes les quelques secondes, des pages sauteraient ou répéteraient des lignes. Augmentez plutôt limit.

ParamètreTypeDéfautSignification
qchaîne, 2–120 caractèresobligatoireArtiste, titre ou les deux. À partir de trois caractères, la recherche utilise des trigrammes ; les requêtes plus courtes cherchent une sous-chaîne.
countryISO 3166-1 alpha-2, répétabletousUniquement les stations de ces pays.
codecchaînetousp. ex. MP3, AAC.
min_bitrateentier0kbit/s.
classificationenumsongsong, ident, programme, ad_break, empty, junk, unknown ou any.
max_age_seconds30–900180Uniquement les lignes dont la station a été interrogée avec succès dans ce nombre de secondes.
limit1–10020Nombre de lignes à renvoyer ; truncated: true indique qu’il y en avait davantage.

Réponse · 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 }
}

Lire une ligne

started_at RFC 3339 · estimation
Début estimé du titre en cours, à ± start_uncertainty_seconds près dans un sens ou dans l’autre.
observed_at RFC 3339
Moment où Tuneping a vu ce titre pour la première fois.
confirmed_at RFC 3339
La dernière interrogation réussie de la station : « toujours en cours à ce moment-là ». Utilisez-le pour juger de la fraîcheur.
track objet · facultatif
Présent quand le titre a été relié à un enregistrement MusicBrainz.
similarity 0–1
Filtrez selon votre propre seuil.

GET/v1/now-playing sans clé public, max-age=5 · ETag

Titres en cours par lot

Le titre en cours d’une liste de stations en un seul appel, pour les écrans de favoris.

ParamètreTypeDéfautSignification
station_idsUUID séparés par des virgulesobligatoireJusqu’à 20 en anonyme ou sur Free, 100 à partir de Developer. Les identifiants sont triés et dédoublonnés avant la mise en cache : deux clients avec les mêmes favoris partagent ainsi un même corps en cache.
max_age_seconds30–86400900Les lignes plus anciennes sont tout de même renvoyées, mais marquées 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 sans clé

Titre en cours d’une station

Une ligne pour une station. 404 not_found quand aucun titre n’a encore été observé, 404 station_opted_out quand la station a demandé à ne pas être indexée.

GET/v1/stations GET/v1/stations/{id} sans clé max-age=60 · 300

Stations

L’annuaire, construit à partir de l’annuaire Radio Browser maintenu par la communauté et enrichi de la façon dont Tuneping lit chaque station.

ParamètreTypeDéfautSignification
qchaîne—Nom de la station, recherche approximative.
countryalpha-2, répétabletous
tagchaîne, répétabletousTous les tags indiqués doivent correspondre.
codec, min_bitrateComme pour la recherche.
has_now_playingbooléenfalseUniquement les stations dont Tuneping lit actuellement les titres.
sortenumclickcountclickcount (popularité, décroissante), name, bitrate.
limit1–20050
cursorchaîne opaque—Issu du next_cursor de la page précédente ; valable 24 heures et uniquement avec les mêmes filtres.
{ "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 est transmis tel quel, sous forme de texte, depuis l’annuaire ; Tuneping ne copie ni ne sert jamais de logos.

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

Historique de diffusion

Les chansons diffusées par une station, les plus récentes d’abord, avec début et fin estimés. La profondeur dépend de l’offre : 7 jours sur Developer, 12 mois sur Pro et Business. Demander un from plus ancien répond 403 forbidden_tier avec required_tier, jamais une fenêtre raccourcie en silence.

ParamètreTypeDéfautSignification
from, toRFC 3339to = maintenant, from = to − 24 hAu plus 31 jours par appel.
classificationenumsongComme pour la recherche.
limit1–20050
cursorchaîne opaque—La fenêtre est figée dans le curseur.
{ "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/me GET/v1/me/usage clé no-store

Clés et consommation

/v1/me renvoie la clé, l’offre et le statut de son titulaire, les limites qui s’appliquent et la consommation jusqu’ici dans la fenêtre en cours. /v1/me/usage?from=&to= renvoie les requêtes par jour (requêtes à l’origine uniquement ; les réponses servies par le cache en périphérie ne sont pas comptées).

{ "key":   { "id": "key_…", "hint": "tp_live_a1b2c3…", "scopes": ["read", "exports", "webhooks", "wishlists"], "livemode": true,
             "created_at": "…", "last_used_at": "…" },
  "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": 7, "batch_station_ids": 100, "webhooks": 0, "wishlists": 10,
              "sse_connections": 1, "tightened_stations": 200 },
  "usage":  { "period_requests": 41230, "today_requests": 1810, "resets_at": "…" } }

GET/v1/health GET/v1/health/series GET/v1/stats sans clé

Santé et statistiques

/v1/health répond 200 avec status ok ou degraded, et 503 quand la base de données est hors service ; faites-y pointer votre outil de surveillance de disponibilité. /v1/health/series?minutes=60 (5–1440) renvoie les chiffres du poller par minute : polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds et une ventilation by_method. /v1/stats est l’instantané public de couverture affiché sur la page d’état : total des stations, stations interrogées, avec métadonnées en direct, changements de titre par minute, part des chansons, par méthode, par pays.

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

Listes de souhaits

Indiquez une chanson, un artiste ou un titre en texte libre et sachez quand une station commence à le diffuser. Chaque changement de titre est comparé à vos listes de souhaits au moment où il est lu, et une nouvelle liste de souhaits est active en moins de 30 secondes. Developer conserve 10 listes de souhaits, Pro 100, Business 1 000. Les correspondances sont conservées 30 jours.

ChampTypeSignification
kindartist · track · queryartist et track prennent un identifiant issu de la recherche (art_…, trk_…) et attrapent aussi les graphies que le normaliseur y relie. query prend du texte libre, normalement Artist - Title.
valuechaîne, ≤ 240L’identifiant ou le texte.
matchexact · fuzzyquery uniquement. Exact compare le texte normalisé ; fuzzy accepte une similarité par trigrammes de 0,6 ou plus.
station_filterobjetstation_ids (≤ 500) et/ou countries (alpha-2 en majuscules ou minuscules, renvoyé en majuscules). Un titre correspond quand sa station figure dans la liste ou se trouve dans un pays listé. Sans filtre, toutes les stations sont concernées.
deliverytableaupoll (par défaut), sse, webhook. La livraison par webhook est réservée à Pro et au-delà, et exige webhook_id ainsi qu’une clé avec le scope webhooks.
descriptionchaîne, ≤ 200À votre usage.
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

Une liste explicite station_ids fait lire ces stations par Tuneping toutes les 30 secondes au lieu de toutes les 60 tant que la liste de souhaits est activée, et la réponse l’indique avec tightens: true. L’union des stations que vous listez est plafonnée selon l’offre (limits.tightened_stations dans /v1/me) ; une liste de souhaits qui la dépasserait répond 422 validation_failed avec une entrée errors[] dont le code est tightened_stations_exceeded (champ station_filter.station_ids, ou data pour PUT). Les filtres par pays fonctionnent toujours, à l’intervalle normal de chaque station.

GET /v1/wishlists/{id}, PATCH (filtre de stations, match, livraison, webhook, description, status: enabled | paused) et DELETE fonctionnent comme on s’y attend. PUT /v1/wishlists avec {"data": [ … ]} remplace tout votre ensemble en un seul appel : les éléments inchangés gardent leur identifiant et leur nombre de correspondances, les autres sont créés ou supprimés. C’est le moyen le plus simple de synchroniser une liste que votre application tient en local. Son corps peut atteindre 1 MiB ; les autres corps de requête sont limités à 64 KiB (413 payload_too_large).

Les correspondances forment un flux à suivre en continu. GET /v1/wishlists/matches couvre toutes vos listes de souhaits et GET /v1/wishlists/{id}/matches une seule d’entre elles. Les lignes arrivent de la plus ancienne à la plus récente. Gardez le next_cursor de chaque réponse et renvoyez-le comme since_cursor : il n’est jamais null, reste valable pendant les 30 jours de conservation et survit aux redémarrages. Sans curseur, vous obtenez les correspondances les plus récentes. limit va de 1 à 500, 100 par défaut.

{ "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 est déjà corrigé en fonction du moment où le titre a été vu : un enregistreur peut donc afficher « commencé il y a environ N ± U secondes ». expected_end_hint_at vaut started_at plus 222 secondes, la durée médiane d’une chanson : une indication, pas une prédiction. confidence vaut high quand la correspondance passe par un identifiant de morceau ou d’artiste relié, medium pour l’artiste et le titre normalisés, low pour les requêtes en texte.

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

Webhooks

Tuneping envoie les événements à votre endpoint HTTPS au moment où ils se produisent. Pro dispose de 5 endpoints, Business de 20.

ÉvénementQuandExigence sur Pro
wishlist.matchedUne liste de souhaits qui désigne cet endpoint dans webhook_id a trouvé une correspondance.—
station.title_changedLa ligne de titre d’une station a changé.filters.station_ids (≤ 200)
track.startedUn morceau relié a commencé.filters.track_ids ou artist_ids (≤ 200)
artist.startedUn artiste relié a commencé.filters.track_ids ou artist_ids (≤ 200)

Les filtres peuvent aussi lister countries et classification. Les filtres par pays seuls et les flux non filtrés de changements de titre sont réservés à Business. Chaque endpoint dispose d’un budget quotidien de 50 000 événements sur Pro et de 500 000 sur Business ; au-delà, l’endpoint passe en paused_budget jusqu’au jour UTC suivant et vous recevez 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

La réponse contient le secret de signature (whsec_…), une seule fois. Les livraisons suivent Standard Webhooks : en-têtes webhook-id, webhook-timestamp et webhook-signature: v1,<base64>, où la signature est un HMAC-SHA256 calculé avec les octets du secret sur {webhook-id}.{webhook-timestamp}.{body}. Rejetez les horodatages décalés de plus de 5 minutes, et utilisez webhook-id pour écarter les doublons : il reste identique d’une nouvelle tentative à l’autre.

{ "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épondez par n’importe quel 2xx sous 10 secondes. Les redirections ne sont pas suivies. Les livraisons en échec sont retentées après 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h et 24 h, soit environ trois jours au total, une à la fois par endpoint. Un endpoint qui répond 410, ou qui échoue cinq jours de suite, est désactivé et vous recevez un e-mail ; remettez status à enabled avec PATCH une fois le problème corrigé.

POST /v1/webhooks/{id}/rotate-secret renvoie un nouveau secret ; pendant 24 heures, chaque livraison porte deux signatures, une par secret. POST /v1/webhooks/{id}/test envoie un ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed liste 30 jours de livraisons, et POST …/deliveries/{dl_id}/redeliver en renvoie une. Les clés de test (tp_test_…) ne peuvent pointer que vers webhook.site.

GET/v1/stream clé · developer + text/event-stream

Flux d’événements

Les mêmes événements sur une seule réponse HTTP longue au format server-sent events, pour les serveurs et les applications natives. Passez soit station_ids (jusqu’à 100), soit wishlist_id (all, ou un identifiant 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
ParamètreDéfautSignification
station_ids—Identifiants de stations séparés par des virgules, 100 au plus.
wishlist_id—all ou un identifiant de liste de souhaits. Envoie wishlist.matched.
eventsstation.title_changedPour les flux de stations, n’importe lesquels parmi station.title_changed, track.started, artist.started.
classificationsongsong ou any, pour les flux de stations.

data contient le même JSON que celui que reçoit un webhook. Un commentaire : ping arrive après 20 secondes sans événement : réglez donc le délai d’inactivité de votre client sur 60 secondes ou plus. Quand la connexion tombe, reconnectez-vous avec l’en-tête Last-Event-ID réglé sur le dernier id reçu : les événements des 10 dernières minutes sont rejoués. Une position plus ancienne reçoit un event: gap avec le plus ancien identifiant disponible ; comblez le trou à partir de l’historique ou du flux de correspondances. Flux ouverts par clé : Developer 1, Pro 5, Business 20, Tapelark 1 par appareil. Un flux de plus répond 429 too_many_connections.

Les navigateurs ne peuvent pas utiliser EventSource ici, car il ne peut pas envoyer l’en-tête Authorization et les clés ne sont jamais acceptées dans l’URL. Lisez plutôt le flux avec fetch().

Champs et énumérations

classification enum
song · ident (nom ou slogan de la station) · programme · ad_break · empty · junk · unknown. Seules les chansons entrent dans l’historique de diffusion ; tout est conservé dans la ligne en direct.
match_grade enum
high · medium · none. Qualité du lien entre le titre et un enregistrement MusicBrainz.
confidence enum
high · medium · low. Calculé pour chaque ligne à partir de la classification, de la qualité de correspondance, de l’ancienneté de la dernière confirmation de la station et de l’historique de fiabilité de la station. Documenté pour que vous puissiez le vérifier.
method enum
La façon dont Tuneping lit la station : icy (métadonnées dans le flux), icecast_json (JSON d’état du serveur), shoutcast_stats (XML d’état), shoutcast_currentsong, seven_html. Des identifiants internes, pas des recommandations ; affichez-les comme « dans le flux » et « page d’état » si vous les montrez.
recordability enum
Indique si le flux de la station est un flux HTTP simple et enregistrable, du point de vue d’un client d’enregistrement. À titre informatif.
Identifiants
Les stations utilisent leur UUID Radio Browser. Les morceaux sont trk_…, les artistes art_…, les événements de diffusion pe_…, les clés key_…, les titulaires own_…, les listes de souhaits wl_…, les correspondances wm_…, les webhooks wh_…, les livraisons dl_…, les événements evt_…. Traitez-les tous comme des chaînes opaques.
Horodatages
RFC 3339, UTC, précision à la milliseconde lorsque la source la fournit.

OpenAPI

La description lisible par machine de cette API se trouve dans /docs/openapi-v1.yaml (OpenAPI 3.1). Elle sert de source pour la génération de clients ; les identifiants d’opération sont stables.

Attribution et usage loyal

L’usage sur l’offre Free et en anonyme affiche un lien visible « Data by Tuneping » là où les données sont présentées (badge). Les diffusions sont détectées à partir des métadonnées des stations et ne constituent pas un suivi certifié ; indiquez « détecté à partir des métadonnées de la station » lorsque vous les présentez comme élément de preuve. Les noms de stations sont du texte ; Tuneping ne fournit ni logos ni visuels. Voir les conditions.