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Отказ от ответственности

report

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:

Все эндпоинты на этой странице указаны относительно:

URL
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 получает его автоматически при первом запуске:

  1. Download the HTML of https://soundcloud.com.
  2. Загрузить HTML страницы https://soundcloud.com.
  3. Extract <script src="…"> URLs pointing at a-v2.sndcdn.com/assets/*.js.
  4. Извлечь из неё URL-адреса скриптов <script src="…">, ведущие на a-v2.sndcdn.com/assets/*.js.
  5. Download those bundles (checked in reverse order) and regex-match a 32 character client id.
  6. Скачать эти бандлы (в обратном порядке) и вытащить регуляркой 32-символьный client_id.
  7. 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.
  8. Закэшировать значение и переиспользовать его, пока запрос не вернёт 401/403, тогда id обновляется и запрос повторяется один раз.
Regex
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-Agenta recent desktop Chrome UA stringстрока UA свежего десктопного Chromerecommendedрекомендуется
Acceptapplication/json, text/javascript, */*; q=0.1recommendedрекомендуется
Originhttps://soundcloud.comrecommendedрекомендуется
Refererhttps://soundcloud.com/recommendedрекомендуется
X-SoundCloud-Clientsame value as client_idто же значение, что и client_idoptionalопционально
X-Client-UUIDrandom UUID, new per requestслучайный UUID на каждый запросoptionalопционально
AuthorizationOAuth <token>user-specific endpoints onlyтолько для пользовательских эндпоинтов
Cookiecaptured web session cookiesперехваченные cookies веб-сессииwrite endpoints onlyтолько для эндпоинтов записи

view_listPaginationПагинация

List endpoints return a consistent envelope:

Эндпоинты со списками возвращают единый формат ответа:

SearchResponse<T>
{
  "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 / 403client_id is invalid, expired or rotated. Refresh it (see client_id) and retry once.client_id недействителен, истёк или сменился. Обновите его (см. client_id) и повторите запрос один раз.
404Entity does not exist, is private, or was deleted.Сущность не существует, приватна или удалена.
429Rate limited. Back off with increasing delay before retrying; do not retry immediately in a loop.Превышен лимит запросов. Перед повтором нужна увеличивающаяся задержка, не повторяйте запрос сразу в цикле.
shield

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(), чтобы запрос нёс подлинный браузерный отпечаток.

music_noteTracksТреки

GET /tracks/{id}

Fetch a single track by its numeric id, including its media.transcodings list needed for playback.

Получить один трек по числовому id, включая список media.transcodings, нужный для воспроизведения.

ParamПараметрTypeТипDescriptionОписание
idlongpath parameter, track idпараметр пути, id трека
cURL
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:

Ссылки на воспроизводимый поток никогда не отдаются напрямую. Их получение требует двух запросов:

  1. Fetch the track (GET /tracks/{id}). Its media.transcodings array lists candidate streams, each with a format.protocol of either progressive (plain MP3 file) or hls (segmented .m3u8 playlist), and its own internal, unsigned url.
  2. Получить трек (GET /tracks/{id}). В его массиве media.transcodings перечислены варианты потоков, у каждого есть format.protocol: либо progressive (обычный MP3-файл), либо hls (сегментированный плейлист .m3u8), и собственный внутренний неподписанный url.
  3. Prefer the progressive transcoding when available, otherwise fall back to the first one listed. Call that transcoding's url directly (with client_id appended) to get the actual, signed, time-limited CDN URL.
  4. По возможности берите транскодинг progressive, иначе первый из списка. Обратитесь напрямую по url этого транскодинга (с добавленным client_id), чтобы получить настоящий подписанный, ограниченный по времени CDN-адрес.
Track.media.transcodings
[
  {
    "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 (step 2)(шаг 2)
curl "https://api-v2.soundcloud.com/media/soundcloud:tracks:293100293/abc.../stream/progressive?client_id=YOUR_CLIENT_ID"
Response
{
  "url": "https://cf-media.sndcdn.com/xxxxxxxxxxxx.128.mp3?Policy=...&Signature=...&Key-Pair-Id=..."
}
timer

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Пользователи

GET /users/{id}

Fetch a public user profile by numeric id.

Получить публичный профиль пользователя по числовому id.

cURL
curl "https://api-v2.soundcloud.com/users/8153021?client_id=YOUR_CLIENT_ID"
Response
{
  "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"
}
GET /users/{id}/tracks

List tracks uploaded by a user, paginated.

Список треков, загруженных пользователем, с пагинацией.

ParamПараметрTypeТипDescriptionОписание
limitintpage size, default 50размер страницы, по умолчанию 50
offsetintfirst page offset, default 0смещение первой страницы, по умолчанию 0
cURL
curl "https://api-v2.soundcloud.com/users/8153021/tracks?limit=50&offset=0&client_id=YOUR_CLIENT_ID"

queue_musicPlaylistsПлейлисты

GET /playlists/{id}

Fetch a playlist or album, including its full track list.

Получить плейлист или альбом вместе с полным списком треков.

cURL
curl "https://api-v2.soundcloud.com/playlists/5521003?client_id=YOUR_CLIENT_ID"
Response
{
  "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Лайки

GET /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Описание
limitintpage size, default 200размер страницы, по умолчанию 200
linked_partitioningbooluse cursor-style pagination via next_href, default trueкурсорная пагинация через next_href, по умолчанию true
cURL
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"
Response
{
  "collection": [
    { "kind": "like", "track": { "id": 293100293, "title": "Midnight Drive" } }
  ],
  "next_href": "https://api-v2.soundcloud.com/users/8153021/likes?cursor=..."
}
PUT /users/{userId}/track_likes/{trackId} lockOAuth requiredТребуется OAuth

Like a track on behalf of the authenticated user.

Поставить трек в лайки от имени авторизованного пользователя.

shield

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-мост.

JS fetch() (inside WebView)(внутри WebView)
fetch(`https://api-v2.soundcloud.com/users/${userId}/track_likes/${trackId}?client_id=${clientId}`, {
  method: 'PUT',
  headers: { 'Authorization': `OAuth ${token}` },
  credentials: 'include'
})
DELETE /users/{userId}/track_likes/{trackId} lockOAuth requiredТребуется OAuth

Unlike a track. Same WebView-bridge requirement as above.

Убрать трек из лайков. Требует того же обхода через WebView, что и выше.

account_circleCurrent userТекущий пользователь

GET /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
curl "https://api-v2.soundcloud.com/me?client_id=YOUR_CLIENT_ID" \
  -H "Authorization: OAuth YOUR_OAUTH_TOKEN"

linkResolveResolve

GET /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Описание
urlstringURL-encoded soundcloud.com linkURL-encoded ссылка на soundcloud.com
cURL
curl "https://api-v2.soundcloud.com/resolve?url=https%3A%2F%2Fsoundcloud.com%2Fartist%2Fmidnight-drive&client_id=YOUR_CLIENT_ID"
Response
{
  "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ПолеTypeDescriptionОписание
idlong
titlestring
durationlongmillisecondsв миллисекундах
artwork_urlstring?
genrestring?
playback_countlong?
likes_countlong?
permalink_urlstring?
userUser
media.transcodingsTranscoding[]see Stream resolutionсм. Разрешение потока

personUser

FieldПолеType
idlong
usernamestring
full_namestring?
avatar_urlstring?
followers_countlong?
permalink_urlstring?

queue_musicPlaylist

FieldПолеType
idlong
titlestring
descriptionstring?
artwork_urlstring?
track_countint
permalink_urlstring?
userUser
tracksTrack[]?

graphic_eqTranscoding

FieldПолеType
urlstring
qualitystring?
format.protocol"progressive" | "hls"
format.mime_typestring

layersSearchResponse<T>

FieldПолеType
collectionT[]
next_hrefstring?