API do Tuneping v1
Uma API JSON para o que as estações de rádio online estão tocando agora e o que tocaram. URL base https://api.tuneping.com/v1. Só metadados: o serviço lê a linha de título pública de cada stream e nunca toca no áudio.
Início rápido
Quem está tocando Madonna agora?
curl "https://api.tuneping.com/v1/now-playing/search?q=madonna&limit=3"
Tocando agora em uma estação que você conhece pelo id (os ids são UUIDs de estação do Radio Browser):
curl "https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/now-playing"
O que uma estação tocou nas últimas 24 horas (exige uma chave com histórico, plano Developer ou superior):
curl -H "Authorization: Bearer tp_live_…" \
"https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/history"
Toda resposta é JSON. Os erros são application/problem+json (veja Erros). Parâmetros de query desconhecidos são rejeitados com 400 em vez de ignorados, para que erros de digitação apareçam na hora.
Obter uma chave
Autoatendimento em tuneping.com/signup: informe um endereço de e-mail, abra o link enviado em até 60 minutos e a chave é exibida uma única vez. As mesmas duas etapas existem como API: POST /v1/signup com {"email": "…", "plan": "free|developer|pro"} responde 202 e envia o e-mail; POST /v1/signup/verify com o {"token": "…"} do link responde 201 com a chave. O Free não exige mais nada. Developer e Pro seguem para o Stripe Checkout pelo link na resposta; o plano do titular muda em até um minuto após o pagamento, e todas as chaves do titular acompanham a mudança.
Perdeu uma chave? Cadastre-se de novo com o mesmo endereço: a chave é rotacionada e a antiga continua funcionando por 24 horas. Uma chave live no Free e no Developer, cinco no Pro, vinte no Business; mais uma chave tp_test_ em todos os planos, emitida sob pedido até o portal ser lançado.
Autenticação
Envie sua chave como bearer token ou em X-Api-Key:
Authorization: Bearer tp_live_a1b2c3…
X-Api-Key: tp_live_a1b2c3…
As chaves começam com tp_live_ ou tp_test_ seguido de 43 caracteres. Uma requisição sem chave é tratada como anônima e limitada por endereço IP. Uma chave malformada, desconhecida ou revogada responde 401 imediatamente, mesmo em endpoints que teriam funcionado de forma anônima. Os endpoints marcados com chave abaixo respondem 401 unauthenticated sem chave.
Escopos. As chaves têm todos os escopos (read, exports, webhooks, wishlists), a menos que tenham sido emitidas com uma lista mais restrita, por exemplo somente leitura; GET /v1/me lista os escopos de uma chave. Listas de desejos e webhooks exigem os escopos wishlists e webhooks (uma lista de desejos com entrega por webhook exige os dois, verificados antes do plano). Uma chave sem o escopo responde 403 forbidden_scope com required_scope, e WWW-Authenticate traz error="insufficient_scope".
Chaves de teste. Uma chave tp_test_ lê os mesmos dados ao vivo com os limites do Free e tem suas próprias listas de desejos, correspondências e endpoints de webhook (livemode: false). Ela nunca vê nem altera os do modo live, então uma suíte de testes pode fazer PUT de um conjunto vazio de listas de desejos sem afetar a produção. Webhooks do modo de teste só podem apontar para webhook.site.
Novas tentativas. POST, PATCH e DELETE em endpoints que exigem uma chave ou um token de parceiro aceitam um cabeçalho Idempotency-Key (um UUID por requisição lógica); os endpoints de cadastro não o aceitam. Uma nova tentativa com a mesma chave e a mesma requisição recebe de novo a primeira resposta bem-sucedida, marcada com Idempotent-Replayed: true, em vez de criar uma segunda lista de desejos ou um segundo endpoint. A chave fica guardada por 24 horas; reutilizá-la para uma requisição diferente resulta em 422 idempotency_key_reused, e uma nova tentativa enquanto a primeira requisição ainda está em execução resulta em 409 idempotency_in_progress. Erros não são guardados, então uma nova tentativa após um erro é executada de novo. Uma conexão perdida não cancela uma requisição que traz o cabeçalho: ela termina e uma resposta bem-sucedida é guardada, então sua nova tentativa a recebe. Uma requisição ainda em execução após 20 segundos é cancelada sem nenhuma alteração e responde 503 unavailable com Retry-After: 1; tente de novo com a mesma chave.
Apps parceiros (atualmente o Tapelark) enviam um token de direito de acesso assinado em vez de uma chave: Authorization: Bearer TPE1-…. O token é verificado offline com as chaves Ed25519 do parceiro e corresponde ao plano de parceiro na tabela abaixo; a API nunca fica sabendo quem é o usuário final. Um token malformado ou desconhecido responde 401 token_invalid, um expirado 401 token_expired (renove-o), um token para outro público (audience) 401 token_audience, e um direito de acesso expirado ou revogado 403 entitlement_revoked.
Rate limits e cotas
| Plano | Taxa | Cota | Ids por lote | Histórico |
|---|---|---|---|---|
| Anônimo (por IP) | 2 req/s, rajada de 5 | 1.000 / dia | 20 | nenhum |
| Free | 2 req/s, rajada de 5 | 1.000 / dia | 20 | nenhum |
| Developer | 10 req/s, rajada de 20 | 100.000 / mês | 100 | 7 dias |
| Pro | 50 req/s, rajada de 100 | 1.000.000 / mês | 100 | 12 meses |
| Business | sob medida | sob medida | 100 | 12 meses |
| Tapelark (token de direito de acesso, por licença) | 5 req/s, rajada de 10 | 2.000 / dia | 50 | 7 dias |
O limite por segundo é um token bucket. Quando ele está vazio, você recebe 429 rate_limited com Retry-After. Quando a cota se esgota, você recebe 429 quota_exceeded com Retry-After definido como os segundos que faltam até a janela ser renovada.
Respostas autenticadas que não podem ir para cache compartilhado trazem os campos de rate limit do IETF:
RateLimit-Policy: "burst";q=10;w=1, "monthly";q=100000;w=2592000
RateLimit: "burst";r=7;t=1
Respostas que podem ir para cache compartilhado (busca, lote, tocando agora na estação, estações, estatísticas, série de saúde) não trazem cabeçalhos de rate limit, porque a borda pode servir o mesmo corpo a outro cliente. Seu saldo restante está sempre disponível em GET /v1/me.
Cache
As respostas ao vivo podem ficar em cache por 5 segundos (Cache-Control: public, max-age=5, s-maxage=5) e trazem um ETag fraco. Envie If-None-Match para receber 304 quando nada mudou; um 304 não conta para a sua cota na origem. O diretório de estações fica em cache por 60 segundos, uma estação individual por 300. O histórico é private, max-age=30. Os endpoints de saúde e de chaves são no-store.
Toda resposta traz X-Request-Id. Informe-o quando escrever para o suporte.
Erros
Os erros seguem os problem details da RFC 9457 com dois membros de extensão: code, uma string estável legível por máquina, e extras específicos de cada endpoint, como errors[] em falhas de validação ou required_tier em 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." } ]
}
A lista completa de códigos com seus significados está no catálogo de erros. Os que você vai encontrar primeiro:
| HTTP | code | Quando |
|---|---|---|
| 400 | invalid_request | Parâmetro de query desconhecido ou requisição malformada |
| 400 | invalid_cursor | Cursor malformado, expirado (24 h; 30 dias para um since_cursor do feed de correspondências) ou usado com filtros diferentes |
| 401 | unauthenticated · invalid_api_key · key_revoked | Chave ausente, desconhecida ou revogada |
| 403 | forbidden_tier | O endpoint ou o parâmetro exige um plano superior; required_tier diz qual |
| 403 | forbidden_scope | A chave foi emitida sem o escopo; required_scope diz qual |
| 404 | not_found · station_opted_out | Id desconhecido, ou a estação pediu para não ser indexada |
| 422 | validation_failed | Erros de campo em errors[] |
| 429 | rate_limited · quota_exceeded | Vá mais devagar; Retry-After em segundos |
| 503 | unavailable | Banco de dados indisponível, ou uma requisição com Idempotency-Key cancelada após 20 segundos; tente de novo após Retry-After |
GET/v1/now-playing/search sem chave public, max-age=5 · ETag
Busca ao vivo
Quais estações estão tocando q agora. Busca aproximada e sem distinção de acentos: gronemeyer manner encontra Grönemeyer – Männer. As linhas são ordenadas por similaridade e depois por quão recentemente o título apareceu. Sem paginação: o índice muda a cada poucos segundos, então as páginas pulariam ou repetiriam linhas. Aumente limit em vez disso.
| Parâmetro | Tipo | Padrão | Significado |
|---|---|---|---|
q | string, 2–120 caracteres | obrigatório | Artista, título ou ambos. Três ou mais caracteres usam correspondência por trigramas; consultas mais curtas são uma correspondência de substring. |
country | ISO 3166-1 alpha-2, repetível | qualquer | Só estações desses países. |
codec | string | qualquer | ex.: MP3, AAC. |
min_bitrate | integer | 0 | kbit/s. |
classification | enum | song | song, ident, programme, ad_break, empty, junk, unknown ou any. |
max_age_seconds | 30–900 | 180 | Só linhas cuja estação foi consultada com sucesso dentro desse número de segundos. |
limit | 1–100 | 20 | Linhas a retornar; truncated: true indica que havia mais. |
Resposta · 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 }
}
Como ler uma linha
- started_at RFC 3339 · estimativa
- Início estimado do título atual, ±
start_uncertainty_secondspara mais ou para menos. - observed_at RFC 3339
- Quando o Tuneping viu este título pela primeira vez.
- confirmed_at RFC 3339
- A consulta bem-sucedida mais recente da estação: "ainda tocando até". Use para avaliar se o dado está atual.
- track object · opcional
- Presente quando o título foi vinculado a uma gravação do MusicBrainz.
- similarity 0–1
- Corte no limiar que preferir.
GET/v1/now-playing sem chave public, max-age=5 · ETag
Tocando agora em lote
O título atual de uma lista de estações em uma única chamada, para telas de favoritas.
| Parâmetro | Tipo | Padrão | Significado |
|---|---|---|---|
station_ids | UUIDs separados por vírgula | obrigatório | Até 20 no modo anônimo ou Free, 100 a partir do Developer. Os ids são ordenados e deduplicados antes do cache, então dois clientes com as mesmas favoritas compartilham um único corpo em cache. |
max_age_seconds | 30–86400 | 900 | Linhas mais antigas que isso ainda são retornadas, mas marcadas com 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 sem chave
Tocando agora na estação
Uma linha para uma estação. 404 not_found quando nenhum título foi observado ainda, 404 station_opted_out quando a estação pediu para não ser indexada.
GET/v1/stations GET/v1/stations/{id} sem chave max-age=60 · 300
Estações
O diretório, construído a partir do diretório Radio Browser, mantido pela comunidade, e enriquecido com a forma como o Tuneping lê cada estação.
| Parâmetro | Tipo | Padrão | Significado |
|---|---|---|---|
q | string | — | Nome da estação, busca aproximada. |
country | alpha-2, repetível | qualquer | |
tag | string, repetível | qualquer | Todas as tags informadas precisam corresponder. |
codec, min_bitrate | Como na busca. | ||
has_now_playing | boolean | false | Só estações das quais o Tuneping lê títulos atualmente. |
sort | enum | clickcount | clickcount (popularidade, decrescente), name, bitrate. |
limit | 1–200 | 50 | |
cursor | string opaca | — | Vem do next_cursor da página anterior; vale por 24 horas e só com os mesmos 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 é repassado como texto a partir do diretório; o Tuneping nunca copia nem serve logos.
GET/v1/stations/{id}/history chave · developer + private, max-age=30
Histórico de execuções
As músicas que uma estação tocou, das mais recentes para as mais antigas, com início e fim estimados. A profundidade depende do plano: 7 dias no Developer, 12 meses no Pro e no Business. Pedir um from mais antigo responde 403 forbidden_tier com required_tier, nunca uma janela cortada silenciosamente.
| Parâmetro | Tipo | Padrão | Significado |
|---|---|---|---|
from, to | RFC 3339 | to = agora, from = to − 24 h | No máximo 31 dias por chamada. |
classification | enum | song | Como na busca. |
limit | 1–200 | 50 | |
cursor | string opaca | — | A janela fica congelada no 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 chave no-store
Chaves e uso
/v1/me retorna a chave, o plano e o status do titular, os limites que se aplicam e o uso até agora na janela atual. /v1/me/usage?from=&to= retorna as requisições por dia (só requisições à origem; acertos no cache da borda não são contados).
{ "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 sem chave
Saúde e estatísticas
/v1/health responde 200 com status ok ou degraded, e 503 quando o banco de dados está fora do ar; aponte seu monitor de disponibilidade para ele. /v1/health/series?minutes=60 (5–1440) retorna os números do coletor por minuto: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds e um detalhamento by_method. /v1/stats é o retrato público da cobertura exibido na página de status: total de estações, consultadas, com metadados ao vivo, mudanças de título por minuto, fração de músicas, por método, por país.
POST/v1/wishlists PUT/v1/wishlists GET/v1/wishlists/matches chave · developer + no-store
Listas de desejos
Indique uma música, um artista ou um título em texto livre e saiba quando uma estação começar a tocá-lo. Cada mudança de título é comparada com as suas listas de desejos no momento em que é lida, e uma nova lista de desejos entra em vigor em até 30 segundos. O Developer guarda 10 listas de desejos, o Pro 100, o Business 1.000. As correspondências são guardadas por 30 dias.
| Campo | Tipo | Significado |
|---|---|---|
kind | artist · track · query | artist e track recebem um id da busca (art_…, trk_…) e também capturam as grafias que o normalizador vincula a ele. query recebe texto livre, normalmente Artist - Title. |
value | string, ≤ 240 | O id ou o texto. |
match | exact · fuzzy | Só para query. Exact compara o texto normalizado; fuzzy aceita uma similaridade por trigramas de 0,6 ou mais. |
station_filter | object | station_ids (≤ 500) e/ou countries (alpha-2 em maiúsculas ou minúsculas, retornados em maiúsculas). Um título corresponde quando sua estação está listada ou fica em um país listado. Sem filtro significa todas as estações. |
delivery | array | poll (padrão), sse, webhook. A entrega por webhook é do Pro para cima e exige webhook_id e uma chave com o escopo webhooks. |
description | string, ≤ 200 | Texto seu. |
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
Uma lista explícita de station_ids faz o Tuneping ler essas estações a cada 30 segundos em vez de a cada 60 enquanto a lista de desejos estiver ativa, e a resposta informa isso com tightens: true. A união das estações que você listou tem um teto por plano (limits.tightened_stations em /v1/me); uma lista de desejos que o ultrapassaria responde 422 validation_failed com uma entrada em errors[] cujo code é tightened_stations_exceeded (campo station_filter.station_ids, ou data no PUT). Os filtros por país continuam valendo, no intervalo normal de cada estação.
GET /v1/wishlists/{id}, PATCH (filtro de estações, match, entrega, webhook, descrição, status: enabled | paused) e DELETE funcionam como esperado. PUT /v1/wishlists com {"data": [ … ]} substitui todo o seu conjunto em uma única chamada: itens inalterados mantêm o id e a contagem de correspondências, os demais são criados ou excluídos. É a forma mais fácil de sincronizar uma lista que seu app mantém localmente. O corpo dessa chamada pode ter até 1 MiB; os demais corpos de requisição são limitados a 64 KiB (413 payload_too_large).
Correspondências são um feed incremental (tail feed). GET /v1/wishlists/matches cobre todas as suas listas de desejos e GET /v1/wishlists/{id}/matches uma delas. As linhas vêm das mais antigas para as mais recentes. Guarde o next_cursor de cada resposta e envie-o de volta como since_cursor: ele nunca é null, continua válido durante a retenção de 30 dias e sobrevive a reinicializações. Sem cursor, você recebe as correspondências mais recentes. limit vai de 1 a 500, padrão 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 já vem corrigido para o momento em que o título foi visto, então um gravador pode mostrar "começou há cerca de N ± U segundos". expected_end_hint_at é started_at mais 222 segundos, a duração mediana de uma música: uma dica, não uma previsão. confidence é high quando a correspondência passou por um id de faixa ou artista vinculado, medium para artista e título normalizados, low para consultas de texto.
POST/v1/webhooks GET/v1/webhooks/{id}/deliveries chave · pro + no-store
Webhooks
O Tuneping envia eventos por POST para o seu endpoint HTTPS à medida que acontecem. O Pro tem 5 endpoints, o Business 20.
| Evento | Quando | O Pro exige |
|---|---|---|
wishlist.matched | Uma lista de desejos que indica este endpoint em webhook_id teve uma correspondência. | — |
station.title_changed | A linha de título de uma estação mudou. | filters.station_ids (≤ 200) |
track.started | Uma faixa vinculada começou. | filters.track_ids ou artist_ids (≤ 200) |
artist.started | Um artista vinculado começou. | filters.track_ids ou artist_ids (≤ 200) |
Os filtros também podem listar countries e classification. Filtros só por país e fluxos sem filtro de mudanças de título são do Business. Cada endpoint tem um orçamento diário de 50.000 eventos no Pro e 500.000 no Business; acima disso, o endpoint fica paused_budget até o próximo dia UTC e você recebe um 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
A resposta traz o secret de assinatura (whsec_…) uma única vez. As entregas seguem o Standard Webhooks: cabeçalhos webhook-id, webhook-timestamp e webhook-signature: v1,<base64>, em que a assinatura é um HMAC-SHA256 com os bytes do secret sobre {webhook-id}.{webhook-timestamp}.{body}. Rejeite timestamps com mais de 5 minutos de diferença e use webhook-id para descartar duplicatas: ele permanece o mesmo entre as novas tentativas.
{ "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_…" } }
Responda com qualquer 2xx em até 10 segundos. Redirecionamentos não são seguidos. Entregas que falham são repetidas após 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h e 24 h, cerca de três dias no total, uma de cada vez por endpoint. Um endpoint que responde 410, ou que falha em cinco dias seguidos, é desativado e você recebe um e-mail; defina status de volta como enabled com PATCH depois de corrigi-lo.
POST /v1/webhooks/{id}/rotate-secret retorna um novo secret; durante 24 horas, cada entrega traz duas assinaturas, uma por secret. POST /v1/webhooks/{id}/test envia um ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed lista 30 dias de entregas, e POST …/deliveries/{dl_id}/redeliver envia uma delas de novo. Chaves de teste (tp_test_…) só podem apontar para webhook.site.
GET/v1/stream chave · developer + text/event-stream
Stream de eventos
Os mesmos eventos em uma única resposta HTTP longa no formato server-sent events, para servidores e apps nativos. Passe station_ids (até 100) ou wishlist_id (all, ou um 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âmetro | Padrão | Significado |
|---|---|---|
station_ids | — | Ids de estações separados por vírgula, no máximo 100. |
wishlist_id | — | all ou um id de lista de desejos. Envia wishlist.matched. |
events | station.title_changed | Para streams de estações, qualquer um entre station.title_changed, track.started, artist.started. |
classification | song | song ou any, para streams de estações. |
data é o mesmo JSON que um webhook recebe. Um comentário : ping chega após 20 segundos sem eventos, então defina o timeout de inatividade do seu cliente para 60 segundos ou mais. Quando a conexão cair, reconecte com o cabeçalho Last-Event-ID definido como o último id que você viu: os eventos dos últimos 10 minutos são reenviados. Uma posição mais antiga recebe um event: gap com o id mais antigo disponível; preencha a lacuna pelo histórico ou pelo feed de correspondências. Streams abertos por chave: Developer 1, Pro 5, Business 20, Tapelark 1 por dispositivo. Um a mais responde 429 too_many_connections.
Navegadores não podem usar EventSource aqui, porque ele não consegue enviar o cabeçalho Authorization e as chaves nunca são aceitas na URL. Leia o stream com fetch().
Campos e enums
- classification enum
song·ident(nome ou slogan da estação) ·programme·ad_break·empty·junk·unknown. Só músicas entram no histórico de execuções; tudo é mantido na linha ao vivo.- match_grade enum
high·medium·none. Quão bem o título foi vinculado a uma gravação do MusicBrainz.- confidence enum
high·medium·low. Calculada por linha a partir da classificação, do grau de correspondência, de quão recentemente a estação foi confirmada e do histórico de confiabilidade da estação. Documentada para que você possa auditá-la.- method enum
- Como o Tuneping lê a estação:
icy(metadados dentro do stream),icecast_json(JSON de status do servidor),shoutcast_stats(XML de status),shoutcast_currentsong,seven_html. Identificadores internos, não recomendações; se for exibi-los, mostre-os como "no stream" e "página de status". - recordability enum
- Se o stream da estação é um stream HTTP simples e gravável do ponto de vista de um cliente de gravação. Informativo.
- Ids
- As estações usam o UUID do Radio Browser. Faixas são
trk_…, artistasart_…, eventos de execuçãope_…, chaveskey_…, titularesown_…, listas de desejoswl_…, correspondênciaswm_…, webhookswh_…, entregasdl_…, eventosevt_…. Trate todos como strings opacas. - Timestamps
- RFC 3339, UTC, com precisão de milissegundos quando a fonte a fornece.
OpenAPI
A descrição legível por máquina desta API está em /docs/openapi-v1.yaml (OpenAPI 3.1). Ela é a fonte para geração de clientes; os ids de operação são estáveis.
Atribuição e uso justo
O uso no plano Free e anônimo exibe um link visível "Data by Tuneping" onde os dados são mostrados (selo). As execuções são detectadas a partir dos metadados das estações e não são monitoramento certificado; diga "detectado a partir dos metadados da estação" onde você as apresentar como evidência. Os nomes das estações são texto; o Tuneping não fornece logos nem artes. Veja os termos.