Tuneping API v1

Eine JSON-API dafür, was Internetradiosender gerade spielen und was sie gespielt haben. Basis-URL https://api.tuneping.com/v1. Nur Metadaten: der Dienst liest die öffentliche Titelzeile jedes Streams und rührt das Audio nie an.

Öffentliche BetaDie Live-Endpoints (Suche, Batch, Jetzt läuft pro Sender, Senderverzeichnis, Health und Statistiken) funktionieren ohne Schlüssel, begrenzt pro IP-Adresse. Schlüssel gibt es per Selbstbedienung auf der Registrierungsseite: Free sofort, Developer und Pro nach Stripe Checkout. Business: Schreiben Sie an [email protected].

Schnellstart

Wer spielt gerade Madonna?

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

Was gerade auf einem Sender läuft, dessen ID Sie kennen (IDs sind die Sender-UUIDs von Radio Browser):

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

Was ein Sender in den letzten 24 Stunden gespielt hat (erfordert einen Schlüssel mit Verlauf, ab dem Developer-Tarif):

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

Jede Antwort ist JSON. Fehler sind application/problem+json (siehe Fehler). Unbekannte Query-Parameter werden mit 400 abgelehnt statt ignoriert, sodass Tippfehler sofort auffallen.

Einen Schlüssel erhalten

Selbstbedienung auf tuneping.com/signup: E-Mail-Adresse eingeben, den zugesandten Link innerhalb von 60 Minuten öffnen, und der Schlüssel wird einmal angezeigt. Dieselben zwei Schritte gibt es als API: POST /v1/signup mit {"email": "…", "plan": "free|developer|pro"} antwortet mit 202 und sendet die E-Mail; POST /v1/signup/verify mit dem {"token": "…"} aus dem Link antwortet mit 201 und dem Schlüssel. Free braucht sonst nichts. Developer und Pro gehen über den Link in der Antwort weiter zu Stripe Checkout; der Tarif des Inhabers ändert sich innerhalb einer Minute nach der Zahlung, und alle Schlüssel des Inhabers folgen.

Schlüssel verloren? Registrieren Sie sich erneut mit derselben Adresse: Der Schlüssel wird rotiert, der alte funktioniert noch 24 Stunden. Ein Live-Schlüssel bei Free und Developer, fünf bei Pro, zwanzig bei Business; dazu ein tp_test_-Schlüssel in jedem Tarif, auf Anfrage ausgestellt, bis das Portal verfügbar ist.

Authentifizierung

Senden Sie Ihren Schlüssel als Bearer-Token oder in X-Api-Key:

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

Schlüssel beginnen mit tp_live_ oder tp_test_, gefolgt von 43 Zeichen. Eine Anfrage ohne Schlüssel gilt als anonym und wird pro IP-Adresse begrenzt. Ein fehlerhafter, unbekannter oder widerrufener Schlüssel führt sofort zu 401, auch bei Endpoints, die anonym funktioniert hätten. Endpoints, die unten mit Schlüssel markiert sind, antworten ohne Schlüssel mit 401 unauthenticated.

Scopes. Schlüssel haben alle Scopes (read, exports, webhooks, wishlists), sofern sie nicht mit einer engeren Liste ausgestellt wurden, zum Beispiel nur lesend; GET /v1/me listet die Scopes eines Schlüssels auf. Wunschlisten und Webhooks benötigen die Scopes wishlists und webhooks (eine Wunschliste mit Zustellung per webhook braucht beide, geprüft vor dem Tarif). Ein Schlüssel ohne den Scope antwortet mit 403 forbidden_scope samt required_scope, und WWW-Authenticate enthält error="insufficient_scope".

Testschlüssel. Ein tp_test_-Schlüssel liest dieselben Live-Daten mit den Limits von Free und hat eigene Wunschlisten, Treffer und Webhook-Endpoints (livemode: false). Er sieht und ändert die Live-Objekte nie, sodass eine Testsuite eine leere Menge von Wunschlisten per PUT setzen kann, ohne die Produktion zu berühren. Webhooks im Testmodus dürfen nur auf webhook.site zeigen.

Wiederholungen. POST, PATCH und DELETE auf Endpoints, die einen Schlüssel oder ein Partner-Token benötigen, akzeptieren einen Idempotency-Key-Header (eine UUID pro logischer Anfrage); die Registrierungs-Endpoints nehmen ihn nicht an. Eine Wiederholung mit demselben Idempotency-Key und derselben Anfrage erhält erneut die erste erfolgreiche Antwort, markiert mit Idempotent-Replayed: true, statt eine zweite Wunschliste oder einen zweiten Endpoint anzulegen. Der Idempotency-Key wird 24 Stunden lang gespeichert; wird er für eine andere Anfrage wiederverwendet, folgt 422 idempotency_key_reused, und eine Wiederholung, während die erste Anfrage noch läuft, ergibt 409 idempotency_in_progress. Fehler werden nicht gespeichert, eine Wiederholung nach einem Fehler wird also erneut ausgeführt. Eine abgebrochene Verbindung bricht eine Anfrage mit diesem Header nicht ab: Sie wird zu Ende ausgeführt, und eine erfolgreiche Antwort wird gespeichert, sodass Ihre Wiederholung sie erhält. Eine Anfrage, die nach 20 Sekunden noch läuft, wird abgebrochen, ohne dass etwas geändert wurde, und antwortet mit 503 unavailable und Retry-After: 1; wiederholen Sie sie mit demselben Idempotency-Key.

Partner-Apps (derzeit Tapelark) senden statt eines Schlüssels ein signiertes Entitlement-Token: Authorization: Bearer TPE1-…. Das Token wird offline gegen die Ed25519-Schlüssel des Partners verifiziert und auf den Partnertarif in der Tabelle unten abgebildet; die API erfährt nie, wer der Endnutzer ist. Ein fehlerhaftes oder unbekanntes Token antwortet mit 401 token_invalid, ein abgelaufenes mit 401 token_expired (erneuern Sie es), ein Token für eine andere Audience mit 401 token_audience und ein abgelaufenes oder widerrufenes Entitlement mit 403 entitlement_revoked.

Rate-Limits und Kontingente

TarifRateKontingentBatch-IDsVerlauf
Anonym (pro IP)2 req/s, Burst 51.000 / Tag20keiner
Free2 req/s, Burst 51.000 / Tag20keiner
Developer10 req/s, Burst 20100.000 / Monat1007 Tage
Pro50 req/s, Burst 1001.000.000 / Monat10012 Monate
Businessindividuellindividuell10012 Monate
Tapelark (Entitlement-Token, pro Lizenz)5 req/s, Burst 102.000 / Tag507 Tage

Das Limit pro Sekunde ist ein Token-Bucket. Ist er leer, erhalten Sie 429 rate_limited mit Retry-After. Ist das Kontingent aufgebraucht, erhalten Sie 429 quota_exceeded, wobei Retry-After die Sekunden bis zum Zurücksetzen des Zeitfensters angibt.

Authentifizierte Antworten, die nicht in gemeinsamen Caches gespeichert werden dürfen, enthalten die Rate-Limit-Felder der IETF:

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

Antworten, die in gemeinsamen Caches gespeichert werden dürfen (Suche, Batch, Jetzt läuft pro Sender, Sender, Statistiken, Health-Zeitreihe), enthalten keine Rate-Limit-Header, weil die Edge denselben Body auch an andere Aufrufer ausliefern kann. Ihr verbleibendes Budget sehen Sie jederzeit unter GET /v1/me.

Caching

Live-Antworten sind 5 Sekunden lang cachebar (Cache-Control: public, max-age=5, s-maxage=5) und tragen ein schwaches ETag. Senden Sie If-None-Match, um 304 zu erhalten, wenn sich nichts geändert hat; ein 304 wird am Origin nicht auf Ihr Kontingent angerechnet. Das Senderverzeichnis wird 60 Sekunden gecacht, ein einzelner Sender 300. Der Verlauf ist private, max-age=30. Health- und Schlüssel-Endpoints sind no-store.

Jede Antwort enthält X-Request-Id. Nennen Sie sie, wenn Sie dem Support schreiben.

Fehler

Fehler folgen den Problem Details nach RFC 9457 mit zwei Erweiterungsfeldern: code, einer stabilen, maschinenlesbaren Zeichenkette, und endpoint-spezifischen Zusätzen wie errors[] bei Validierungsfehlern oder required_tier bei 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." } ]
}

Die vollständige Liste der Codes mit ihrer Bedeutung steht im Fehlerkatalog. Diese begegnen Ihnen zuerst:

HTTPCodeWann
400invalid_requestUnbekannter Query-Parameter oder fehlerhafte Anfrage
400invalid_cursorCursor fehlerhaft, abgelaufen (24 h; 30 Tage für einen since_cursor des Treffer-Feeds) oder mit anderen Filtern verwendet
401unauthenticated · invalid_api_key · key_revokedSchlüssel fehlt, ist unbekannt oder widerrufen
403forbidden_tierEndpoint oder Parameter erfordert einen höheren Tarif; required_tier nennt ihn
403forbidden_scopeDer Schlüssel wurde ohne den Scope ausgestellt; required_scope nennt ihn
404not_found · station_opted_outUnbekannte ID, oder der Sender hat gebeten, nicht indexiert zu werden
422validation_failedFeldfehler in errors[]
429rate_limited · quota_exceededLangsamer werden; Retry-After in Sekunden
503unavailableDatenbank nicht verfügbar, oder eine Anfrage mit Idempotency-Key nach 20 Sekunden abgebrochen; nach Retry-After wiederholen

GET/v1/now-playing/search ohne Schlüssel public, max-age=5 · ETag

Welche Sender gerade q spielen. Unscharf und unabhängig von Akzenten: gronemeyer manner findet Grönemeyer – Männer. Die Zeilen sind nach Ähnlichkeit sortiert, dann danach, wie kürzlich der Titel erschienen ist. Keine Paginierung: Der Index ändert sich alle paar Sekunden, Seiten würden also Einträge überspringen oder wiederholen. Erhöhen Sie stattdessen limit.

ParameterTypStandardBedeutung
qString, 2–120 ZeichenerforderlichInterpret, Titel oder beides. Ab drei Zeichen wird per Trigramm verglichen; kürzere Anfragen sind eine Teilstring-Suche.
countryISO 3166-1 alpha-2, wiederholbaralleNur Sender aus diesen Ländern.
codecStringallez. B. MP3, AAC.
min_bitrateInteger0kbit/s.
classificationEnumsongsong, ident, programme, ad_break, empty, junk, unknown oder any.
max_age_seconds30–900180Nur Zeilen, deren Sender innerhalb so vieler Sekunden erfolgreich abgefragt wurde.
limit1–10020Anzahl der zurückgegebenen Zeilen; truncated: true bedeutet, dass es mehr gab.

Antwort · 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 }
}

Eine Zeile lesen

started_at RFC 3339 · Schätzung
Geschätzter Beginn des aktuellen Titels, ± start_uncertainty_seconds in beide Richtungen.
observed_at RFC 3339
Wann Tuneping diesen Titel zuerst gesehen hat.
confirmed_at RFC 3339
Die letzte erfolgreiche Abfrage des Senders: „läuft noch, Stand“. Verwenden Sie es zur Beurteilung der Aktualität.
track Objekt · optional
Vorhanden, wenn der Titel mit einer MusicBrainz-Aufnahme verknüpft wurde.
similarity 0–1
Schneiden Sie bei Ihrem eigenen Schwellenwert ab.

GET/v1/now-playing ohne Schlüssel public, max-age=5 · ETag

Jetzt läuft im Batch

Der aktuelle Titel für eine Liste von Sendern in einem Aufruf, für Favoriten-Ansichten.

ParameterTypStandardBedeutung
station_idskommagetrennte UUIDserforderlichBis zu 20 anonym oder bei Free, 100 ab Developer. IDs werden vor dem Caching sortiert und dedupliziert, sodass sich zwei Clients mit denselben Favoriten einen gecachten Body teilen.
max_age_seconds30–86400900Ältere Zeilen werden weiterhin zurückgegeben, aber mit is_stale: true markiert.
{ "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 ohne Schlüssel

Jetzt läuft pro Sender

Eine Zeile für einen Sender. 404 not_found, wenn noch kein Titel beobachtet wurde, 404 station_opted_out, wenn der Sender gebeten hat, nicht indexiert zu werden.

GET/v1/stations GET/v1/stations/{id} ohne Schlüssel max-age=60 · 300

Sender

Das Verzeichnis, aufgebaut aus dem von der Community gepflegten Radio-Browser-Verzeichnis und ergänzt um die Angabe, wie Tuneping jeden Sender liest.

ParameterTypStandardBedeutung
qString—Sendername, unscharf.
countryalpha-2, wiederholbaralle
tagString, wiederholbaralleAlle angegebenen Tags müssen passen.
codec, min_bitrateWie bei der Suche.
has_now_playingBooleanfalseNur Sender, von denen Tuneping derzeit Titel liest.
sortEnumclickcountclickcount (Beliebtheit, absteigend), name, bitrate.
limit1–20050
cursoropaker String—Aus next_cursor der vorherigen Seite; 24 Stunden gültig und nur mit denselben Filtern.
{ "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 wird als Text aus dem Verzeichnis durchgereicht; Tuneping kopiert oder liefert niemals Logos aus.

GET/v1/stations/{id}/history Schlüssel · ab Developer private, max-age=30

Wiedergabeverlauf

Songs, die ein Sender gespielt hat, neueste zuerst, mit geschätztem Beginn und Ende. Wie weit der Verlauf zurückreicht, hängt vom Tarif ab: 7 Tage bei Developer, 12 Monate bei Pro und Business. Ein älteres from führt zu 403 forbidden_tier mit required_tier, nie zu einem stillschweigend gekürzten Zeitfenster.

ParameterTypStandardBedeutung
from, toRFC 3339to = jetzt, from = to − 24 hHöchstens 31 Tage pro Aufruf.
classificationEnumsongWie bei der Suche.
limit1–20050
cursoropaker String—Das Zeitfenster ist im Cursor festgeschrieben.
{ "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 Schlüssel no-store

Schlüssel und Nutzung

/v1/me liefert den Schlüssel, Tarif und Status seines Inhabers, die geltenden Limits und die bisherige Nutzung im aktuellen Zeitfenster. /v1/me/usage?from=&to= liefert die Anfragen pro Tag (nur Anfragen am Origin; Treffer im Edge-Cache werden nicht gezählt).

{ "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 ohne Schlüssel

Health und Statistiken

/v1/health antwortet mit 200 und status ok oder degraded, mit 503, wenn die Datenbank ausgefallen ist; richten Sie Ihr Uptime-Monitoring darauf. /v1/health/series?minutes=60 (5–1440) liefert Kennzahlen des Abfragedienstes pro Minute: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds und eine Aufschlüsselung by_method. /v1/stats ist der öffentliche Abdeckungs-Snapshot, den die Statusseite zeigt: Sender insgesamt, abgefragt, mit Live-Metadaten, Titelwechsel pro Minute, Anteil der Songs, nach Methode, nach Land.

POST/v1/wishlists PUT/v1/wishlists GET/v1/wishlists/matches Schlüssel · ab Developer no-store

Wunschlisten

Nennen Sie einen Song, einen Interpreten oder einen Freitext-Titel und erfahren Sie, wann ein Sender ihn zu spielen beginnt. Jeder Titelwechsel wird beim Lesen mit Ihren Wunschlisten abgeglichen, und eine neue Wunschliste ist innerhalb von 30 Sekunden aktiv. Developer speichert 10 Wunschlisten, Pro 100, Business 1.000. Treffer werden 30 Tage aufbewahrt.

FeldTypBedeutung
kindartist · track · queryartist und track nehmen eine ID aus der Suche (art_…, trk_…) und erfassen auch Schreibweisen, die der Normalisierer damit verknüpft. query nimmt Freitext, normalerweise Artist - Title.
valueString, ≤ 240Die ID oder der Text.
matchexact · fuzzyNur bei query. Exact vergleicht den normalisierten Text; fuzzy akzeptiert eine Trigramm-Ähnlichkeit von 0,6 oder mehr.
station_filterObjektstation_ids (≤ 500) und/oder countries (alpha-2 in beliebiger Schreibung, zurückgegeben in Großbuchstaben). Ein Titel passt, wenn sein Sender aufgeführt ist oder in einem aufgeführten Land liegt. Ohne Filter gilt jeder Sender.
deliveryArraypoll (Standard), sse, webhook. Zustellung per Webhook gibt es ab Pro; sie benötigt webhook_id und einen Schlüssel mit dem Scope webhooks.
descriptionString, ≤ 200Für Sie.
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

Eine ausdrückliche station_ids-Liste lässt Tuneping diese Sender alle 30 statt alle 60 Sekunden lesen, solange die Wunschliste aktiviert ist, und die Antwort sagt das mit tightens: true. Die Vereinigungsmenge Ihrer aufgeführten Sender ist pro Tarif begrenzt (limits.tightened_stations in /v1/me); eine Wunschliste, die diese Grenze überschreiten würde, antwortet mit 422 validation_failed und einem Eintrag in errors[], dessen code tightened_stations_exceeded ist (Feld station_filter.station_ids, bei PUT data). Länderfilter greifen weiterhin, im normalen Intervall des jeweiligen Senders.

GET /v1/wishlists/{id}, PATCH (Senderfilter, match, delivery, Webhook, Beschreibung, status: enabled | paused) und DELETE funktionieren wie erwartet. PUT /v1/wishlists mit {"data": [ … ]} ersetzt Ihre gesamte Menge in einem Aufruf: Unveränderte Einträge behalten ihre ID und ihren Trefferzähler, die übrigen werden angelegt oder gelöscht. So lässt sich eine Liste, die Ihre App lokal führt, am einfachsten synchronisieren. Der Body darf bis zu 1 MiB groß sein; andere Request-Bodys sind auf 64 KiB begrenzt (413 payload_too_large).

Treffer sind ein fortlaufender Feed. GET /v1/wishlists/matches umfasst alle Ihre Wunschlisten, GET /v1/wishlists/{id}/matches eine davon. Die Zeilen kommen älteste zuerst. Behalten Sie den next_cursor jeder Antwort und senden Sie ihn als since_cursor zurück: Er ist nie null, bleibt für die Aufbewahrungsdauer von 30 Tagen gültig und übersteht Neustarts. Ohne Cursor erhalten Sie die neuesten Treffer. limit ist 1–500, Standard 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 ist bereits um den Zeitpunkt korrigiert, zu dem der Titel gesehen wurde, sodass ein Recorder „begann vor etwa N ± U Sekunden“ anzeigen kann. expected_end_hint_at ist started_at plus 222 Sekunden, der Median der Songlängen: ein Hinweis, keine Vorhersage. confidence ist high, wenn der Treffer über eine verknüpfte Track- oder Interpreten-ID zustande kam, medium bei normalisiertem Interpret und Titel, low bei Textanfragen.

POST/v1/webhooks GET/v1/webhooks/{id}/deliveries Schlüssel · ab Pro no-store

Webhooks

Tuneping sendet Events per POST an Ihren HTTPS-Endpoint, sobald sie eintreten. Pro hat 5 Endpoints, Business 20.

EventWannPro benötigt
wishlist.matchedEine Wunschliste, die diesen Endpoint in webhook_id nennt, hat einen Treffer.—
station.title_changedDie Titelzeile eines Senders hat sich geändert.filters.station_ids (≤ 200)
track.startedEin verknüpfter Track hat begonnen.filters.track_ids oder artist_ids (≤ 200)
artist.startedEin verknüpfter Interpret hat begonnen.filters.track_ids oder artist_ids (≤ 200)

Filter können auch countries und classification enthalten. Reine Länderfilter und ungefilterte Streams von Titelwechseln gibt es nur bei Business. Jeder Endpoint hat ein Tagesbudget von 50.000 Events bei Pro und 500.000 bei Business; darüber hinaus ist der Endpoint bis zum nächsten UTC-Tag paused_budget, und Sie erhalten eine 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

Die Antwort enthält das Signatur-secret (whsec_…) ein einziges Mal. Zustellungen folgen Standard Webhooks: Header webhook-id, webhook-timestamp und webhook-signature: v1,<base64>, wobei die Signatur ein HMAC-SHA256 mit den Bytes des Secrets über {webhook-id}.{webhook-timestamp}.{body} ist. Lehnen Sie Zeitstempel ab, die mehr als 5 Minuten abweichen, und verwenden Sie webhook-id, um Duplikate zu verwerfen: Sie bleibt über Wiederholungen hinweg gleich.

{ "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_…" } }

Antworten Sie innerhalb von 10 Sekunden mit einem beliebigen 2xx. Weiterleitungen werden nicht verfolgt. Fehlgeschlagene Zustellungen werden nach 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h und 24 h wiederholt, insgesamt etwa drei Tage, pro Endpoint jeweils eine nach der anderen. Ein Endpoint, der mit 410 antwortet oder an fünf Tagen in Folge fehlschlägt, wird deaktiviert, und Sie erhalten eine E-Mail; setzen Sie status per PATCH wieder auf enabled, sobald er repariert ist.

POST /v1/webhooks/{id}/rotate-secret liefert ein neues Secret; 24 Stunden lang trägt jede Zustellung zwei Signaturen, eine pro Secret. POST /v1/webhooks/{id}/test sendet einen ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed listet die Zustellungen der letzten 30 Tage auf, und POST …/deliveries/{dl_id}/redeliver sendet eine davon erneut. Testschlüssel (tp_test_…) dürfen nur auf webhook.site zeigen.

GET/v1/stream Schlüssel · ab Developer text/event-stream

Event-Stream

Dieselben Events über eine einzige lange HTTP-Antwort im Format Server-Sent Events, für Server und native Apps. Übergeben Sie entweder station_ids (bis zu 100) oder wishlist_id (all oder eine wl_…-ID).

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
ParameterStandardBedeutung
station_ids—Kommagetrennte Sender-IDs, höchstens 100.
wishlist_id—all oder eine Wunschlisten-ID. Sendet wishlist.matched.
eventsstation.title_changedFür Sender-Streams beliebige von station.title_changed, track.started, artist.started.
classificationsongsong oder any, für Sender-Streams.

data ist dasselbe JSON, das ein Webhook erhält. Ein : ping-Kommentar kommt nach 20 Sekunden ohne Event, setzen Sie den Idle-Timeout Ihres Clients also auf 60 Sekunden oder mehr. Bricht die Verbindung ab, verbinden Sie sich erneut mit dem Header Last-Event-ID, gesetzt auf die letzte id, die Sie gesehen haben: Events der letzten 10 Minuten werden erneut gesendet. Eine ältere Position erhält ein event: gap mit der frühesten verfügbaren ID; füllen Sie die Lücke aus dem Verlauf oder dem Treffer-Feed. Offene Streams pro Schlüssel: Developer 1, Pro 5, Business 20, Tapelark 1 pro Gerät. Ein weiterer antwortet mit 429 too_many_connections.

Browser können hier EventSource nicht verwenden, weil es den Authorization-Header nicht senden kann und Schlüssel nie in der URL akzeptiert werden. Lesen Sie den Stream stattdessen mit fetch().

Felder und Enums

classification Enum
song · ident (Sendername oder Slogan) · programme · ad_break · empty · junk · unknown. Nur Songs gelangen in den Wiedergabeverlauf; in der Live-Zeile bleibt alles erhalten.
match_grade Enum
high · medium · none. Wie gut der Titel mit einer MusicBrainz-Aufnahme verknüpft wurde.
confidence Enum
high · medium · low. Pro Zeile berechnet aus Klassifizierung, Match-Grad, wie kürzlich der Sender bestätigt wurde und der bisherigen Zuverlässigkeit des Senders. Dokumentiert, damit Sie es nachprüfen können.
method Enum
Wie Tuneping den Sender liest: icy (Metadaten im Stream), icecast_json (Server-Status als JSON), shoutcast_stats (Status als XML), shoutcast_currentsong, seven_html. Interne Bezeichner, keine Empfehlungen; zeigen Sie sie, wenn überhaupt, als „im Stream“ und „Statusseite“ an.
recordability Enum
Ob der Stream des Senders aus Sicht eines aufzeichnenden Clients ein einfacher, aufzeichenbarer HTTP-Stream ist. Nur zur Information.
IDs
Sender verwenden ihre Radio-Browser-UUID. Tracks sind trk_…, Interpreten art_…, Wiedergabe-Events pe_…, Schlüssel key_…, Inhaber own_…, Wunschlisten wl_…, Treffer wm_…, Webhooks wh_…, Zustellungen dl_…, Events evt_…. Behandeln Sie alle als opake Strings.
Zeitstempel
RFC 3339, UTC, mit Millisekunden-Genauigkeit, wo die Quelle sie liefert.

OpenAPI

Die maschinenlesbare Beschreibung dieser API liegt unter /docs/openapi-v1.yaml (OpenAPI 3.1). Sie ist die Grundlage für die Client-Generierung; die Operation-IDs sind stabil.

Namensnennung und faire Nutzung

Bei Nutzung im Free-Tarif und anonymer Nutzung wird dort, wo die Daten angezeigt werden, ein sichtbarer Link „Data by Tuneping“ angezeigt (Badge). Wiedergaben werden anhand von Sender-Metadaten erkannt und sind kein zertifiziertes Monitoring; schreiben Sie „anhand von Sender-Metadaten erkannt“, wo Sie sie als Nachweis vorlegen. Sendernamen sind Text; Tuneping stellt keine Logos oder Artwork bereit. Siehe die Nutzungsbedingungen.