Tuneping API v1
JSON API untuk apa yang sedang diputar stasiun radio internet saat ini, dan apa yang telah mereka putar. Base URL https://api.tuneping.com/v1. Hanya metadata: layanan ini membaca baris judul publik dari setiap stream dan tidak pernah menyentuh audio.
Mulai cepat
Siapa yang sedang memutar Madonna saat ini?
curl "https://api.tuneping.com/v1/now-playing/search?q=madonna&limit=3"
Yang sedang diputar di stasiun yang id-nya Anda ketahui (id adalah UUID stasiun Radio Browser):
curl "https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/now-playing"
Apa yang diputar sebuah stasiun dalam 24 jam terakhir (memerlukan key dengan riwayat, paket Developer ke atas):
curl -H "Authorization: Bearer tp_live_…" \
"https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/history"
Setiap respons berformat JSON. Error berformat application/problem+json (lihat Error). Parameter query yang tidak dikenal ditolak dengan 400, bukan diabaikan, sehingga salah ketik langsung terlihat.
Mendapatkan key
Layanan mandiri di tuneping.com/signup: masukkan alamat e-mail, buka tautan yang dikirim dalam 60 menit, dan key ditampilkan sekali. Dua langkah yang sama juga tersedia sebagai API: POST /v1/signup dengan {"email": "…", "plan": "free|developer|pro"} menjawab 202 dan mengirim e-mail; POST /v1/signup/verify dengan {"token": "…"} dari tautan menjawab 201 dengan key. Free tidak memerlukan apa pun lagi. Developer dan Pro berlanjut ke Stripe Checkout dari tautan di respons; tier pemilik berubah dalam satu menit setelah pembayaran dan semua key milik pemilik itu ikut berubah.
Kehilangan key? Daftar lagi dengan alamat yang sama: key dirotasi, key lama tetap berfungsi selama 24 jam. Satu live key pada Free dan Developer, lima pada Pro, dua puluh pada Business; ditambah satu key tp_test_ di setiap tier, diterbitkan atas permintaan sampai portal tersedia.
Autentikasi
Kirim key Anda sebagai bearer token, atau di X-Api-Key:
Authorization: Bearer tp_live_a1b2c3…
X-Api-Key: tp_live_a1b2c3…
Key diawali tp_live_ atau tp_test_ diikuti 43 karakter. Permintaan tanpa key diperlakukan sebagai anonim dan dibatasi per alamat IP. Key yang salah format, tidak dikenal, atau dicabut langsung menjawab 401, bahkan pada endpoint yang sebenarnya berfungsi secara anonim. Endpoint yang ditandai key di bawah menjawab 401 unauthenticated jika tidak disertai key.
Scope. Key membawa semua scope (read, exports, webhooks, wishlists) kecuali diterbitkan dengan daftar yang lebih sempit, misalnya hanya-baca; GET /v1/me mencantumkan scope sebuah key. Wishlist dan webhook memerlukan scope wishlists dan webhooks (wishlist dengan pengiriman webhook memerlukan keduanya, diperiksa sebelum paket). Key tanpa scope tersebut menjawab 403 forbidden_scope dengan required_scope, dan WWW-Authenticate membawa error="insufficient_scope".
Key pengujian. Key tp_test_ membaca data langsung yang sama dengan batas Free, dan memiliki wishlist, kecocokan, dan endpoint webhook sendiri (livemode: false). Key ini tidak pernah melihat atau mengubah yang live, sehingga test suite dapat melakukan PUT himpunan wishlist kosong tanpa menyentuh produksi. Webhook mode pengujian hanya boleh mengarah ke webhook.site.
Pengulangan. POST, PATCH, dan DELETE pada endpoint yang memerlukan key atau partner token menerima header Idempotency-Key (satu UUID per permintaan logis); endpoint pendaftaran tidak menerimanya. Pengulangan dengan key yang sama dan permintaan yang sama mendapatkan kembali respons sukses pertama, ditandai Idempotent-Replayed: true, alih-alih membuat wishlist atau endpoint kedua. Key diingat selama 24 jam; menggunakannya lagi untuk permintaan yang berbeda menghasilkan 422 idempotency_key_reused, dan pengulangan saat permintaan pertama masih berjalan menghasilkan 409 idempotency_in_progress. Error tidak diingat, sehingga pengulangan setelah error dijalankan lagi. Koneksi yang terputus tidak membatalkan permintaan yang membawa header tersebut: permintaan tetap diselesaikan dan respons sukses diingat, sehingga pengulangan Anda mendapatkannya. Permintaan yang masih berjalan setelah 20 detik dibatalkan tanpa ada yang berubah dan menjawab 503 unavailable dengan Retry-After: 1; ulangi dengan key yang sama.
Aplikasi partner (saat ini Tapelark) mengirim entitlement token yang ditandatangani alih-alih key: Authorization: Bearer TPE1-…. Token diverifikasi secara offline terhadap key Ed25519 milik partner dan dipetakan ke paket partner di tabel di bawah; API tidak pernah mengetahui siapa pengguna akhirnya. Token yang salah format atau tidak dikenal menjawab 401 token_invalid, token yang kedaluwarsa 401 token_expired (perbarui token), token untuk audience lain 401 token_audience, dan entitlement yang telah berakhir atau dicabut 403 entitlement_revoked.
Rate limit dan kuota
| Tier | Laju | Kuota | Id batch | Riwayat |
|---|---|---|---|---|
| Anonim (per IP) | 2 req/s, burst 5 | 1.000 / hari | 20 | tidak ada |
| Free | 2 req/s, burst 5 | 1.000 / hari | 20 | tidak ada |
| Developer | 10 req/s, burst 20 | 100.000 / bulan | 100 | 7 hari |
| Pro | 50 req/s, burst 100 | 1.000.000 / bulan | 100 | 12 bulan |
| Business | khusus | khusus | 100 | 12 bulan |
| Tapelark (entitlement token, per lisensi) | 5 req/s, burst 10 | 2.000 / hari | 50 | 7 hari |
Batas per detik berupa token bucket. Jika bucket kosong, Anda mendapatkan 429 rate_limited dengan Retry-After. Jika kuota habis, Anda mendapatkan 429 quota_exceeded dengan Retry-After berisi jumlah detik sampai jendela waktu direset.
Respons terautentikasi yang tidak dapat di-cache bersama membawa field rate limit IETF:
RateLimit-Policy: "burst";q=10;w=1, "monthly";q=100000;w=2592000
RateLimit: "burst";r=7;t=1
Respons yang dapat di-cache bersama (pencarian, batch, now-playing stasiun, stasiun, statistik, health series) tidak membawa header rate limit, karena edge dapat menyajikan body yang sama kepada pemanggil lain. Sisa anggaran Anda selalu tersedia di GET /v1/me.
Caching
Respons langsung dapat di-cache selama 5 detik (Cache-Control: public, max-age=5, s-maxage=5) dan membawa ETag lemah. Kirim If-None-Match untuk mendapatkan 304 jika tidak ada yang berubah; respons 304 tidak dihitung terhadap kuota Anda di origin. Direktori stasiun di-cache selama 60 detik, satu stasiun selama 300. Riwayat bernilai private, max-age=30. Endpoint health dan key bernilai no-store.
Setiap respons membawa X-Request-Id. Sertakan nilai itu saat Anda menghubungi dukungan.
Error
Error mengikuti problem details RFC 9457 dengan dua anggota ekstensi: code, string stabil yang dapat dibaca mesin, dan tambahan khusus endpoint seperti errors[] pada kegagalan validasi atau required_tier pada 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." } ]
}
Daftar lengkap kode beserta artinya ada di katalog error. Yang akan pertama kali Anda temui:
| HTTP | code | Kapan |
|---|---|---|
| 400 | invalid_request | Parameter query tidak dikenal atau permintaan salah format |
| 400 | invalid_cursor | Cursor salah format, kedaluwarsa (24 h; 30 hari untuk since_cursor feed kecocokan) atau digunakan dengan filter yang berbeda |
| 401 | unauthenticated · invalid_api_key · key_revoked | Key tidak ada, tidak dikenal, atau dicabut |
| 403 | forbidden_tier | Endpoint atau parameter memerlukan paket yang lebih tinggi; required_tier menyebutkan yang mana |
| 403 | forbidden_scope | Key diterbitkan tanpa scope tersebut; required_scope menyebutkan yang mana |
| 404 | not_found · station_opted_out | Id tidak dikenal, atau stasiun meminta untuk tidak diindeks |
| 422 | validation_failed | Error per field di errors[] |
| 429 | rate_limited · quota_exceeded | Perlambat; Retry-After dalam detik |
| 503 | unavailable | Database tidak tersedia, atau permintaan dengan Idempotency-Key dibatalkan setelah 20 detik; ulangi setelah Retry-After |
GET/v1/now-playing/search tanpa key public, max-age=5 · ETag
Pencarian langsung
Stasiun mana yang sedang memutar q saat ini. Fuzzy dan tidak peka aksen: gronemeyer manner menemukan Grönemeyer – Männer. Baris diurutkan berdasarkan kemiripan, lalu berdasarkan seberapa baru judul itu muncul. Tanpa paginasi: indeks berubah setiap beberapa detik, sehingga halaman bisa terlewat atau berulang. Naikkan limit sebagai gantinya.
| Parameter | Tipe | Default | Arti |
|---|---|---|---|
q | string, 2–120 karakter | wajib | Artis, judul, atau keduanya. Tiga karakter atau lebih menggunakan pencocokan trigram; query yang lebih pendek berupa pencocokan substring. |
country | ISO 3166-1 alpha-2, dapat diulang | semua | Hanya stasiun dari negara-negara ini. |
codec | string | semua | mis. MP3, AAC. |
min_bitrate | integer | 0 | kbit/s. |
classification | enum | song | song, ident, programme, ad_break, empty, junk, unknown atau any. |
max_age_seconds | 30–900 | 180 | Hanya baris yang stasiunnya berhasil di-poll dalam rentang detik ini. |
limit | 1–100 | 20 | Jumlah baris yang dikembalikan; truncated: true menandakan masih ada lagi. |
Respons · 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 }
}
Membaca sebuah baris
- started_at RFC 3339 · perkiraan
- Perkiraan waktu mulai judul saat ini, ±
start_uncertainty_secondske arah mana pun. - observed_at RFC 3339
- Kapan Tuneping pertama kali melihat judul ini.
- confirmed_at RFC 3339
- Polling sukses terakhir untuk stasiun tersebut: “masih diputar per waktu ini”. Gunakan untuk menilai kesegaran data.
- track object · opsional
- Ada jika judul telah ditautkan ke rekaman MusicBrainz.
- similarity 0–1
- Potong pada ambang batas Anda sendiri.
GET/v1/now-playing tanpa key public, max-age=5 · ETag
Batch now-playing
Judul saat ini untuk daftar stasiun dalam satu panggilan, untuk layar favorit.
| Parameter | Tipe | Default | Arti |
|---|---|---|---|
station_ids | UUID dipisahkan koma | wajib | Hingga 20 untuk anonim atau Free, 100 mulai Developer. Id diurutkan dan dihapus duplikatnya sebelum di-cache, sehingga dua klien dengan favorit yang sama berbagi satu body yang di-cache. |
max_age_seconds | 30–86400 | 900 | Baris yang lebih lama dari ini tetap dikembalikan tetapi ditandai 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 tanpa key
Now-playing stasiun
Satu baris untuk satu stasiun. 404 not_found jika belum ada judul yang teramati, 404 station_opted_out jika stasiun meminta untuk tidak diindeks.
GET/v1/stations GET/v1/stations/{id} tanpa key max-age=60 · 300
Stasiun
Direktori, dibangun dari direktori Radio Browser yang dikelola komunitas dan diperkaya dengan cara Tuneping membaca setiap stasiun.
| Parameter | Tipe | Default | Arti |
|---|---|---|---|
q | string | — | Nama stasiun, fuzzy. |
country | alpha-2, dapat diulang | semua | |
tag | string, dapat diulang | semua | Semua tag yang diberikan harus cocok. |
codec, min_bitrate | Seperti pada pencarian. | ||
has_now_playing | boolean | false | Hanya stasiun yang judulnya saat ini dibaca oleh Tuneping. |
sort | enum | clickcount | clickcount (popularitas, menurun), name, bitrate. |
limit | 1–200 | 50 | |
cursor | string opaque | — | Dari next_cursor halaman sebelumnya; berlaku 24 jam dan hanya dengan filter yang sama. |
{ "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 diteruskan sebagai teks dari direktori; Tuneping tidak pernah menyalin atau menyajikan logo.
GET/v1/stations/{id}/history key · developer + private, max-age=30
Riwayat pemutaran
Lagu yang diputar sebuah stasiun, terbaru lebih dulu, dengan perkiraan waktu mulai dan selesai. Kedalamannya bergantung pada paket: 7 hari pada Developer, 12 bulan pada Pro dan Business. Meminta from yang lebih lama menjawab 403 forbidden_tier dengan required_tier, tidak pernah berupa jendela waktu yang dipangkas diam-diam.
| Parameter | Tipe | Default | Arti |
|---|---|---|---|
from, to | RFC 3339 | to = sekarang, from = to − 24 h | Paling banyak 31 hari per panggilan. |
classification | enum | song | Seperti pada pencarian. |
limit | 1–200 | 50 | |
cursor | string opaque | — | Jendela waktu dibekukan di dalam 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 key no-store
Key dan penggunaan
/v1/me mengembalikan key, paket dan status pemiliknya, batas yang berlaku, dan penggunaan sejauh ini dalam jendela waktu saat ini. /v1/me/usage?from=&to= mengembalikan jumlah permintaan per hari (hanya permintaan ke origin; cache hit di edge tidak dihitung).
{ "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 tanpa key
Health dan statistik
/v1/health menjawab 200 dengan status ok atau degraded, 503 jika database mati; arahkan monitor uptime Anda ke sana. /v1/health/series?minutes=60 (5–1440) mengembalikan angka poller per menit: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds, dan rincian by_method. /v1/stats adalah snapshot cakupan publik yang ditampilkan di halaman status: total stasiun, yang di-poll, yang memiliki metadata langsung, perubahan judul per menit, porsi lagu, per metode, per negara.
POST/v1/wishlists PUT/v1/wishlists GET/v1/wishlists/matches key · developer + no-store
Wishlist
Sebutkan sebuah lagu, artis, atau judul teks bebas dan ketahui kapan sebuah stasiun mulai memutarnya. Setiap perubahan judul dicocokkan dengan wishlist Anda saat dibaca, dan wishlist baru aktif dalam 30 detik. Developer menyimpan 10 wishlist, Pro 100, Business 1.000. Kecocokan disimpan selama 30 hari.
| Field | Tipe | Arti |
|---|---|---|
kind | artist · track · query | artist dan track menerima id dari pencarian (art_…, trk_…) dan juga menangkap ejaan lain yang ditautkan normalizer ke id tersebut. query menerima teks bebas, biasanya Artist - Title. |
value | string, ≤ 240 | Id atau teksnya. |
match | exact · fuzzy | Hanya untuk query. Exact membandingkan teks yang telah dinormalisasi; fuzzy menerima kemiripan trigram 0,6 atau lebih. |
station_filter | object | station_ids (≤ 500) dan/atau countries (alpha-2 dengan huruf besar atau kecil, dikembalikan dalam huruf besar). Sebuah judul cocok jika stasiunnya tercantum atau berada di negara yang tercantum. Tanpa filter berarti semua stasiun. |
delivery | array | poll (default), sse, webhook. Pengiriman webhook tersedia mulai Pro dan memerlukan webhook_id serta key dengan scope webhooks. |
description | string, ≤ 200 | Milik Anda. |
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
Daftar station_ids yang eksplisit membuat Tuneping membaca stasiun-stasiun tersebut setiap 30 detik, bukan setiap 60 detik, selama wishlist aktif, dan respons menyatakannya dengan tightens: true. Gabungan stasiun yang Anda cantumkan dibatasi per paket (limits.tightened_stations di /v1/me); wishlist yang akan melampauinya menjawab 422 validation_failed dengan entri errors[] yang code-nya tightened_stations_exceeded (field station_filter.station_ids, atau data pada PUT). Filter negara tetap cocok, pada interval normal setiap stasiun.
GET /v1/wishlists/{id}, PATCH (filter stasiun, match, pengiriman, webhook, deskripsi, status: enabled | paused) dan DELETE berfungsi seperti yang diharapkan. PUT /v1/wishlists dengan {"data": [ … ]} mengganti seluruh himpunan Anda dalam satu panggilan: item yang tidak berubah mempertahankan id dan jumlah kecocokannya, sisanya dibuat atau dihapus. Ini cara termudah untuk menyinkronkan daftar yang disimpan aplikasi Anda secara lokal. Body-nya boleh hingga 1 MiB; body permintaan lain dibatasi 64 KiB (413 payload_too_large).
Kecocokan berupa tail feed. GET /v1/wishlists/matches mencakup semua wishlist Anda dan GET /v1/wishlists/{id}/matches salah satunya. Baris datang dari yang terlama. Simpan next_cursor dari setiap jawaban dan kirimkan kembali sebagai since_cursor: nilainya tidak pernah null, tetap berlaku selama masa retensi 30 hari, dan bertahan saat restart. Tanpa cursor, Anda mendapatkan kecocokan terbaru. limit bernilai 1–500, default 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 sudah dikoreksi terhadap kapan judul itu terlihat, sehingga aplikasi perekam dapat menampilkan “dimulai sekitar N ± U detik yang lalu”. expected_end_hint_at adalah started_at ditambah 222 detik, durasi median sebuah lagu: sebuah petunjuk, bukan prediksi. confidence bernilai high jika kecocokan terjadi melalui id lagu atau artis yang tertaut, medium untuk artis dan judul yang dinormalisasi, low untuk query teks.
POST/v1/webhooks GET/v1/webhooks/{id}/deliveries key · pro + no-store
Webhook
Tuneping mengirim (POST) event ke endpoint HTTPS Anda saat event itu terjadi. Pro memiliki 5 endpoint, Business 20.
| Event | Kapan | Syarat di Pro |
|---|---|---|
wishlist.matched | Wishlist yang menyebut endpoint ini di webhook_id menemukan kecocokan. | — |
station.title_changed | Baris judul sebuah stasiun berubah. | filters.station_ids (≤ 200) |
track.started | Sebuah lagu yang tertaut mulai diputar. | filters.track_ids atau artist_ids (≤ 200) |
artist.started | Lagu dari artis yang tertaut mulai diputar. | filters.track_ids atau artist_ids (≤ 200) |
Filter juga dapat mencantumkan countries dan classification. Filter yang hanya berisi negara dan stream perubahan judul tanpa filter tersedia di Business. Setiap endpoint memiliki anggaran harian 50.000 event pada Pro dan 500.000 pada Business; setelah melampauinya, endpoint berstatus paused_budget sampai hari UTC berikutnya dan Anda menerima 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
Jawabannya membawa secret penandatanganan (whsec_…) satu kali. Pengiriman mengikuti Standard Webhooks: header webhook-id, webhook-timestamp, dan webhook-signature: v1,<base64>, dengan tanda tangan berupa HMAC-SHA256 menggunakan byte dari secret atas {webhook-id}.{webhook-timestamp}.{body}. Tolak timestamp yang selisihnya lebih dari 5 menit, dan gunakan webhook-id untuk membuang duplikat: nilainya tetap sama di setiap pengulangan.
{ "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_…" } }
Jawab dengan 2xx apa pun dalam 10 detik. Redirect tidak diikuti. Pengiriman yang gagal diulang setelah 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h, dan 24 h, sekitar tiga hari secara keseluruhan, satu per satu per endpoint. Endpoint yang menjawab 410, atau gagal selama lima hari berturut-turut, dinonaktifkan dan Anda menerima e-mail; setel status kembali ke enabled dengan PATCH setelah diperbaiki.
POST /v1/webhooks/{id}/rotate-secret mengembalikan secret baru; selama 24 jam setiap pengiriman membawa dua tanda tangan, satu per secret. POST /v1/webhooks/{id}/test mengirim ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed mencantumkan pengiriman selama 30 hari, dan POST …/deliveries/{dl_id}/redeliver mengirim ulang satu pengiriman. Key pengujian (tp_test_…) hanya boleh mengarah ke webhook.site.
GET/v1/stream key · developer + text/event-stream
Event stream
Event yang sama melalui satu respons HTTP panjang dalam format server-sent events, untuk server dan aplikasi native. Kirim station_ids (hingga 100) atau wishlist_id (all, atau satu 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
| Parameter | Default | Arti |
|---|---|---|
station_ids | — | Id stasiun dipisahkan koma, paling banyak 100. |
wishlist_id | — | all atau id wishlist. Mengirim wishlist.matched. |
events | station.title_changed | Untuk stream stasiun, salah satu atau beberapa dari station.title_changed, track.started, artist.started. |
classification | song | song atau any, untuk stream stasiun. |
data adalah JSON yang sama dengan yang diterima webhook. Komentar : ping datang setelah 20 detik tanpa event, jadi setel idle timeout klien Anda ke 60 detik atau lebih. Jika koneksi terputus, sambungkan ulang dengan header Last-Event-ID berisi id terakhir yang Anda lihat: event dari 10 menit terakhir diputar ulang. Posisi yang lebih lama mendapatkan event: gap dengan id paling awal yang tersedia; isi kekosongannya dari riwayat atau feed kecocokan. Stream terbuka per key: Developer 1, Pro 5, Business 20, Tapelark 1 per perangkat. Satu lagi di atas itu menjawab 429 too_many_connections.
Browser tidak dapat menggunakan EventSource di sini, karena EventSource tidak dapat mengirim header Authorization dan key tidak pernah diterima di URL. Baca stream dengan fetch() sebagai gantinya.
Field dan enum
- classification enum
song·ident(nama stasiun atau slogan) ·programme·ad_break·empty·junk·unknown. Hanya lagu yang menjadi riwayat pemutaran; semuanya disimpan di baris langsung.- match_grade enum
high·medium·none. Seberapa baik judul ditautkan ke rekaman MusicBrainz.- confidence enum
high·medium·low. Dihitung per baris dari klasifikasi, match grade, seberapa baru stasiun dikonfirmasi, dan rekam jejak stasiun. Didokumentasikan agar Anda dapat mengauditnya.- method enum
- Cara Tuneping membaca stasiun:
icy(metadata di dalam stream),icecast_json(JSON status server),shoutcast_stats(XML status),shoutcast_currentsong,seven_html. Identifier internal, bukan dukungan terhadap produk; tampilkan sebagai “di dalam stream” dan “halaman status” jika Anda menampilkannya. - recordability enum
- Apakah stream stasiun merupakan stream HTTP biasa yang dapat direkam dari sudut pandang klien perekam. Bersifat informatif.
- Id
- Stasiun menggunakan UUID Radio Browser-nya. Lagu adalah
trk_…, artisart_…, event pemutaranpe_…, keykey_…, pemilikown_…, wishlistwl_…, kecocokanwm_…, webhookwh_…, pengirimandl_…, eventevt_…. Perlakukan semuanya sebagai string opaque. - Timestamp
- RFC 3339, UTC, presisi milidetik jika sumbernya memilikinya.
OpenAPI
Deskripsi API ini yang dapat dibaca mesin ada di /docs/openapi-v1.yaml (OpenAPI 3.1). File ini menjadi sumber untuk pembuatan klien; operation id-nya stabil.
Atribusi dan penggunaan wajar
Penggunaan tier Free dan anonim menampilkan tautan “Data by Tuneping” yang terlihat di tempat data ditampilkan (badge). Pemutaran dideteksi dari metadata stasiun dan bukan pemantauan tersertifikasi; sebutkan “terdeteksi dari metadata stasiun” saat Anda menyajikannya sebagai bukti. Nama stasiun berupa teks; Tuneping tidak menyediakan logo atau artwork. Lihat ketentuan.