Tuneping API v1
JSON API о том, что интернет-радиостанции играют прямо сейчас и что они играли раньше. Базовый URL https://api.tuneping.com/v1. Только метаданные: сервис читает публичную строку с названием в каждом потоке и никогда не трогает аудио.
Быстрый старт
Кто сейчас играет Madonna?
curl "https://api.tuneping.com/v1/now-playing/search?q=madonna&limit=3"
Что сейчас в эфире станции, id которой вы знаете (id — это UUID станций в Radio Browser):
curl "https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/now-playing"
Что станция играла за последние 24 часа (нужен ключ с доступом к истории, тариф Developer или выше):
curl -H "Authorization: Bearer tp_live_…" \
"https://api.tuneping.com/v1/stations/159824f0-5475-4263-bcc8-59a48a213896/history"
Все ответы — JSON. Ошибки приходят как application/problem+json (см. Ошибки). Неизвестные query-параметры не игнорируются, а отклоняются с кодом 400, поэтому опечатки видны сразу.
Получение ключа
Самостоятельно на tuneping.com/signup: укажите адрес электронной почты, откройте ссылку из письма в течение 60 минут, и ключ будет показан один раз. Те же два шага доступны через API: POST /v1/signup с {"email": "…", "plan": "free|developer|pro"} отвечает 202 и отправляет письмо; POST /v1/signup/verify с {"token": "…"} из ссылки отвечает 201 и возвращает ключ. Для Free больше ничего не нужно. Developer и Pro продолжают оформление в Stripe Checkout по ссылке из ответа; тариф владельца меняется в течение минуты после оплаты, и все ключи владельца переходят на него.
Потеряли ключ? Зарегистрируйтесь снова с тем же адресом: ключ будет перевыпущен, а старый продолжит работать 24 часа. Один боевой ключ на Free и Developer, пять на Pro, двадцать на Business; плюс один ключ tp_test_ на любом тарифе — по запросу, пока не появится личный кабинет.
Аутентификация
Передавайте ключ как bearer-токен или в заголовке X-Api-Key:
Authorization: Bearer tp_live_a1b2c3…
X-Api-Key: tp_live_a1b2c3…
Ключи начинаются с tp_live_ или tp_test_, за которыми следуют 43 символа. Запрос без ключа считается анонимным и ограничивается по IP-адресу. Некорректный, неизвестный или отозванный ключ сразу получает ответ 401, даже на эндпоинтах, которые сработали бы анонимно. Эндпоинты, помеченные ниже как ключ, без ключа отвечают 401 unauthenticated.
Области доступа (scopes). Ключи получают все области доступа (read, exports, webhooks, wishlists), если только при выпуске им не был задан более узкий список, например только чтение; GET /v1/me показывает области доступа ключа. Для списков желаний и вебхуков нужны области wishlists и webhooks (списку желаний с доставкой webhook нужны обе; это проверяется до тарифа). Ключ без нужной области получает 403 forbidden_scope с required_scope, а WWW-Authenticate содержит error="insufficient_scope".
Тестовые ключи. Ключ tp_test_ читает те же живые данные с лимитами Free и имеет собственные списки желаний, совпадения и эндпоинты вебхуков (livemode: false). Он никогда не видит и не меняет боевые, поэтому набор тестов может сделать PUT с пустым набором списков желаний, не затрагивая продакшен. Вебхуки в тестовом режиме могут указывать только на webhook.site.
Повторы. POST, PATCH и DELETE на эндпоинтах, которым нужен ключ или токен партнёра, принимают заголовок Idempotency-Key (UUID на каждый логический запрос); эндпоинты регистрации его не принимают. Повтор с тем же ключом и тем же запросом снова получает первый успешный ответ с пометкой Idempotent-Replayed: true вместо того, чтобы создать второй список желаний или эндпоинт. Ключ запоминается на 24 часа; повторное использование для другого запроса даёт 422 idempotency_key_reused, а повтор, пока первый запрос ещё выполняется, — 409 idempotency_in_progress. Ошибки не запоминаются, поэтому повтор после ошибки выполняется заново. Разрыв соединения не отменяет запрос с этим заголовком: он завершается, успешный ответ запоминается, и ваш повтор его получит. Запрос, который всё ещё выполняется через 20 секунд, отменяется без каких-либо изменений и отвечает 503 unavailable с Retry-After: 1; повторите его с тем же ключом.
Партнёрские приложения (сейчас Tapelark) вместо ключа отправляют подписанный токен доступа: Authorization: Bearer TPE1-…. Токен проверяется офлайн по ключам Ed25519 партнёра и соответствует партнёрскому тарифу из таблицы ниже; API никогда не узнаёт, кто конечный пользователь. Некорректный или неизвестный токен получает 401 token_invalid, просроченный — 401 token_expired (обновите его), токен для другой аудитории — 401 token_audience, а истёкшее или отозванное право доступа — 403 entitlement_revoked.
Лимиты запросов и квоты
| Тариф | Частота | Квота | Id в пакете | История |
|---|---|---|---|---|
| Анонимно (на IP) | 2 req/s, всплеск до 5 | 1 000 / день | 20 | нет |
| Free | 2 req/s, всплеск до 5 | 1 000 / день | 20 | нет |
| Developer | 10 req/s, всплеск до 20 | 100 000 / месяц | 100 | 7 дней |
| Pro | 50 req/s, всплеск до 100 | 1 000 000 / месяц | 100 | 12 месяцев |
| Business | индивидуально | индивидуально | 100 | 12 месяцев |
| Tapelark (токен доступа, на лицензию) | 5 req/s, всплеск до 10 | 2 000 / день | 50 | 7 дней |
Посекундный лимит работает по алгоритму token bucket. Когда корзина пуста, вы получаете 429 rate_limited с Retry-After. Когда квота исчерпана, вы получаете 429 quota_exceeded с Retry-After, равным числу секунд до сброса окна.
Аутентифицированные ответы, которые нельзя кешировать в общем кеше, содержат поля лимитов запросов по спецификации IETF:
RateLimit-Policy: "burst";q=10;w=1, "monthly";q=100000;w=2592000
RateLimit: "burst";r=7;t=1
Ответы, допускающие общий кеш (поиск, пакетный запрос, «сейчас в эфире» для станции, станции, stats, health series), не содержат заголовков лимитов, потому что edge может отдать то же тело другому клиенту. Оставшийся бюджет всегда доступен в GET /v1/me.
Кеширование
Живые ответы кешируются на 5 секунд (Cache-Control: public, max-age=5, s-maxage=5) и содержат слабый ETag. Отправьте If-None-Match, чтобы получить 304, если ничего не изменилось; 304 на origin не расходует квоту. Каталог станций кешируется на 60 секунд, отдельная станция — на 300. История — private, max-age=30. Эндпоинты health и ключей — no-store.
Каждый ответ содержит X-Request-Id. Указывайте его, когда пишете в поддержку.
Ошибки
Ошибки следуют формату problem details из RFC 9457 с двумя полями-расширениями: code — стабильная машиночитаемая строка, и дополнительные поля, зависящие от эндпоинта, например errors[] при ошибках валидации или required_tier при 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." } ]
}
Полный список кодов с описанием — в каталоге ошибок. Те, с которыми вы столкнётесь первыми:
| HTTP | code | Когда |
|---|---|---|
| 400 | invalid_request | Неизвестный query-параметр или некорректный запрос |
| 400 | invalid_cursor | Курсор некорректен, просрочен (24 ч; 30 дней для since_cursor ленты совпадений) или использован с другими фильтрами |
| 401 | unauthenticated · invalid_api_key · key_revoked | Ключ отсутствует, неизвестен или отозван |
| 403 | forbidden_tier | Эндпоинту или значению параметра нужен более высокий тариф; required_tier указывает какой |
| 403 | forbidden_scope | Ключ выпущен без нужной области доступа; required_scope указывает какой |
| 404 | not_found · station_opted_out | Неизвестный id, или станция попросила не индексировать её |
| 422 | validation_failed | Ошибки полей в errors[] |
| 429 | rate_limited · quota_exceeded | Сбавьте темп; Retry-After в секундах |
| 503 | unavailable | База данных недоступна, или запрос с Idempotency-Key отменён через 20 секунд; повторите через Retry-After |
GET/v1/now-playing/search без ключа public, max-age=5 · ETag
Живой поиск
Какие станции играют q прямо сейчас. Нечёткий поиск без учёта диакритики: gronemeyer manner находит Grönemeyer – Männer. Строки упорядочены по сходству, затем по тому, насколько недавно появилось название. Пагинации нет: индекс меняется каждые несколько секунд, и страницы пропускали бы или повторяли строки. Вместо этого увеличьте limit.
| Параметр | Тип | По умолчанию | Значение |
|---|---|---|---|
q | строка, 2–120 символов | обязательный | Исполнитель, название или и то и другое. Запросы из трёх и более символов сравниваются по триграммам; более короткие ищутся как подстрока. |
country | ISO 3166-1 alpha-2, можно повторять | любая | Только станции из этих стран. |
codec | строка | любой | например, MP3, AAC. |
min_bitrate | целое число | 0 | кбит/с. |
classification | enum | song | song, ident, programme, ad_break, empty, junk, unknown или any. |
max_age_seconds | 30–900 | 180 | Только строки, станция которых была успешно опрошена не более чем столько секунд назад. |
limit | 1–100 | 20 | Сколько строк вернуть; truncated: true означает, что их было больше. |
Ответ · 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 }
}
Как читать строку
- started_at RFC 3339 · оценка
- Оценка начала текущего названия, ±
start_uncertainty_secondsв обе стороны. - observed_at RFC 3339
- Когда Tuneping впервые увидел это название.
- confirmed_at RFC 3339
- Последний успешный опрос станции: «всё ещё играет по состоянию на». Используйте для оценки актуальности.
- track объект · необязательно
- Присутствует, если название связано с записью в MusicBrainz.
- similarity 0–1
- Отсекайте по собственному порогу.
GET/v1/now-playing без ключа public, max-age=5 · ETag
Пакетный запрос «сейчас в эфире»
Текущее название для списка станций одним вызовом — для экранов избранного.
| Параметр | Тип | По умолчанию | Значение |
|---|---|---|---|
station_ids | UUID через запятую | обязательный | До 20 анонимно или на Free, до 100 начиная с Developer. Перед кешированием id сортируются и очищаются от дубликатов, поэтому два клиента с одинаковым избранным получают одно и то же закешированное тело. |
max_age_seconds | 30–86400 | 900 | Строки старше этого значения всё равно возвращаются, но с пометкой 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 без ключа
«Сейчас в эфире» для станции
Одна строка для одной станции. 404 not_found, если название ещё ни разу не наблюдалось, 404 station_opted_out, если станция попросила не индексировать её.
GET/v1/stations GET/v1/stations/{id} без ключа max-age=60 · 300
Станции
Каталог, построенный на основе каталога Radio Browser, который ведёт сообщество, и дополненный сведениями о том, как Tuneping читает каждую станцию.
| Параметр | Тип | По умолчанию | Значение |
|---|---|---|---|
q | строка | — | Название станции, нечёткий поиск. |
country | alpha-2, можно повторять | любая | |
tag | строка, можно повторять | любой | Должны совпасть все указанные теги. |
codec, min_bitrate | Как в поиске. | ||
has_now_playing | boolean | false | Только станции, названия с которых Tuneping сейчас читает. |
sort | enum | clickcount | clickcount (популярность, по убыванию), name, bitrate. |
limit | 1–200 | 50 | |
cursor | непрозрачная строка | — | Из next_cursor предыдущей страницы; действителен 24 часа и только с теми же фильтрами. |
{ "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 передаётся из каталога как текст; Tuneping никогда не копирует и не отдаёт логотипы.
GET/v1/stations/{id}/history ключ · developer + private, max-age=30
История эфира
Песни, которые играла станция, сначала новые, с оценкой начала и конца. Глубина зависит от тарифа: 7 дней на Developer, 12 месяцев на Pro и Business. Запрос с более ранним from получает 403 forbidden_tier с required_tier, а не молча урезанное окно.
| Параметр | Тип | По умолчанию | Значение |
|---|---|---|---|
from, to | RFC 3339 | to = сейчас, from = to − 24 ч | Не более 31 дня за вызов. |
classification | enum | song | Как в поиске. |
limit | 1–200 | 50 | |
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 ключ no-store
Ключи и использование
/v1/me возвращает ключ, тариф и статус его владельца, действующие лимиты и использование в текущем окне. /v1/me/usage?from=&to= возвращает число запросов по дням (только запросы к origin; попадания в кеш edge не учитываются).
{ "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 без ключа
Health и stats
/v1/health отвечает 200 со status ok или degraded и 503, если база данных недоступна; направьте на него свой мониторинг доступности. /v1/health/series?minutes=60 (5–1440) возвращает поминутные показатели поллера: polls, ok, titles_changed, songs, bytes_in, bytes_out, latency_p50_ms, latency_p90_ms, inflight_max, ips_paused, queue_lag_seconds и разбивку by_method. /v1/stats — публичный снимок покрытия, который показан на странице статуса: всего станций, опрашиваемых, с живыми метаданными, смен названий в минуту, доля песен, по методам, по странам.
POST/v1/wishlists PUT/v1/wishlists GET/v1/wishlists/matches ключ · developer + no-store
Списки желаний
Назовите песню, исполнителя или произвольное название и узнайте, когда станция начнёт его играть. Каждая смена названия проверяется по вашим спискам желаний в момент чтения, а новый список желаний начинает работать в течение 30 секунд. Developer хранит 10 списков желаний, Pro — 100, Business — 1 000. Совпадения хранятся 30 дней.
| Поле | Тип | Значение |
|---|---|---|
kind | artist · track · query | artist и track принимают id из поиска (art_…, trk_…) и также ловят варианты написания, которые нормализатор с ним связывает. query принимает произвольный текст, обычно Artist - Title. |
value | строка, ≤ 240 | Id или текст. |
match | exact · fuzzy | Только для query. Exact сравнивает нормализованный текст; fuzzy принимает триграммное сходство 0,6 и выше. |
station_filter | объект | station_ids (≤ 500) и/или countries (alpha-2 в любом регистре, возвращаются в верхнем). Название совпадает, если его станция указана в списке или находится в указанной стране. Без фильтра — все станции. |
delivery | массив | poll (по умолчанию), sse, webhook. Доставка вебхуком доступна на Pro и выше, и для неё нужны webhook_id и ключ с областью доступа webhooks. |
description | строка, ≤ 200 | Ваше описание. |
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
Явный список station_ids заставляет Tuneping читать эти станции каждые 30 секунд вместо 60, пока список желаний включён, и ответ сообщает об этом через tightens: true. Объединение всех указанных вами станций ограничено в зависимости от тарифа (limits.tightened_stations в /v1/me); список желаний, который превысил бы это ограничение, получает 422 validation_failed с записью в errors[], у которой code равен tightened_stations_exceeded (поле station_filter.station_ids или data при PUT). Фильтры по странам по-прежнему работают, с обычным интервалом каждой станции.
GET /v1/wishlists/{id}, PATCH (фильтр станций, match, доставка, вебхук, описание, status: enabled | paused) и DELETE работают так, как ожидается. PUT /v1/wishlists с {"data": [ … ]} заменяет весь ваш набор за один вызов: неизменённые элементы сохраняют свой id и счётчик совпадений, остальные создаются или удаляются. Это самый простой способ синхронизировать список, который ваше приложение хранит локально. Его тело может быть до 1 MiB; тела остальных запросов ограничены 64 KiB (413 payload_too_large).
Совпадения — это лента, за концом которой вы следите. GET /v1/wishlists/matches охватывает все ваши списки желаний, а GET /v1/wishlists/{id}/matches — один из них. Строки идут от старых к новым. Сохраняйте next_cursor из каждого ответа и отправляйте его обратно как since_cursor: он никогда не равен null, остаётся действительным в течение 30-дневного срока хранения и переживает перезапуски. Без курсора вы получите самые новые совпадения. limit — от 1 до 500, по умолчанию 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 уже скорректирован с учётом того, когда было замечено название, поэтому программа записи может показать «началось примерно N ± U секунд назад». expected_end_hint_at — это started_at плюс 222 секунды, медианная длина песни: подсказка, а не прогноз. confidence равен high, если совпадение прошло через связанный id трека или исполнителя, medium — для нормализованных исполнителя и названия, low — для текстовых запросов.
POST/v1/webhooks GET/v1/webhooks/{id}/deliveries ключ · pro + no-store
Вебхуки
Tuneping отправляет события на ваш HTTPS-эндпоинт по мере того, как они происходят. На Pro доступно 5 эндпоинтов, на Business — 20.
| Событие | Когда | Что нужно на Pro |
|---|---|---|
wishlist.matched | Сработал список желаний, который указывает этот эндпоинт в webhook_id. | — |
station.title_changed | Изменилась строка с названием на станции. | filters.station_ids (≤ 200) |
track.started | Зазвучал связанный трек. | filters.track_ids или artist_ids (≤ 200) |
artist.started | Зазвучал связанный исполнитель. | filters.track_ids или artist_ids (≤ 200) |
Фильтры могут также содержать countries и classification. Фильтры только по стране и нефильтрованные потоки смен названий доступны на Business. У каждого эндпоинта есть суточный бюджет: 50 000 событий на Pro и 500 000 на Business; после его исчерпания эндпоинт переходит в paused_budget до следующих суток по UTC, и вы получаете письмо.
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
Ответ один раз содержит секрет для подписи secret (whsec_…). Доставки следуют спецификации Standard Webhooks: заголовки webhook-id, webhook-timestamp и webhook-signature: v1,<base64>, где подпись — HMAC-SHA256 с байтами секрета по строке {webhook-id}.{webhook-timestamp}.{body}. Отклоняйте метки времени, расходящиеся более чем на 5 минут, и используйте webhook-id, чтобы отбрасывать дубликаты: он не меняется между повторными попытками.
{ "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_…" } }
Ответьте любым кодом 2xx в течение 10 секунд. Редиректы не выполняются. Неудачные доставки повторяются через 5 с, 5 мин, 30 мин, 2 ч, 5 ч, 10 ч, 14 ч, 20 ч и 24 ч, всего около трёх суток, по одной за раз на эндпоинт. Эндпоинт, который отвечает 410 или не принимает доставки пять дней подряд, отключается, и вы получаете письмо; после исправления верните status в enabled через PATCH.
POST /v1/webhooks/{id}/rotate-secret возвращает новый секрет; в течение 24 часов каждая доставка несёт две подписи, по одной на каждый секрет. POST /v1/webhooks/{id}/test отправляет ping. GET /v1/webhooks/{id}/deliveries?status=pending|succeeded|failed выдаёт доставки за 30 дней, а POST …/deliveries/{dl_id}/redeliver отправляет одну из них повторно. Тестовые ключи (tp_test_…) могут указывать только на webhook.site.
GET/v1/stream ключ · developer + text/event-stream
Поток событий
Те же события в одном длинном HTTP-ответе в формате server-sent events — для серверов и нативных приложений. Передайте либо station_ids (до 100), либо wishlist_id (all или один 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
| Параметр | По умолчанию | Значение |
|---|---|---|
station_ids | — | Id станций через запятую, не более 100. |
wishlist_id | — | all или id списка желаний. Отправляет wishlist.matched. |
events | station.title_changed | Для потоков по станциям — любые из station.title_changed, track.started, artist.started. |
classification | song | song или any, для потоков по станциям. |
data — тот же JSON, который получает вебхук. Комментарий : ping приходит после 20 секунд без событий, поэтому установите таймаут простоя клиента на 60 секунд или больше. При разрыве соединения переподключитесь с заголовком Last-Event-ID, в котором указан последний полученный id: события за последние 10 минут будут отправлены повторно. Для более старой позиции придёт event: gap с самым ранним доступным id; заполните пробел из истории или ленты совпадений. Открытых потоков на ключ: Developer — 1, Pro — 5, Business — 20, Tapelark — 1 на устройство. Ещё один получит 429 too_many_connections.
Браузеры не могут использовать здесь EventSource, потому что он не умеет отправлять заголовок Authorization, а ключи в URL никогда не принимаются. Читайте поток через fetch().
Поля и перечисления
- classification enum
song·ident(название или слоган станции) ·programme·ad_break·empty·junk·unknown. В историю эфира попадают только песни; в живой строке сохраняется всё.- match_grade enum
high·medium·none. Насколько надёжно название связано с записью в MusicBrainz.- confidence enum
high·medium·low. Вычисляется для каждой строки по классификации, степени совпадения, давности последнего подтверждения станции и её истории надёжности. Описано, чтобы вы могли это проверить.- method enum
- Как Tuneping читает станцию:
icy(метаданные в потоке),icecast_json(JSON статуса сервера),shoutcast_stats(XML статуса),shoutcast_currentsong,seven_html. Это внутренние идентификаторы, а не рекомендации; если показываете их, отображайте как «в потоке» и «страница статуса». - recordability enum
- Является ли поток станции простым HTTP-потоком, пригодным для записи, с точки зрения программы записи. Информационное поле.
- Id
- Станции используют свой UUID из Radio Browser. Треки —
trk_…, исполнители —art_…, события эфира —pe_…, ключи —key_…, владельцы —own_…, списки желаний —wl_…, совпадения —wm_…, вебхуки —wh_…, доставки —dl_…, события —evt_…. Считайте их все непрозрачными строками. - Метки времени
- RFC 3339, UTC, с точностью до миллисекунд, если она есть в источнике.
OpenAPI
Машиночитаемое описание этого API находится в /docs/openapi-v1.yaml (OpenAPI 3.1). Это источник для генерации клиентов; идентификаторы операций стабильны.
Атрибуция и добросовестное использование
При использовании на тарифе Free и анонимно там, где отображаются данные, должна быть видимая ссылка «Data by Tuneping» (значок). Проигрывания определяются по метаданным станций и не являются сертифицированным мониторингом; если вы приводите их как доказательство, пишите «обнаружено по метаданным станции». Названия станций — это текст; Tuneping не предоставляет логотипы и обложки. См. условия.