Resona API referenceСправочник Resona API
This document describes the unofficial SoundCloud API that Resona talks to. It is the same private api-v2.soundcloud.com interface used internally by soundcloud.com and the official mobile apps, reconstructed from observed network traffic. There is no affiliation with SoundCloud Ltd.
Здесь описан неофициальный API SoundCloud, с которым работает Resona. Это тот же приватный интерфейс api-v2.soundcloud.com, которым пользуются сам сайт soundcloud.com и официальные мобильные приложения, восстановленный по перехваченному трафику. Никакой связи с SoundCloud Ltd. нет.
infoOverviewОбзор
SoundCloud closed public registration for its official REST API (api.soundcloud.com, OAuth2, api keys) back in 2015. Since then, the only way to build a third-party client is to speak the same private API that the soundcloud.com web app and the official mobile apps use internally: api-v2.soundcloud.com. Resona, like every other unofficial SoundCloud client, is built on top of that interface.
This reference documents the endpoints Resona actually calls: search, track and playlist lookups, user profiles, stream URL resolution, and the authenticated likes flow. It is meant for people reading or contributing to Resona's source, or building their own client against the same API.
SoundCloud закрыл публичную регистрацию для своего официального REST API (api.soundcloud.com, OAuth2, api-ключи) ещё в 2015 году. С тех пор единственный способ сделать сторонний клиент, это использовать тот же приватный API, которым пользуются сам веб-клиент soundcloud.com и официальные мобильные приложения: api-v2.soundcloud.com. Resona, как и любой другой неофициальный клиент SoundCloud, построен поверх этого интерфейса.
В этом справочнике описаны эндпоинты, которые реально использует Resona: поиск, получение треков и плейлистов, профили пользователей, разрешение ссылки на поток и авторизованная работа с лайками. Он рассчитан на тех, кто читает исходный код Resona или пишет свой клиент под тот же API.
gavelDisclaimerОтказ от ответственности
Not an official SoundCloud API. Endpoints, fields, headers and quirks below were observed by inspecting requests made by soundcloud.com. SoundCloud does not publish or support this interface, can change it at any time without notice, and may rate limit, block or ban clients that misuse it. Use responsibly, cache aggressively, and do not rely on this for anything mission critical.
Это не официальный API SoundCloud. Эндпоинты, поля, заголовки и особенности ниже получены путём анализа запросов, которые делает soundcloud.com. SoundCloud не публикует и не поддерживает этот интерфейс, может изменить его в любой момент без предупреждения, а также ограничивать, блокировать или банить клиентов, которые им злоупотребляют. Используйте аккуратно, кэшируйте запросы и не полагайтесь на это для чего-то критичного.
dnsBase URLБазовый URL
All endpoints on this page are relative to:
Все эндпоинты на этой странице указаны относительно:
https://api-v2.soundcloud.com/
Playback streams are served from a separate CDN host (cf-media.sndcdn.com) with a signed, short-lived URL, obtained through the stream resolution flow rather than requested directly.
Аудиопотоки отдаются с отдельного CDN-хоста (cf-media.sndcdn.com) по подписанной короткоживущей ссылке, которую нужно получить через разрешение потока, а не запрашивать напрямую.
keyclient_idclient_id
Every request, authenticated or not, requires a client_id query parameter. It identifies the calling web app to SoundCloud's infrastructure, it is not a per-developer API key and it is not secret: it is embedded in plain text inside the JavaScript bundles that soundcloud.com serves to every visitor.
Каждый запрос, авторизованный или нет, требует query-параметр client_id. Он идентифицирует вызывающее веб-приложение перед инфраструктурой SoundCloud, это не персональный API-ключ разработчика и не секрет: он лежит открытым текстом внутри JS-бандлов, которые soundcloud.com отдаёт каждому посетителю.
Resona obtains one automatically on first launch:
Resona получает его автоматически при первом запуске:
- Download the HTML of
https://soundcloud.com. - Загрузить HTML страницы
https://soundcloud.com. - Extract
<script src="…">URLs pointing ata-v2.sndcdn.com/assets/*.js. - Извлечь из неё URL-адреса скриптов
<script src="…">, ведущие наa-v2.sndcdn.com/assets/*.js. - Download those bundles (checked in reverse order) and regex-match a 32 character client id.
- Скачать эти бандлы (в обратном порядке) и вытащить регуляркой 32-символьный client_id.
- Cache the value locally and reuse it until a request comes back
401/403, at which point it is refreshed and the request retried once. - Закэшировать значение и переиспользовать его, пока запрос не вернёт
401/403, тогда id обновляется и запрос повторяется один раз.
client_id[=:]"?([a-zA-Z0-9]{32})"?
In every example below, replace YOUR_CLIENT_ID with a value obtained this way.
Во всех примерах ниже подставляйте вместо YOUR_CLIENT_ID значение, полученное таким способом.
lockToken captureПерехват токена
A subset of endpoints act on behalf of a logged-in user (the current profile, private likes, liking or unliking a track). These require an Authorization: OAuth <token> header in addition to client_id.
Часть эндпоинтов действует от имени вошедшего пользователя (текущий профиль, приватные лайки, лайк/анлайк трека). Для них нужен заголовок Authorization: OAuth <token> в дополнение к client_id.
Resona obtains the token by loading the real soundcloud.com login in an embedded WebView and reading it out of the page's own Authorization header (or its window.__sc_hydration state) once the user signs in. Session cookies are captured the same way. This is needed because write requests such as liking a track are blocked by SoundCloud's bot protection (DataDome) when they come from a bare HTTP client, but pass when they originate from a real browser context.
Resona получает токен, загружая настоящую страницу входа soundcloud.com во встроенном WebView, и после логина читает его из заголовка Authorization собственных запросов страницы (либо из состояния window.__sc_hydration). Cookies сессии перехватываются так же. Это нужно потому, что запросы на запись, например лайк трека, блокируются защитой от ботов SoundCloud (DataDome), если они идут от голого HTTP-клиента, но проходят, если исходят из настоящего браузерного контекста.
list_altRequest headersЗаголовки запроса
To avoid being flagged as a bot, every request should look like it came from the soundcloud.com web app:
Чтобы запрос не приняли за бота, он должен выглядеть так, будто пришёл из веб-приложения soundcloud.com:
| HeaderЗаголовок | ValueЗначение | RequiredОбязателен |
|---|---|---|
| User-Agent | a recent desktop Chrome UA stringстрока UA свежего десктопного Chrome | recommendedрекомендуется |
| Accept | application/json, text/javascript, */*; q=0.1 | recommendedрекомендуется |
| Origin | https://soundcloud.com | recommendedрекомендуется |
| Referer | https://soundcloud.com/ | recommendedрекомендуется |
| X-SoundCloud-Client | same value as client_idто же значение, что и client_id | optionalопционально |
| X-Client-UUID | random UUID, new per requestслучайный UUID на каждый запрос | optionalопционально |
| Authorization | OAuth <token> | user-specific endpoints onlyтолько для пользовательских эндпоинтов |
| Cookie | captured web session cookiesперехваченные cookies веб-сессии | write endpoints onlyтолько для эндпоинтов записи |
view_listPaginationПагинация
List endpoints return a consistent envelope:
Эндпоинты со списками возвращают единый формат ответа:
{
"collection": [ ... ],
"next_href": "https://api-v2.soundcloud.com/search/tracks?q=...&offset=25&limit=25"
}
The first page is requested with limit and offset query params. Every following page should be fetched by calling next_href verbatim rather than incrementing offset yourself, since some endpoints (like likes, with linked_partitioning=true) use opaque cursors instead of numeric offsets. next_href is null once there are no more pages.
Первая страница запрашивается с параметрами limit и offset. Каждую следующую страницу нужно получать, вызывая next_href как есть, а не увеличивая offset самостоятельно, поскольку некоторые эндпоинты (например лайки с linked_partitioning=true) используют непрозрачные курсоры вместо числовых offset. Когда страниц больше нет, next_href равен null.
errorErrors & rate limitsОшибки и лимиты
| StatusСтатус | MeaningЗначение |
|---|---|
| 401 / 403 | client_id is invalid, expired or rotated. Refresh it (see client_id) and retry once.client_id недействителен, истёк или сменился. Обновите его (см. client_id) и повторите запрос один раз. |
| 404 | Entity does not exist, is private, or was deleted.Сущность не существует, приватна или удалена. |
| 429 | Rate limited. Back off with increasing delay before retrying; do not retry immediately in a loop.Превышен лимит запросов. Перед повтором нужна увеличивающаяся задержка, не повторяйте запрос сразу в цикле. |
Write endpoints (liking/unliking a track) sit behind DataDome bot protection and reject plain HTTP client requests outright, even with correct headers and a valid token. Resona routes those specific calls through a real WebView instance instead, using its JS fetch() so the request carries genuine browser fingerprinting.
Эндпоинты записи (лайк/анлайк трека) прикрыты защитой от ботов DataDome и отклоняют запросы обычного HTTP-клиента даже с правильными заголовками и валидным токеном. Resona прогоняет именно эти вызовы через настоящий WebView, используя его JS fetch(), чтобы запрос нёс подлинный браузерный отпечаток.
searchSearchПоиск
/search/tracks
Full-text search across public tracks.
Полнотекстовый поиск по публичным трекам.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| q | string | search queryпоисковый запрос |
| limit | int | page size, default 25размер страницы, по умолчанию 25 |
| offset | int | first page offset, default 0смещение первой страницы, по умолчанию 0 |
curl "https://api-v2.soundcloud.com/search/tracks?q=synthwave&limit=25&offset=0&client_id=YOUR_CLIENT_ID"
{
"collection": [
{
"id": 293100293,
"title": "Midnight Drive",
"duration": 214000,
"genre": "Synthwave",
"playback_count": 128340,
"likes_count": 942,
"artwork_url": "https://i1.sndcdn.com/artworks-xxxx-t500x500.jpg",
"permalink_url": "https://soundcloud.com/artist/midnight-drive",
"user": { "id": 8153021, "username": "artist" },
"media": { "transcodings": [ ... ] }
}
],
"next_href": "https://api-v2.soundcloud.com/search/tracks?q=synthwave&offset=25&limit=25"
}
/search/users
Search users and artists by name.
Поиск пользователей и артистов по имени.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| q | string | search queryпоисковый запрос |
| limit | int | page size, default 10размер страницы, по умолчанию 10 |
curl "https://api-v2.soundcloud.com/search/users?q=artist&limit=10&client_id=YOUR_CLIENT_ID"
/search/playlists
Search public playlists and albums.
Поиск публичных плейлистов и альбомов.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| q | string | search queryпоисковый запрос |
| limit | int | page size, default 10размер страницы, по умолчанию 10 |
| offset | int | first page offset, default 0смещение первой страницы, по умолчанию 0 |
curl "https://api-v2.soundcloud.com/search/playlists?q=lofi&limit=10&offset=0&client_id=YOUR_CLIENT_ID"
music_noteTracksТреки
/tracks/{id}
Fetch a single track by its numeric id, including its media.transcodings list needed for playback.
Получить один трек по числовому id, включая список media.transcodings, нужный для воспроизведения.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| id | long | path parameter, track idпараметр пути, id трека |
curl "https://api-v2.soundcloud.com/tracks/293100293?client_id=YOUR_CLIENT_ID"
graphic_eqStream resolutionРазрешение потока
Playable audio URLs are never returned directly. Resolving one takes two requests:
Ссылки на воспроизводимый поток никогда не отдаются напрямую. Их получение требует двух запросов:
- Fetch the track (
GET /tracks/{id}). Itsmedia.transcodingsarray lists candidate streams, each with aformat.protocolof eitherprogressive(plain MP3 file) orhls(segmented.m3u8playlist), and its own internal, unsignedurl. - Получить трек (
GET /tracks/{id}). В его массивеmedia.transcodingsперечислены варианты потоков, у каждого естьformat.protocol: либоprogressive(обычный MP3-файл), либоhls(сегментированный плейлист.m3u8), и собственный внутренний неподписанныйurl. - Prefer the
progressivetranscoding when available, otherwise fall back to the first one listed. Call that transcoding'surldirectly (withclient_idappended) to get the actual, signed, time-limited CDN URL. - По возможности берите транскодинг
progressive, иначе первый из списка. Обратитесь напрямую поurlэтого транскодинга (с добавленнымclient_id), чтобы получить настоящий подписанный, ограниченный по времени CDN-адрес.
[
{
"url": "https://api-v2.soundcloud.com/media/soundcloud:tracks:293100293/abc.../stream/progressive",
"quality": "sq",
"format": { "protocol": "progressive", "mime_type": "audio/mpeg" }
},
{
"url": "https://api-v2.soundcloud.com/media/soundcloud:tracks:293100293/abc.../stream/hls",
"quality": "sq",
"format": { "protocol": "hls", "mime_type": "audio/mp4" }
}
]
curl "https://api-v2.soundcloud.com/media/soundcloud:tracks:293100293/abc.../stream/progressive?client_id=YOUR_CLIENT_ID"
{
"url": "https://cf-media.sndcdn.com/xxxxxxxxxxxx.128.mp3?Policy=...&Signature=...&Key-Pair-Id=..."
}
The final CDN URL is time-limited. Resolve it right before playback rather than caching it long-term; cache the track metadata instead and re-resolve when needed.
Финальная CDN-ссылка ограничена по времени. Получайте её непосредственно перед воспроизведением, а не кэшируйте надолго; вместо этого кэшируйте метаданные трека и получайте ссылку заново по необходимости.
personUsersПользователи
/users/{id}
Fetch a public user profile by numeric id.
Получить публичный профиль пользователя по числовому id.
curl "https://api-v2.soundcloud.com/users/8153021?client_id=YOUR_CLIENT_ID"
{
"id": 8153021,
"username": "artist",
"full_name": "Artist Name",
"avatar_url": "https://i1.sndcdn.com/avatars-xxxx-t500x500.jpg",
"followers_count": 45210,
"permalink_url": "https://soundcloud.com/artist"
}
/users/{id}/tracks
List tracks uploaded by a user, paginated.
Список треков, загруженных пользователем, с пагинацией.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| limit | int | page size, default 50размер страницы, по умолчанию 50 |
| offset | int | first page offset, default 0смещение первой страницы, по умолчанию 0 |
curl "https://api-v2.soundcloud.com/users/8153021/tracks?limit=50&offset=0&client_id=YOUR_CLIENT_ID"
queue_musicPlaylistsПлейлисты
/playlists/{id}
Fetch a playlist or album, including its full track list.
Получить плейлист или альбом вместе с полным списком треков.
curl "https://api-v2.soundcloud.com/playlists/5521003?client_id=YOUR_CLIENT_ID"
{
"id": 5521003,
"title": "Late Night Drive",
"description": "A playlist for long drives.",
"track_count": 24,
"artwork_url": "https://i1.sndcdn.com/artworks-xxxx-t500x500.jpg",
"permalink_url": "https://soundcloud.com/artist/sets/late-night-drive",
"user": { "id": 8153021, "username": "artist" },
"tracks": [ { "id": 293100293, "title": "Midnight Drive" } ]
}
favoriteLikesЛайки
/users/{id}/likes
lockOAuth for private likesOAuth для приватных лайков
List a user's liked tracks and playlists. Works without a token for a user's public likes; requires Authorization: OAuth matching the id to also see private ones.
Список понравившихся треков и плейлистов пользователя. Работает без токена для публичных лайков; для приватных нужен Authorization: OAuth, соответствующий этому id.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| limit | int | page size, default 200размер страницы, по умолчанию 200 |
| linked_partitioning | bool | use cursor-style pagination via next_href, default trueкурсорная пагинация через next_href, по умолчанию true |
curl "https://api-v2.soundcloud.com/users/8153021/likes?limit=200&linked_partitioning=true&client_id=YOUR_CLIENT_ID" \
-H "Authorization: OAuth YOUR_OAUTH_TOKEN"
{
"collection": [
{ "kind": "like", "track": { "id": 293100293, "title": "Midnight Drive" } }
],
"next_href": "https://api-v2.soundcloud.com/users/8153021/likes?cursor=..."
}
/users/{userId}/track_likes/{trackId}
lockOAuth requiredТребуется OAuth
Like a track on behalf of the authenticated user.
Поставить трек в лайки от имени авторизованного пользователя.
Blocked by DataDome when called from a plain HTTP client, even with a valid token and correct headers. Resona issues this call from inside a live WebView's JS fetch() with credentials: 'include' so session cookies ride along, then reads the response back through a JS bridge.
Блокируется DataDome при вызове из обычного HTTP-клиента, даже с валидным токеном и правильными заголовками. Resona выполняет этот запрос изнутри живого WebView через JS fetch() с credentials: 'include', чтобы cookies сессии передавались вместе с запросом, а затем читает ответ через JS-мост.
fetch(`https://api-v2.soundcloud.com/users/${userId}/track_likes/${trackId}?client_id=${clientId}`, {
method: 'PUT',
headers: { 'Authorization': `OAuth ${token}` },
credentials: 'include'
})
/users/{userId}/track_likes/{trackId}
lockOAuth requiredТребуется OAuth
Unlike a track. Same WebView-bridge requirement as above.
Убрать трек из лайков. Требует того же обхода через WebView, что и выше.
account_circleCurrent userТекущий пользователь
/me
lockOAuth requiredТребуется OAuth
Fetch the profile of the currently authenticated user. Used to resolve the numeric user id needed by the likes endpoints.
Получить профиль текущего авторизованного пользователя. Используется, чтобы узнать числовой id, нужный для эндпоинтов лайков.
curl "https://api-v2.soundcloud.com/me?client_id=YOUR_CLIENT_ID" \
-H "Authorization: OAuth YOUR_OAUTH_TOKEN"
linkResolveResolve
/resolve
Turns any soundcloud.com link (track, playlist or profile, including shortened on.soundcloud.com links after following the redirect) into the matching entity. Used for deep links and for importing a profile by pasted URL.
Превращает любую ссылку soundcloud.com (трек, плейлист или профиль, включая укороченные on.soundcloud.com после перехода по редиректу) в соответствующую сущность. Используется для диплинков и импорта профиля по вставленной ссылке.
| ParamПараметр | TypeТип | DescriptionОписание |
|---|---|---|
| url | string | URL-encoded soundcloud.com linkURL-encoded ссылка на soundcloud.com |
curl "https://api-v2.soundcloud.com/resolve?url=https%3A%2F%2Fsoundcloud.com%2Fartist%2Fmidnight-drive&client_id=YOUR_CLIENT_ID"
{
"kind": "track",
"id": 293100293,
"title": "Midnight Drive",
"permalink_url": "https://soundcloud.com/artist/midnight-drive"
}
data_objectData modelsМодели данных
Fields Resona actually reads off each entity. The live API returns many more fields than listed here.
Поля, которые Resona реально читает у каждой сущности. Настоящий API возвращает гораздо больше полей, чем перечислено здесь.
music_noteTrack
| FieldПоле | Type | DescriptionОписание |
|---|---|---|
| id | long | |
| title | string | |
| duration | long | millisecondsв миллисекундах |
| artwork_url | string? | |
| genre | string? | |
| playback_count | long? | |
| likes_count | long? | |
| permalink_url | string? | |
| user | User | |
| media.transcodings | Transcoding[] | see Stream resolutionсм. Разрешение потока |
personUser
| FieldПоле | Type |
|---|---|
| id | long |
| username | string |
| full_name | string? |
| avatar_url | string? |
| followers_count | long? |
| permalink_url | string? |
queue_musicPlaylist
| FieldПоле | Type |
|---|---|
| id | long |
| title | string |
| description | string? |
| artwork_url | string? |
| track_count | int |
| permalink_url | string? |
| user | User |
| tracks | Track[]? |
graphic_eqTranscoding
| FieldПоле | Type |
|---|---|
| url | string |
| quality | string? |
| format.protocol | "progressive" | "hls" |
| format.mime_type | string |
layersSearchResponse<T>
| FieldПоле | Type |
|---|---|
| collection | T[] |
| next_href | string? |