Tuneping API v1

Một JSON API cho biết các đài radio internet đang phát gì ngay lúc này, và đã phát gì. Base URL https://api.tuneping.com/v1. Chỉ metadata: dịch vụ đọc dòng tiêu đề công khai của mỗi luồng và không bao giờ chạm vào âm thanh.

Beta công khaiCác endpoint trực tiếp (tìm kiếm, theo lô, now-playing của đài, danh bạ đài, health và stats) hoạt động mà không cần khóa, giới hạn theo địa chỉ IP. Khóa được cấp theo hình thức tự phục vụ trên trang đăng ký: Free cấp ngay, Developer và Pro sau Stripe Checkout. Business: hãy viết cho [email protected].

Bắt đầu nhanh

Ai đang phát Madonna ngay lúc này?

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

Bài đang phát trên một đài mà bạn biết id (id là UUID đài của Radio Browser):

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

Một đài đã phát gì trong 24 giờ qua (cần khóa có quyền xem lịch sử, gói Developer trở lên):

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

Mọi phản hồi đều là JSON. Lỗi có dạng application/problem+json (xem Lỗi). Query parameter không xác định bị từ chối với 400 thay vì bị bỏ qua, nên lỗi gõ sai lộ ra ngay.

Lấy khóa

Tự phục vụ tại tuneping.com/signup: nhập địa chỉ e-mail, mở liên kết được gửi tới trong vòng 60 phút, và khóa được hiển thị một lần. Hai bước này cũng là một API: POST /v1/signup với {"email": "…", "plan": "free|developer|pro"} trả về 202 và gửi thư; POST /v1/signup/verify với {"token": "…"} lấy từ liên kết trả về 201 kèm khóa. Gói Free không cần gì thêm. Developer và Pro chuyển tiếp sang Stripe Checkout từ liên kết trong phản hồi; gói của chủ sở hữu thay đổi trong vòng một phút sau khi thanh toán và mọi khóa của chủ sở hữu đó đều theo.

Mất khóa? Đăng ký lại bằng cùng địa chỉ: khóa được xoay vòng, khóa cũ vẫn hoạt động thêm 24 giờ. Một khóa live ở gói Free và Developer, năm ở Pro, hai mươi ở Business; cộng thêm một khóa tp_test_ ở mọi gói, cấp theo yêu cầu cho đến khi cổng quản lý ra mắt.

Xác thực

Gửi khóa của bạn dưới dạng bearer token, hoặc trong X-Api-Key:

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

Khóa bắt đầu bằng tp_live_ hoặc tp_test_, theo sau là 43 ký tự. Yêu cầu không có khóa được coi là ẩn danh và bị giới hạn theo địa chỉ IP. Khóa sai định dạng, không xác định hoặc đã bị thu hồi sẽ nhận 401 ngay lập tức, kể cả trên các endpoint vốn hoạt động được ở chế độ ẩn danh. Các endpoint được đánh dấu khóa bên dưới trả về 401 unauthenticated nếu không có khóa.

Phạm vi (scope). Khóa mang mọi phạm vi (read, exports, webhooks, wishlists) trừ khi được cấp với danh sách hẹp hơn, ví dụ chỉ đọc; GET /v1/me liệt kê các phạm vi của khóa. Danh sách mong muốn và webhook cần phạm vi wishlists và webhooks (một danh sách mong muốn gửi qua webhook cần cả hai, được kiểm tra trước gói). Khóa không có phạm vi cần thiết trả về 403 forbidden_scope kèm required_scope, và WWW-Authenticate mang error="insufficient_scope".

Khóa thử nghiệm. Khóa tp_test_ đọc cùng dữ liệu live với giới hạn của gói Free, và có danh sách mong muốn, kết quả khớp và endpoint webhook riêng (livemode: false). Nó không bao giờ thấy hay thay đổi những thứ của chế độ live, nên một bộ test có thể PUT một tập danh sách mong muốn rỗng mà không động tới production. Webhook ở chế độ thử nghiệm chỉ được trỏ tới webhook.site.

Thử lại. POST, PATCH và DELETE trên các endpoint cần khóa hoặc token đối tác chấp nhận header Idempotency-Key (một UUID cho mỗi yêu cầu logic); các endpoint đăng ký không nhận header này. Một lần thử lại với cùng Idempotency-Key và cùng yêu cầu sẽ nhận lại phản hồi thành công đầu tiên, được đánh dấu Idempotent-Replayed: true, thay vì tạo ra danh sách mong muốn hay endpoint thứ hai. Idempotency-Key được ghi nhớ trong 24 giờ; dùng lại nó cho một yêu cầu khác sẽ nhận 422 idempotency_key_reused, và thử lại trong khi yêu cầu đầu tiên vẫn đang chạy sẽ nhận 409 idempotency_in_progress. Lỗi không được ghi nhớ, nên thử lại sau một lỗi sẽ chạy lại yêu cầu. Kết nối bị ngắt không hủy yêu cầu có mang header này: yêu cầu vẫn chạy xong và phản hồi thành công được ghi nhớ, nên lần thử lại của bạn sẽ nhận được nó. Yêu cầu vẫn còn chạy sau 20 giây sẽ bị hủy mà không thay đổi gì và trả về 503 unavailable kèm Retry-After: 1; hãy thử lại với cùng Idempotency-Key.

Ứng dụng đối tác (hiện là Tapelark) gửi một entitlement token đã ký thay cho khóa: Authorization: Bearer TPE1-…. Token được xác minh offline bằng khóa Ed25519 của đối tác và tương ứng với gói đối tác trong bảng bên dưới; API không bao giờ biết người dùng cuối là ai. Token sai định dạng hoặc không xác định trả về 401 token_invalid, token hết hạn trả về 401 token_expired (hãy làm mới), token dành cho audience khác trả về 401 token_audience, và quyền sử dụng đã hết hiệu lực hoặc bị thu hồi trả về 403 entitlement_revoked.

Giới hạn tốc độ và hạn mức

GóiTốc độHạn mứcId theo lôLịch sử
Ẩn danh (theo IP)2 req/s, burst 51.000 / ngày20không có
Free2 req/s, burst 51.000 / ngày20không có
Developer10 req/s, burst 20100.000 / tháng1007 ngày
Pro50 req/s, burst 1001.000.000 / tháng10012 tháng
Businesstùy chỉnhtùy chỉnh10012 tháng
Tapelark (entitlement token, theo giấy phép)5 req/s, burst 102.000 / ngày507 ngày

Giới hạn theo giây là một token bucket. Khi bucket cạn, bạn nhận 429 rate_limited kèm Retry-After. Khi hạn mức đã dùng hết, bạn nhận 429 quota_exceeded với Retry-After là số giây đến khi cửa sổ được đặt lại.

Các phản hồi đã xác thực mà không thể cache chung sẽ mang các trường giới hạn tốc độ của IETF:

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

Các phản hồi có thể cache chung (tìm kiếm, theo lô, now-playing của đài, danh sách đài, stats, health series) không mang header giới hạn tốc độ, vì edge có thể phục vụ cùng một body cho người gọi khác. Ngân sách còn lại của bạn luôn có tại GET /v1/me.

Cache

Phản hồi trực tiếp có thể cache trong 5 giây (Cache-Control: public, max-age=5, s-maxage=5) và mang một ETag yếu. Gửi If-None-Match để nhận 304 khi không có gì thay đổi; một phản hồi 304 không bị tính vào hạn mức của bạn tại origin. Danh bạ đài được cache 60 giây, một đài đơn lẻ 300 giây. Lịch sử là private, max-age=30. Các endpoint health và khóa là no-store.

Mọi phản hồi đều mang X-Request-Id. Hãy gửi kèm giá trị này khi bạn liên hệ hỗ trợ.

Lỗi

Lỗi tuân theo RFC 9457 problem details với hai thành phần mở rộng: code, một chuỗi ổn định cho máy đọc, và các trường bổ sung riêng theo endpoint như errors[] khi xác thực dữ liệu thất bại hoặc required_tier với 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." } ]
}

Danh sách đầy đủ các mã cùng ý nghĩa nằm trong danh mục lỗi. Những mã bạn sẽ gặp đầu tiên:

HTTPcodeKhi nào
400invalid_requestQuery parameter không xác định hoặc yêu cầu sai định dạng
400invalid_cursorCursor sai định dạng, hết hạn (24 h; 30 ngày với since_cursor của feed kết quả khớp) hoặc dùng với bộ lọc khác
401unauthenticated · invalid_api_key · key_revokedThiếu khóa, khóa không xác định hoặc đã bị thu hồi
403forbidden_tierEndpoint hoặc tham số cần gói cao hơn; required_tier cho biết gói nào
403forbidden_scopeKhóa được cấp mà không có phạm vi cần thiết; required_scope cho biết phạm vi nào
404not_found · station_opted_outId không xác định, hoặc đài đã yêu cầu không được lập chỉ mục
422validation_failedLỗi theo trường trong errors[]
429rate_limited · quota_exceededHãy chậm lại; Retry-After tính bằng giây
503unavailableCơ sở dữ liệu không khả dụng, hoặc một yêu cầu có Idempotency-Key bị hủy sau 20 giây; thử lại sau Retry-After

GET/v1/now-playing/search không cần khóa public, max-age=5 · ETag

Những đài nào đang phát q ngay lúc này. Khớp gần đúng và không phân biệt dấu: gronemeyer manner tìm ra Grönemeyer – Männer. Các dòng được sắp xếp theo độ tương đồng, sau đó theo mức độ gần đây mà tiêu đề xuất hiện. Không phân trang: chỉ mục thay đổi sau mỗi vài giây, nên các trang sẽ bị bỏ sót hoặc lặp lại. Thay vào đó, hãy tăng limit.

Tham sốKiểuMặc địnhÝ nghĩa
qchuỗi, 2–120 ký tựbắt buộcNghệ sĩ, tiêu đề hoặc cả hai. Từ ba ký tự trở lên dùng so khớp trigram; truy vấn ngắn hơn là so khớp chuỗi con.
countryISO 3166-1 alpha-2, lặp lại đượcbất kỳChỉ các đài từ những quốc gia này.
codecchuỗibất kỳví dụ MP3, AAC.
min_bitratesố nguyên0kbit/s.
classificationenumsongsong, ident, programme, ad_break, empty, junk, unknown hoặc any.
max_age_seconds30–900180Chỉ các dòng có đài được poll thành công trong vòng số giây này.
limit1–10020Số dòng trả về; truncated: true cho biết còn nhiều hơn.

Phản hồi · 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 một dòng

started_at RFC 3339 · ước tính
Thời điểm bắt đầu ước tính của tiêu đề hiện tại, sai lệch ± start_uncertainty_seconds về cả hai phía.
observed_at RFC 3339
Thời điểm Tuneping thấy tiêu đề này lần đầu.
confirmed_at RFC 3339
Lần poll thành công gần nhất của đài: "vẫn đang phát tính đến lúc". Dùng trường này để đánh giá độ mới.
track object · tùy chọn
Có mặt khi tiêu đề được liên kết với một recording của MusicBrainz.
similarity 0–1
Tự cắt theo ngưỡng của bạn.

GET/v1/now-playing không cần khóa public, max-age=5 · ETag

Now-playing theo lô

Tiêu đề hiện tại cho một danh sách đài trong một lần gọi, dành cho màn hình đài yêu thích.

Tham sốKiểuMặc địnhÝ nghĩa
station_idscác UUID cách nhau bằng dấu phẩybắt buộcTối đa 20 khi ẩn danh hoặc Free, 100 từ Developer trở lên. Id được sắp xếp và loại trùng trước khi cache, nên hai client có cùng danh sách yêu thích dùng chung một body đã cache.
max_age_seconds30–86400900Các dòng cũ hơn giá trị này vẫn được trả về nhưng được gắn cờ 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 không cần khóa

Now-playing của đài

Một dòng cho một đài. 404 not_found khi chưa quan sát được tiêu đề nào, 404 station_opted_out khi đài đã yêu cầu không được lập chỉ mục.

GET/v1/stations GET/v1/stations/{id} không cần khóa max-age=60 · 300

Đài

Danh bạ, xây dựng từ danh bạ Radio Browser do cộng đồng duy trì và bổ sung thông tin về cách Tuneping đọc từng đài.

Tham sốKiểuMặc địnhÝ nghĩa
qchuỗi—Tên đài, khớp gần đúng.
countryalpha-2, lặp lại đượcbất kỳ
tagchuỗi, lặp lại đượcbất kỳMọi tag đã cho đều phải khớp.
codec, min_bitrateNhư trong tìm kiếm.
has_now_playingbooleanfalseChỉ các đài mà Tuneping hiện đang đọc được tiêu đề.
sortenumclickcountclickcount (độ phổ biến, giảm dần), name, bitrate.
limit1–20050
cursorchuỗi mờ (opaque)—Lấy từ next_cursor của trang trước; có hiệu lực 24 giờ và chỉ với cùng bộ lọc.
{ "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 được chuyển tiếp nguyên dạng văn bản từ danh bạ; Tuneping không bao giờ sao chép hay phục vụ logo.

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

Lịch sử phát

Các bài hát một đài đã phát, mới nhất trước, kèm thời điểm bắt đầu và kết thúc ước tính. Độ sâu tùy theo gói: 7 ngày với Developer, 12 tháng với Pro và Business. Yêu cầu from cũ hơn sẽ nhận 403 forbidden_tier kèm required_tier, không bao giờ âm thầm thu hẹp khoảng thời gian.

Tham sốKiểuMặc địnhÝ nghĩa
from, toRFC 3339to = hiện tại, from = to − 24 hTối đa 31 ngày mỗi lần gọi.
classificationenumsongNhư trong tìm kiếm.
limit1–20050
cursorchuỗi mờ (opaque)—Khoảng thời gian được cố định trong 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 khóa no-store

Khóa và mức sử dụng

/v1/me trả về khóa, gói và trạng thái của chủ sở hữu khóa, các giới hạn áp dụng và mức sử dụng tính đến hiện tại trong cửa sổ hiện hành. /v1/me/usage?from=&to= trả về số yêu cầu mỗi ngày (chỉ yêu cầu tới origin; lượt trúng cache tại edge không được tính).

{ "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 không cần khóa

Health và stats

/v1/health trả về 200 với status là ok hoặc degraded, và 503 khi cơ sở dữ liệu ngừng hoạt động; hãy trỏ công cụ giám sát uptime của bạn vào đây. /v1/health/series?minutes=60 (5–1440) trả về số liệu của poller theo từng phút: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds và bảng phân tích by_method. /v1/stats là ảnh chụp độ phủ công khai hiển thị trên trang trạng thái: tổng số đài, số đài được poll, số đài có metadata trực tiếp, số lần đổi tiêu đề mỗi phút, tỷ lệ bài hát, theo phương thức, theo quốc gia.

POST/v1/wishlists PUT/v1/wishlists GET/v1/wishlists/matches khóa · developer + no-store

Danh sách mong muốn

Nêu tên một bài hát, một nghệ sĩ hoặc một tiêu đề dạng văn bản tự do và biết khi nào một đài bắt đầu phát nó. Mỗi lần đổi tiêu đề đều được đối chiếu với các danh sách mong muốn của bạn ngay khi được đọc, và một danh sách mong muốn mới có hiệu lực trong vòng 30 giây. Developer giữ được 10 danh sách mong muốn, Pro 100, Business 1.000. Kết quả khớp được lưu 30 ngày.

TrườngKiểuÝ nghĩa
kindartist · track · queryartist và track nhận một id từ tìm kiếm (art_…, trk_…) và cũng bắt được các cách viết mà bộ chuẩn hóa liên kết tới id đó. query nhận văn bản tự do, thường là Artist - Title.
valuechuỗi, ≤ 240Id hoặc văn bản.
matchexact · fuzzyChỉ với query. Exact so sánh văn bản đã chuẩn hóa; fuzzy chấp nhận độ tương đồng trigram từ 0,6 trở lên.
station_filterobjectstation_ids (≤ 500) và/hoặc countries (alpha-2 viết hoa hay thường đều được, trả về dạng viết hoa). Một tiêu đề khớp khi đài của nó có trong danh sách hoặc thuộc một quốc gia có trong danh sách. Không có bộ lọc nghĩa là mọi đài.
deliverymảngpoll (mặc định), sse, webhook. Gửi qua webhook dành cho Pro trở lên và cần webhook_id cùng một khóa có phạm vi webhooks.
descriptionchuỗi, ≤ 200Của riêng bạn.
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

Một danh sách station_ids tường minh khiến Tuneping đọc các đài đó cứ 30 giây một lần thay vì 60 giây trong khi danh sách mong muốn được bật, và phản hồi cho biết điều đó bằng tightens: true. Hợp của tất cả các đài bạn liệt kê bị giới hạn theo gói (limits.tightened_stations trong /v1/me); một danh sách mong muốn vượt quá giới hạn đó sẽ nhận 422 validation_failed với một mục errors[] có code là tightened_stations_exceeded (trường station_filter.station_ids, hoặc data khi dùng PUT). Bộ lọc theo quốc gia vẫn khớp, theo chu kỳ bình thường của từng đài.

GET /v1/wishlists/{id}, PATCH (bộ lọc đài, match, delivery, webhook, description, status: enabled | paused) và DELETE hoạt động như bạn mong đợi. PUT /v1/wishlists với {"data": [ … ]} thay thế toàn bộ tập của bạn trong một lần gọi: các mục không đổi giữ nguyên id và số lần khớp, phần còn lại được tạo mới hoặc xóa. Đây là cách dễ nhất để đồng bộ một danh sách mà ứng dụng của bạn lưu cục bộ. Body của nó có thể lên tới 1 MiB; body của các yêu cầu khác giới hạn ở 64 KiB (413 payload_too_large).

Kết quả khớp là một feed dạng tail. GET /v1/wishlists/matches bao quát mọi danh sách mong muốn của bạn và GET /v1/wishlists/{id}/matches cho một danh sách. Các dòng đến theo thứ tự cũ nhất trước. Hãy giữ next_cursor từ mỗi phản hồi và gửi lại dưới dạng since_cursor: nó không bao giờ null, có hiệu lực trong suốt thời gian lưu giữ 30 ngày và tồn tại qua các lần khởi động lại. Không có cursor, bạn nhận các kết quả khớp mới nhất. limit là 1–500, mặc định 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 đã được hiệu chỉnh theo thời điểm tiêu đề được thấy, nên một ứng dụng ghi âm có thể hiển thị "bắt đầu khoảng N ± U giây trước". expected_end_hint_at là started_at cộng 222 giây, độ dài trung vị của một bài hát: một gợi ý, không phải dự đoán. confidence là high khi kết quả khớp đi qua một id bản nhạc hoặc nghệ sĩ đã liên kết, medium khi khớp theo nghệ sĩ và tiêu đề đã chuẩn hóa, low với truy vấn văn bản.

POST/v1/webhooks GET/v1/webhooks/{id}/deliveries khóa · pro + no-store

Webhook

Tuneping gửi sự kiện tới endpoint HTTPS của bạn ngay khi chúng xảy ra. Pro có 5 endpoint, Business 20.

Sự kiệnKhi nàoPro cần
wishlist.matchedMột danh sách mong muốn có nêu endpoint này trong webhook_id đã khớp.—
station.title_changedDòng tiêu đề của một đài đã thay đổi.filters.station_ids (≤ 200)
track.startedMột bản nhạc đã liên kết bắt đầu phát.filters.track_ids hoặc artist_ids (≤ 200)
artist.startedMột nghệ sĩ đã liên kết bắt đầu phát.filters.track_ids hoặc artist_ids (≤ 200)

Bộ lọc cũng có thể liệt kê countries và classification. Bộ lọc chỉ theo quốc gia và luồng đổi tiêu đề không lọc dành cho Business. Mỗi endpoint có ngân sách hằng ngày là 50.000 sự kiện với Pro và 500.000 với Business; vượt quá mức đó, endpoint chuyển sang paused_budget cho đến ngày UTC tiếp theo và bạn nhận được 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

Phản hồi mang secret dùng để ký (whsec_…) một lần duy nhất. Các lần gửi tuân theo Standard Webhooks: header webhook-id, webhook-timestamp và webhook-signature: v1,<base64>, trong đó chữ ký là HMAC-SHA256 dùng các byte của secret trên {webhook-id}.{webhook-timestamp}.{body}. Hãy từ chối timestamp lệch quá 5 phút, và dùng webhook-id để loại bỏ bản trùng: giá trị này giữ nguyên qua các lần thử lại.

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

Hãy trả lời bằng bất kỳ mã 2xx nào trong vòng 10 giây. Chuyển hướng không được theo. Các lần gửi thất bại được thử lại sau 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 14 h, 20 h và 24 h, tổng cộng khoảng ba ngày, lần lượt từng lần gửi cho mỗi endpoint. Endpoint trả về 410, hoặc thất bại năm ngày liên tiếp, sẽ bị vô hiệu hóa và bạn nhận được e-mail; hãy đặt status trở lại enabled bằng PATCH khi đã sửa xong.

POST /v1/webhooks/{id}/rotate-secret trả về một secret mới; trong 24 giờ, mỗi lần gửi mang hai chữ ký, mỗi chữ ký ứng với một secret. POST /v1/webhooks/{id}/test gửi một ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed liệt kê các lần gửi trong 30 ngày, và POST …/deliveries/{dl_id}/redeliver gửi lại một lần. Khóa thử nghiệm (tp_test_…) chỉ được trỏ tới webhook.site.

GET/v1/stream khóa · developer + text/event-stream

Luồng sự kiện

Cùng các sự kiện đó qua một phản hồi HTTP kéo dài, theo định dạng server-sent events, dành cho máy chủ và ứng dụng native. Truyền station_ids (tối đa 100) hoặc wishlist_id (all, hoặc một 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
Tham sốMặc địnhÝ nghĩa
station_ids—Các id đài cách nhau bằng dấu phẩy, tối đa 100.
wishlist_id—all hoặc một id danh sách mong muốn. Gửi wishlist.matched.
eventsstation.title_changedVới luồng theo đài, bất kỳ sự kiện nào trong station.title_changed, track.started, artist.started.
classificationsongsong hoặc any, với luồng theo đài.

data là cùng JSON mà một webhook nhận được. Một comment : ping được gửi sau 20 giây không có sự kiện, vì vậy hãy đặt idle timeout của client từ 60 giây trở lên. Khi kết nối bị ngắt, hãy kết nối lại với header Last-Event-ID đặt bằng id cuối cùng bạn thấy: các sự kiện trong 10 phút gần nhất sẽ được phát lại. Một vị trí cũ hơn sẽ nhận event: gap kèm id sớm nhất còn có; hãy lấp khoảng trống từ lịch sử hoặc feed kết quả khớp. Số luồng mở cho mỗi khóa: Developer 1, Pro 5, Business 20, Tapelark 1 cho mỗi thiết bị. Thêm một luồng nữa sẽ nhận 429 too_many_connections.

Trình duyệt không thể dùng EventSource ở đây, vì nó không gửi được header Authorization và khóa không bao giờ được chấp nhận trong URL. Thay vào đó, hãy đọc luồng bằng fetch().

Trường và enum

classification enum
song · ident (tên hoặc khẩu hiệu của đài) · programme · ad_break · empty · junk · unknown. Chỉ bài hát mới trở thành lịch sử phát; mọi thứ đều được giữ trong dòng trực tiếp.
match_grade enum
high · medium · none. Mức độ tiêu đề được liên kết tốt tới một recording của MusicBrainz.
confidence enum
high · medium · low. Tính cho từng dòng từ classification, match grade, mức độ gần đây đài được xác nhận và lịch sử độ tin cậy của đài. Được công bố để bạn có thể kiểm tra.
method enum
Cách Tuneping đọc đài: icy (metadata trong luồng), icecast_json (JSON trạng thái của máy chủ), shoutcast_stats (XML trạng thái), shoutcast_currentsong, seven_html. Đây là mã định danh nội bộ, không phải sự xác nhận hay quảng bá; nếu hiển thị, hãy ghi là "trong luồng" và "trang trạng thái".
recordability enum
Luồng của đài có phải là luồng HTTP thuần, ghi được, xét từ góc độ một client ghi âm hay không. Chỉ mang tính thông tin.
Id
Đài dùng UUID của Radio Browser. Bản nhạc là trk_…, nghệ sĩ art_…, sự kiện phát pe_…, khóa key_…, chủ sở hữu own_…, danh sách mong muốn wl_…, kết quả khớp wm_…, webhook wh_…, lần gửi dl_…, sự kiện evt_…. Hãy coi tất cả là chuỗi mờ (opaque).
Timestamp
RFC 3339, UTC, độ chính xác mili giây nếu nguồn có.

OpenAPI

Mô tả cho máy đọc của API này nằm tại /docs/openapi-v1.yaml (OpenAPI 3.1). Đây là nguồn để sinh client; các operation id ổn định.

Ghi nguồn và sử dụng hợp lý

Việc sử dụng ở gói Free và ẩn danh phải hiển thị một liên kết "Data by Tuneping" dễ thấy ở nơi dữ liệu được hiển thị (huy hiệu). Lượt phát được phát hiện từ metadata của đài và không phải là giám sát được chứng nhận; hãy ghi "phát hiện từ metadata của đài" khi bạn trình bày chúng làm bằng chứng. Tên đài là văn bản; Tuneping không cung cấp logo hay ảnh bìa. Xem điều khoản.