API de Tuneping v1

Una API JSON para saber lo que suena ahora mismo en las emisoras de radio por internet, y lo que pusieron antes. URL base https://api.tuneping.com/v1. Solo metadatos: el servicio lee la línea de título pública de cada stream y nunca toca el audio.

Beta públicaLos endpoints en vivo (búsqueda, lotes, now-playing de una emisora, directorio de emisoras, salud y estadísticas) funcionan sin clave, con un límite por dirección IP. Las claves son de autoservicio en la página de registro: Free al instante, Developer y Pro después de Stripe Checkout. Business: escribe a [email protected].

Inicio rápido

¿Quién está poniendo Madonna ahora mismo?

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

Lo que suena en una emisora que conoces por su id (los ids son UUID de emisora de Radio Browser):

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

Lo que puso una emisora en las últimas 24 horas (necesita una clave con historial, plan Developer o superior):

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

Todas las respuestas son JSON. Los errores son application/problem+json (consulta Errores). Los parámetros de consulta desconocidos se rechazan con 400 en lugar de ignorarse, así que las erratas salen a la luz enseguida.

Obtener una clave

Autoservicio en tuneping.com/signup: escribe una dirección de correo, abre el enlace recibido en menos de 60 minutos y la clave se muestra una vez. Esos mismos dos pasos son una API: POST /v1/signup con {"email": "…", "plan": "free|developer|pro"} responde 202 y envía el correo; POST /v1/signup/verify con el {"token": "…"} del enlace responde 201 con la clave. Free no necesita nada más. Developer y Pro continúan a Stripe Checkout desde el enlace de la respuesta; el plan del titular cambia en menos de un minuto tras el pago y todas las claves del titular lo siguen.

¿Perdiste una clave? Regístrate de nuevo con la misma dirección: la clave se rota y la anterior sigue funcionando durante 24 horas. Una clave live en Free y Developer, cinco en Pro, veinte en Business; además, una clave tp_test_ en todos los planes, emitida a petición hasta que esté disponible el portal.

Autenticación

Envía tu clave como token bearer o en X-Api-Key:

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

Las claves empiezan por tp_live_ o tp_test_ seguido de 43 caracteres. Una solicitud sin clave se trata como anónima y se limita por dirección IP. Una clave mal formada, desconocida o revocada responde 401 de inmediato, incluso en endpoints que habrían funcionado de forma anónima. Los endpoints marcados más abajo con clave responden 401 unauthenticated si no se envía una.

Ámbitos. Las claves tienen todos los ámbitos (read, exports, webhooks, wishlists) salvo que se emitieran con una lista más restringida, por ejemplo de solo lectura; GET /v1/me muestra los ámbitos de una clave. Las listas de deseos y los webhooks necesitan los ámbitos wishlists y webhooks (una lista de deseos con entrega por webhook necesita ambos, y se comprueba antes que el plan). Una clave sin el ámbito responde 403 forbidden_scope con required_scope, y WWW-Authenticate lleva error="insufficient_scope".

Claves de prueba. Una clave tp_test_ lee los mismos datos en vivo con los límites de Free, y tiene sus propias listas de deseos, coincidencias y endpoints de webhook (livemode: false). Nunca ve ni modifica los de producción, así que un conjunto de pruebas puede hacer PUT de un conjunto vacío de listas de deseos sin tocar producción. Los webhooks en modo de prueba solo pueden apuntar a webhook.site.

Reintentos. POST, PATCH y DELETE en los endpoints que necesitan una clave o un token de socio aceptan una cabecera Idempotency-Key (un UUID por solicitud lógica); los endpoints de registro no la admiten. Un reintento con la misma clave de idempotencia y la misma solicitud vuelve a recibir la primera respuesta correcta, marcada con Idempotent-Replayed: true, en lugar de crear una segunda lista de deseos o un segundo endpoint. La clave de idempotencia se recuerda durante 24 horas; reutilizarla para una solicitud distinta da 422 idempotency_key_reused, y un reintento mientras la primera solicitud sigue en curso da 409 idempotency_in_progress. Los errores no se recuerdan, así que un reintento después de un error se vuelve a ejecutar. Una conexión cortada no cancela una solicitud que lleva la cabecera: la solicitud termina y, si la respuesta es correcta, se recuerda, de modo que tu reintento la recibe. Una solicitud que sigue en curso tras 20 segundos se cancela sin cambiar nada y responde 503 unavailable con Retry-After: 1; reinténtala con la misma clave de idempotencia.

Apps de socios (actualmente Tapelark) envían un token de derecho de uso firmado en lugar de una clave: Authorization: Bearer TPE1-…. El token se verifica sin conexión con las claves Ed25519 del socio y se resuelve en el plan de socio de la tabla de abajo; la API nunca sabe quién es el usuario final. Un token mal formado o desconocido responde 401 token_invalid, uno caducado 401 token_expired (renuévalo), un token para otra audiencia 401 token_audience, y un derecho de uso vencido o revocado 403 entitlement_revoked.

Límites de frecuencia y cuotas

PlanFrecuenciaCuotaIds por loteHistorial
Anónimo (por IP)2 req/s, ráfaga de 51.000 / día20ninguno
Free2 req/s, ráfaga de 51.000 / día20ninguno
Developer10 req/s, ráfaga de 20100.000 / mes1007 días
Pro50 req/s, ráfaga de 1001.000.000 / mes10012 meses
Businessa medidaa medida10012 meses
Tapelark (token de derecho de uso, por licencia)5 req/s, ráfaga de 102.000 / día507 días

El límite por segundo es un token bucket. Cuando está vacío recibes 429 rate_limited con Retry-After. Cuando la cuota se agota recibes 429 quota_exceeded con Retry-After fijado en los segundos que faltan para que se restablezca la ventana.

Las respuestas autenticadas que no se pueden almacenar en una caché compartida llevan los campos de límite de frecuencia del IETF:

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

Las respuestas que sí se pueden almacenar en una caché compartida (búsqueda, lotes, now-playing de una emisora, emisoras, estadísticas, series de salud) no llevan cabeceras de límite de frecuencia, porque el edge puede servir el mismo cuerpo a otro cliente. Tu saldo restante siempre está disponible en GET /v1/me.

Caché

Las respuestas en vivo se pueden almacenar en caché durante 5 segundos (Cache-Control: public, max-age=5, s-maxage=5) y llevan un ETag débil. Envía If-None-Match para recibir 304 cuando nada ha cambiado; un 304 no cuenta contra tu cuota en el origen. El directorio de emisoras se almacena en caché durante 60 segundos, una emisora individual durante 300. El historial es private, max-age=30. Los endpoints de salud y de claves son no-store.

Todas las respuestas llevan X-Request-Id. Indícalo cuando escribas al soporte.

Errores

Los errores siguen los detalles de problema de RFC 9457 con dos miembros de extensión: code, una cadena estable legible por máquina, y extras específicos de cada endpoint, como errors[] en los fallos de validación o required_tier en el 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 lista completa de códigos con su significado está en el catálogo de errores. Los primeros con los que te encontrarás:

HTTPcodeCuándo
400invalid_requestParámetro de consulta desconocido o solicitud mal formada
400invalid_cursorCursor mal formado, caducado (24 h; 30 días para un since_cursor del feed de coincidencias) o usado con otros filtros
401unauthenticated · invalid_api_key · key_revokedClave ausente, desconocida o revocada
403forbidden_tierEl endpoint o el parámetro necesita un plan superior; required_tier indica cuál
403forbidden_scopeLa clave se emitió sin el ámbito; required_scope indica cuál
404not_found · station_opted_outId desconocido, o la emisora pidió no ser indexada
422validation_failedErrores de campo en errors[]
429rate_limited · quota_exceededVe más despacio; Retry-After en segundos
503unavailableBase de datos no disponible, o una solicitud con un Idempotency-Key cancelada tras 20 segundos; reintenta pasado Retry-After

GET/v1/now-playing/search sin clave public, max-age=5 · ETag

Qué emisoras están poniendo q ahora mismo. Búsqueda aproximada que ignora los acentos: gronemeyer manner encuentra Grönemeyer – Männer. Las filas se ordenan por similitud y después por lo reciente que es la aparición del título. Sin paginación: el índice cambia cada pocos segundos, así que las páginas se saltarían o repetirían filas. Aumenta limit en su lugar.

ParámetroTipoPredeterminadoSignificado
qstring, 2–120 caracteresobligatorioArtista, título o ambos. Con tres o más caracteres se usa coincidencia por trigramas; las consultas más cortas buscan una subcadena.
countryISO 3166-1 alfa-2, repetiblecualquieraSolo emisoras de estos países.
codecstringcualquierap. ej. MP3, AAC.
min_bitrateinteger0kbit/s.
classificationenumsongsong, ident, programme, ad_break, empty, junk, unknown o any.
max_age_seconds30–900180Solo filas cuya emisora se sondeó con éxito en este número de segundos.
limit1–10020Filas que se devuelven; truncated: true indica que había más.

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

Cómo leer una fila

started_at RFC 3339 · estimación
Inicio estimado del título actual, ± start_uncertainty_seconds en cualquier sentido.
observed_at RFC 3339
Cuándo vio Tuneping este título por primera vez.
confirmed_at RFC 3339
El último sondeo correcto de la emisora: «sigue sonando a esta hora». Úsalo para medir la frescura.
track object · opcional
Presente cuando el título se vinculó a una grabación de MusicBrainz.
similarity 0–1
Corta en tu propio umbral.

GET/v1/now-playing sin clave public, max-age=5 · ETag

Now-playing por lotes

El título actual de una lista de emisoras en una sola llamada, para pantallas de favoritas.

ParámetroTipoPredeterminadoSignificado
station_idsUUID separados por comasobligatorioHasta 20 en anónimo o Free, 100 a partir de Developer. Los ids se ordenan y se eliminan los duplicados antes de almacenar en caché, así que dos clientes con las mismas favoritas comparten un mismo cuerpo en caché.
max_age_seconds30–86400900Las filas más antiguas que este valor se siguen devolviendo, pero marcadas con 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 sin clave

Now-playing de una emisora

Una fila para una emisora. 404 not_found cuando todavía no se ha observado ningún título, 404 station_opted_out cuando la emisora pidió no ser indexada.

GET/v1/stations GET/v1/stations/{id} sin clave max-age=60 · 300

Emisoras

El directorio, construido a partir del directorio Radio Browser mantenido por la comunidad y enriquecido con la forma en que Tuneping lee cada emisora.

ParámetroTipoPredeterminadoSignificado
qstring—Nombre de la emisora, búsqueda aproximada.
countryalfa-2, repetiblecualquiera
tagstring, repetiblecualquieraDeben coincidir todas las etiquetas indicadas.
codec, min_bitrateComo en la búsqueda.
has_now_playingbooleanfalseSolo emisoras de las que Tuneping lee títulos actualmente.
sortenumclickcountclickcount (popularidad, descendente), name, bitrate.
limit1–20050
cursorstring opaco—Del next_cursor de la página anterior; válido 24 horas y solo con los mismos filtros.
{ "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 se transmite tal cual como texto desde el directorio; Tuneping nunca copia ni sirve logotipos.

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

Historial de reproducciones

Las canciones que puso una emisora, las más recientes primero, con inicio y fin estimados. La profundidad depende del plan: 7 días en Developer, 12 meses en Pro y Business. Pedir un from más antiguo responde 403 forbidden_tier con required_tier, nunca una ventana recortada en silencio.

ParámetroTipoPredeterminadoSignificado
from, toRFC 3339to = ahora, from = to − 24 hComo máximo 31 días por llamada.
classificationenumsongComo en la búsqueda.
limit1–20050
cursorstring opaco—La ventana queda fijada en el 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/me GET/v1/me/usage clave no-store

Claves y uso

/v1/me devuelve la clave, el plan y el estado de su titular, los límites que se aplican y el uso hasta el momento en la ventana actual. /v1/me/usage?from=&to= devuelve las solicitudes por día (solo solicitudes al origen; los aciertos de la caché del edge no se cuentan).

{ "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 sin clave

Salud y estadísticas

/v1/health responde 200 con status ok o degraded, y 503 cuando la base de datos está caída; apunta a él tu monitor de disponibilidad. /v1/health/series?minutes=60 (5–1440) devuelve cifras del sondeador por minuto: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds y un desglose by_method. /v1/stats es la instantánea pública de cobertura que se muestra en la página de estado: emisoras en total, sondeadas, con metadatos en vivo, cambios de título por minuto, proporción de canciones, por método y por país.

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

Listas de deseos

Indica una canción, un artista o un título en texto libre y entérate de cuándo una emisora empieza a ponerlo. Cada cambio de título se compara con tus listas de deseos en el momento en que se lee, y una lista de deseos nueva está activa en menos de 30 segundos. Developer admite 10 listas de deseos, Pro 100, Business 1.000. Las coincidencias se conservan 30 días.

CampoTipoSignificado
kindartist · track · queryartist y track toman un id de la búsqueda (art_…, trk_…) y también capturan las grafías que el normalizador vincula con él. query toma texto libre, normalmente Artist - Title.
valuestring, ≤ 240El id o el texto.
matchexact · fuzzySolo query. Exact compara el texto normalizado; fuzzy acepta una similitud por trigramas de 0,6 o más.
station_filterobjectstation_ids (≤ 500) y/o countries (alfa-2 en mayúsculas o minúsculas, devuelto en mayúsculas). Un título coincide cuando su emisora está en la lista o pertenece a un país de la lista. Sin filtro, se incluyen todas las emisoras.
deliveryarraypoll (predeterminado), sse, webhook. La entrega por webhook es de Pro en adelante y necesita webhook_id y una clave con el ámbito webhooks.
descriptionstring, ≤ 200Para tu uso.
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

Una lista explícita de station_ids hace que Tuneping lea esas emisoras cada 30 segundos en lugar de cada 60 mientras la lista de deseos esté activada, y la respuesta lo indica con tightens: true. La unión de las emisoras que listas tiene un tope por plan (limits.tightened_stations en /v1/me); una lista de deseos que lo superaría responde 422 validation_failed con una entrada en errors[] cuyo code es tightened_stations_exceeded (campo station_filter.station_ids, o data en PUT). Los filtros por país siguen generando coincidencias, al intervalo normal de cada emisora.

GET /v1/wishlists/{id}, PATCH (filtro de emisoras, match, entrega, webhook, descripción, status: enabled | paused) y DELETE funcionan como cabe esperar. PUT /v1/wishlists con {"data": [ … ]} sustituye todo tu conjunto en una sola llamada: los elementos sin cambios conservan su id y su recuento de coincidencias, el resto se crean o se eliminan. Es la forma más sencilla de sincronizar una lista que tu app guarda localmente. Su cuerpo puede ocupar hasta 1 MiB; los demás cuerpos de solicitud están limitados a 64 KiB (413 payload_too_large).

Las coincidencias son un feed de cola. GET /v1/wishlists/matches abarca todas tus listas de deseos y GET /v1/wishlists/{id}/matches una de ellas. Las filas llegan de la más antigua a la más reciente. Guarda el next_cursor de cada respuesta y devuélvelo como since_cursor: nunca es null, sigue siendo válido durante los 30 días de retención y sobrevive a los reinicios. Sin cursor recibes las coincidencias más recientes. limit va de 1 a 500, con 100 por defecto.

{ "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 ya está corregido según el momento en que se vio el título, así que una grabadora puede mostrar «empezó hace unos N ± U segundos». expected_end_hint_at es started_at más 222 segundos, la duración mediana de una canción: una pista, no una predicción. confidence es high cuando la coincidencia pasó por un id de pista o de artista vinculado, medium para el artista y el título normalizados, y low para las consultas de texto.

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

Webhooks

Tuneping envía eventos por POST a tu endpoint HTTPS a medida que ocurren. Pro tiene 5 endpoints, Business 20.

EventoCuándoPro necesita
wishlist.matchedHubo una coincidencia en una lista de deseos que indica este endpoint en webhook_id.—
station.title_changedCambió la línea de título de una emisora.filters.station_ids (≤ 200)
track.startedEmpezó una pista vinculada.filters.track_ids o artist_ids (≤ 200)
artist.startedEmpezó un artista vinculado.filters.track_ids o artist_ids (≤ 200)

Los filtros también pueden incluir countries y classification. Los filtros solo por país y los flujos sin filtrar de cambios de título son de Business. Cada endpoint tiene un presupuesto diario de 50.000 eventos en Pro y 500.000 en Business; si se supera, el endpoint pasa a paused_budget hasta el siguiente día UTC y recibes un correo.

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 respuesta incluye el secret de firma (whsec_…) una sola vez. Las entregas siguen Standard Webhooks: cabeceras webhook-id, webhook-timestamp y webhook-signature: v1,<base64>, donde la firma es un HMAC-SHA256 con los bytes del secreto sobre {webhook-id}.{webhook-timestamp}.{body}. Rechaza las marcas de tiempo desfasadas más de 5 minutos y usa webhook-id para descartar duplicados: se mantiene igual entre reintentos.

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

Responde con cualquier 2xx en menos de 10 segundos. Las redirecciones no se siguen. Las entregas fallidas se reintentan pasados 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h y 24 h, unos tres días en total, de una en una por endpoint. Un endpoint que responde 410, o que falla cinco días seguidos, se desactiva y recibes un correo; vuelve a poner status en enabled con PATCH cuando esté arreglado.

POST /v1/webhooks/{id}/rotate-secret devuelve un secreto nuevo; durante 24 horas cada entrega lleva dos firmas, una por secreto. POST /v1/webhooks/{id}/test envía un ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed lista 30 días de entregas, y POST …/deliveries/{dl_id}/redeliver vuelve a enviar una. Las claves de prueba (tp_test_…) solo pueden apuntar a webhook.site.

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

Flujo de eventos

Los mismos eventos a través de una única respuesta HTTP larga en formato server-sent events, para servidores y apps nativas. Pasa station_ids (hasta 100) o wishlist_id (all o 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
ParámetroPredeterminadoSignificado
station_ids—Ids de emisoras separados por comas, como máximo 100.
wishlist_id—all o un id de lista de deseos. Envía wishlist.matched.
eventsstation.title_changedPara flujos de emisoras, cualquiera de station.title_changed, track.started, artist.started.
classificationsongsong o any, para flujos de emisoras.

data es el mismo JSON que recibe un webhook. Un comentario : ping llega tras 20 segundos sin eventos, así que configura el tiempo de inactividad de tu cliente en 60 segundos o más. Cuando se corte la conexión, vuelve a conectarte con la cabecera Last-Event-ID fijada en el último id que viste: se reenvían los eventos de los últimos 10 minutos. Una posición más antigua recibe un event: gap con el id disponible más antiguo; rellena el hueco con el historial o con el feed de coincidencias. Flujos abiertos por clave: Developer 1, Pro 5, Business 20, Tapelark 1 por dispositivo. Uno más responde 429 too_many_connections.

Los navegadores no pueden usar EventSource aquí, porque no puede enviar la cabecera Authorization y las claves nunca se aceptan en la URL. Lee el flujo con fetch() en su lugar.

Campos y enums

classification enum
song · ident (nombre o eslogan de la emisora) · programme · ad_break · empty · junk · unknown. Solo las canciones pasan al historial de reproducciones; todo se conserva en la fila en vivo.
match_grade enum
high · medium · none. Qué tan bien se vinculó el título a una grabación de MusicBrainz.
confidence enum
high · medium · low. Se calcula por fila a partir de la clasificación, el grado de coincidencia, lo reciente que es la última confirmación de la emisora y su historial de fiabilidad. Está documentado para que puedas auditarlo.
method enum
Cómo lee Tuneping la emisora: icy (metadatos dentro del stream), icecast_json (JSON de estado del servidor), shoutcast_stats (XML de estado), shoutcast_currentsong, seven_html. Son identificadores internos, no avales; si los muestras, preséntalos como «en el stream» y «página de estado».
recordability enum
Si el stream de la emisora es un stream HTTP simple y grabable desde el punto de vista de un cliente de grabación. Informativo.
Ids
Las emisoras usan su UUID de Radio Browser. Las pistas son trk_…, los artistas art_…, los eventos de reproducción pe_…, las claves key_…, los titulares own_…, las listas de deseos wl_…, las coincidencias wm_…, los webhooks wh_…, las entregas dl_…, los eventos evt_…. Trátalos todos como cadenas opacas.
Marcas de tiempo
RFC 3339, UTC, con precisión de milisegundos cuando la fuente la tiene.

OpenAPI

La descripción legible por máquina de esta API está en /docs/openapi-v1.yaml (OpenAPI 3.1). Es la fuente para generar clientes; los ids de operación son estables.

Atribución y uso razonable

El uso del plan Free y el uso anónimo muestran un enlace visible «Data by Tuneping» donde se presentan los datos (insignia). Las reproducciones se detectan a partir de los metadatos de las emisoras y no son una monitorización certificada; indica «detectado a partir de los metadatos de la emisora» cuando las presentes como prueba. Los nombres de las emisoras son texto; Tuneping no proporciona logotipos ni imágenes. Consulta las condiciones.