- FRONTEND-GUIDE.md: comprehensive API reference with all 16 endpoints, data models, user flows, auth model, error handling, pagination, provider system, example curl/JSON, playback rendering guide - WIREFRAME.md: page descriptions for all 6 screens, component specs, navigation flow, mobile-first layout, color scheme, typography Both in Russian for mockup development workflow.
29 KiB
Music Orchestrator — Гайд для фронтенда (v0.1.0 MVP)
Содержание
- Видение продукта
- Аутентификация
- Модели данных
- Система провайдеров
- Полный справочник API
- User Flows
- Обработка ошибок
- Пагинация
- Real-time и playback
- CORS и конфигурация
1. Видение продукта
Music Orchestrator — self-hosted сервис в стиле Spotify / YouTube Music, где интернет = библиотека. Пользователь ищет музыку через единый UI, а бэкенд оркестрирует несколько провайдеров (YouTube, SoundCloud, локальная библиотека/Navidrome), нормализует метаданные и отдаёт треки с учётом capabilities и policy каждого источника.
Ключевые принципы
- Единый поиск: один запрос → результаты из всех подключённых провайдеров, merged и deduplicated.
- Capabilities-first: фронтенд никогда не предполагает возможности провайдера — всегда читает
capabilitiesиpolicyиз ответа. - Provider badges: каждый трек показывает, откуда он пришёл и какие ограничения есть.
- Playback-aware: YouTube → embed iframe, Local → audio stream, SoundCloud → official stream URL.
- Безопасный по умолчанию: cache/download кнопки показываются только если
policy.cache_allowed/policy.download_allowed==true.
MVP scope
- Поиск по YouTube (metadata + embed), SoundCloud (metadata), Local (полный stream/cache).
- Избранное и плейлисты (серверные, привязаны к API key).
- Background jobs (resolve, metadata refresh, local ingest) — в MVP только хранят статус, не запускают unsafe extractors.
- Single-user model (один API key = один user_id
local-user).
2. Аутентификация
Все записывающие (POST, DELETE) эндпоинты требуют заголовок X-API-Key.
X-API-Key: <your-api-key>
- По умолчанию dev key:
dev-local-key-change-me(настраивается черезAPP_API_KEYSenv). - Можно указать несколько ключей через запятую в env.
- При невалидном/отсутствующем ключе →
401 Unauthorized.
Пример
curl -X POST http://localhost:8080/v1/favorites \
-H 'X-API-Key: dev-local-key-change-me' \
-H 'Content-Type: application/json' \
-d '{"track_id": "local:seed-1"}'
Для фронтенда
- GET-эндпоинты (search, providers, tracks, playback, health) не требуют API key.
- POST/DELETE (favorites, playlists, jobs) требуют API key.
- Фронтенд должен хранить ключ в localStorage/secure storage и передавать в заголовке.
3. Модели данных
3.1 TrackRead — Трек (результат поиска / получения)
| Поле | Тип | Описание |
|---|---|---|
id |
string |
Уникальный ID трека, формат {provider}:{provider_track_id}, напр. local:seed-1 |
title |
string |
Название трека |
artist |
string? |
Исполнитель |
album |
string? |
Альбом |
duration_seconds |
int? |
Длительность в секундах |
artwork_url |
string? |
URL обложки |
canonical_key |
string |
Нормализованный ключ для deduplication (lowercase artist::title) |
providers |
string[] |
Список ID провайдеров, у которых есть этот трек |
provider_results |
ProviderResult[] |
Детальные результаты от каждого провайдера |
risk_level |
RiskLevel |
Объединённый уровень риска |
capabilities |
Capabilities |
Объединённые capabilities |
policy |
Policy |
Объединённая policy |
3.2 ProviderResult — Результат от одного провайдера
| Поле | Тип | Описание |
|---|---|---|
provider_id |
string |
ID провайдера (local, youtube_official, soundcloud_official) |
provider_track_id |
string |
ID трека внутри провайдера |
title |
string |
Название |
artist |
string? |
Исполнитель |
album |
string? |
Альбом |
duration_seconds |
int? |
Длительность |
artwork_url |
string? |
Обложка |
source_url |
string? |
Прямая ссылка на источник |
attribution |
string? |
Текст attribution (обязателен для SoundCloud) |
risk_level |
RiskLevel |
Уровень риска провайдера |
capabilities |
Capabilities |
Возможности |
policy |
Policy |
Ограничения |
3.3 PlaybackRead — Инструкции воспроизведения
| Поле | Тип | Описание |
|---|---|---|
track_id |
string |
ID трека |
playback_type |
PlaybackType |
Тип воспроизведения |
provider_id |
string |
Какой провайдер используется |
stream_url |
string? |
URL для прямого аудио-стрима (local, official_stream) |
embed_url |
string? |
URL для iframe embed (YouTube) |
expires_in_seconds |
int? |
TTL стрим-URL (для official_stream) |
attribution |
string? |
Текст attribution |
capabilities |
Capabilities |
|
policy |
Policy |
PlaybackType enum
| Значение | Описание | Когда |
|---|---|---|
local_stream |
Прямой аудиофайл | Local/Navidrome |
embed |
iframe embed | YouTube |
official_stream |
Официальный стрим URL | SoundCloud |
unavailable |
Невоспроизводимый | Нет прав/возможностей |
3.4 Capabilities (ProviderCapabilities)
| Поле | Тип | Описание |
|---|---|---|
search_metadata |
bool |
Может искать метаданные |
raw_audio_stream |
bool |
Может отдать raw audio |
embed_playback |
bool |
Поддерживает embed-воспроизведение |
official_stream_url |
bool |
Отдаёт official stream URL |
persistent_cache |
bool |
Разрешено кэшировать на диск |
offline_playback |
bool |
Поддерживает offline |
server_favorites |
bool |
Серверные избранные |
server_playlists |
bool |
Серверные плейлисты |
multiuser_safe |
bool |
Безопасно для multiuser |
public_deployment_safe |
bool |
Безопасно для публичного деплоя |
3.5 Policy
| Поле | Тип | Описание |
|---|---|---|
compliant_mode |
bool |
Работает в compliant режиме |
cache_allowed |
bool |
Можно ли кэшировать (→ показывать кнопку "Скачать") |
download_allowed |
bool |
Можно ли скачивать (→ показывать кнопку "Сохранить") |
requires_attribution |
bool |
Нужно показывать attribution |
requires_external_credentials |
bool |
Нужны внешние credentials |
notes |
string[] |
Пояснения/предупреждения |
3.6 RiskLevel enum
| Значение | Описание | UI-цвет (рекомендация) |
|---|---|---|
compliant |
Полностью легально | 🟢 зелёный |
constrained |
Ограничения (attribution, no cache) | 🟡 жёлтый |
risky |
ToS risk, личное использование | 🔴 красный |
3.7 FavoriteRead
| Поле | Тип |
|---|---|
track_id |
string |
created_at |
string (ISO 8601) |
3.8 PlaylistSummary / PlaylistRead
PlaylistSummary (для списков):
| Поле | Тип |
|---|---|
id |
string (UUID) |
name |
string |
description |
string? |
track_count |
int |
created_at |
string |
updated_at |
string |
PlaylistRead (extends PlaylistSummary):
| Поле | Тип |
|---|---|
tracks |
PlaylistTrackRead[] |
PlaylistTrackRead:
| Поле | Тип |
|---|---|
track_id |
string |
position |
int |
added_at |
string |
3.9 JobRead
| Поле | Тип | Описание |
|---|---|---|
id |
string (UUID) |
ID задачи |
type |
string |
Тип: resolve, metadata_refresh, local_ingest |
status |
JobStatus |
Статус |
track_id |
string? |
Связанный трек |
payload |
object |
Произвольные параметры |
result |
object? |
Результат |
error |
string? |
Текст ошибки |
created_at |
string |
|
updated_at |
string |
JobStatus enum: queued → running → succeeded / failed / blocked_by_policy
3.10 ProviderRead
| Поле | Тип |
|---|---|
id |
string |
name |
string |
kind |
"local" | "youtube" | "soundcloud" |
enabled |
bool |
configured |
bool |
risky_enabled |
bool |
risk_level |
RiskLevel |
capabilities |
Capabilities |
policy |
Policy |
docs_url |
string? |
3.11 HealthRead
| Поле | Тип |
|---|---|
status |
"ok" |
mode |
"compliant" |
risky_extractors_enabled |
bool |
database |
string |
4. Система провайдеров
Провайдеры в MVP
| Провайдер | ID | kind | Что может | Playback |
|---|---|---|---|---|
| Local / Navidrome | local |
local |
Поиск, stream, cache, download, offline | local_stream (прямой URL к файлу) |
| YouTube official | youtube_official |
youtube |
Metadata/search (нужен API key), embed playback | embed (iframe youtube.com/embed/...) |
| SoundCloud official | soundcloud_official |
soundcloud |
Metadata/search (нужен client_id), official stream | official_stream (временный URL) |
Ключевое правило для UI
Перед показом любой кнопки воспроизведения / скачивания / кэша:
1. Проверить capabilities нужного типа
2. Проверить policy.cache_allowed / policy.download_allowed
3. Показывать provider badge и risk_level на каждом треке
Track ID формат
{provider_id}:{provider_track_id}
Примеры: local:seed-1, youtube_official:dQw4w9WgXcQ, soundcloud_official:12345
5. Полный справочник API
Base URL: http://localhost:8080
OpenAPI schema: GET /openapi.json
5.1 GET /health — Проверка здоровья
Auth: не нужен
Ответ 200:
{
"status": "ok",
"mode": "compliant",
"risky_extractors_enabled": false,
"database": "sqlite"
}
curl http://localhost:8080/health
5.2 GET /v1/providers — Список провайдеров
Auth: не нужен
Ответ 200:
{
"items": [
{
"id": "local",
"name": "Local / Navidrome-compatible library",
"kind": "local",
"enabled": true,
"configured": true,
"risky_enabled": false,
"risk_level": "compliant",
"capabilities": {
"search_metadata": true,
"raw_audio_stream": true,
"embed_playback": false,
"official_stream_url": false,
"persistent_cache": true,
"offline_playback": true,
"server_favorites": true,
"server_playlists": true,
"multiuser_safe": true,
"public_deployment_safe": true
},
"policy": {
"compliant_mode": true,
"cache_allowed": true,
"download_allowed": true,
"requires_attribution": false,
"requires_external_credentials": false,
"notes": ["Local files only. ..."]
},
"docs_url": "https://www.navidrome.org/docs/developers/subsonic-api/"
},
{
"id": "youtube_official",
"name": "YouTube official metadata/embed",
"kind": "youtube",
"enabled": false,
"configured": false,
"risk_level": "compliant",
"capabilities": {
"raw_audio_stream": false,
"embed_playback": true,
"persistent_cache": false,
"offline_playback": false,
"public_deployment_safe": true
},
"policy": {
"cache_allowed": false,
"download_allowed": false,
"requires_external_credentials": true,
"notes": ["Metadata/search requires YouTube Data API key. ..."]
},
"docs_url": "https://developers.google.com/youtube/v3"
}
]
}
curl http://localhost:8080/v1/providers
5.3 GET /v1/search — Поиск треков
Auth: не нужен
Параметры запроса:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
q |
string |
обязательный | Поисковый запрос (1–200 символов) |
providers |
string |
local,youtube_official,soundcloud_official |
Через запятую |
limit |
int |
20 |
1–50 |
offset |
int |
0 |
≥ 0 |
Ответ 200:
{
"query": "demo song",
"limit": 20,
"offset": 0,
"total": 2,
"items": [
{
"id": "local:seed-1",
"title": "Demo Song",
"artist": "Local Artist",
"album": "Home Library",
"duration_seconds": 180,
"artwork_url": null,
"canonical_key": "local artist::demo song",
"providers": ["local"],
"provider_results": [
{
"provider_id": "local",
"provider_track_id": "seed-1",
"title": "Demo Song",
"artist": "Local Artist",
"album": "Home Library",
"duration_seconds": 180,
"artwork_url": null,
"source_url": "/media/demo-song.opus",
"attribution": "Local library",
"risk_level": "compliant",
"capabilities": { "...": "..." },
"policy": { "...": "..." }
}
],
"risk_level": "compliant",
"capabilities": {
"raw_audio_stream": true,
"persistent_cache": true,
"cache_allowed": true,
"download_allowed": true
},
"policy": {
"compliant_mode": true,
"cache_allowed": true,
"download_allowed": true,
"requires_attribution": false,
"requires_external_credentials": false,
"notes": ["Local files only. ..."]
}
}
]
}
curl 'http://localhost:8080/v1/search?q=demo%20song&limit=10'
curl 'http://localhost:8080/v1/search?q=rock&providers=local&limit=5&offset=0'
5.4 GET /v1/tracks/{track_id} — Получить трек по ID
Auth: не нужен
Path params: track_id — формат {provider}:{id}, напр. local:seed-1
Ответ 200: объект TrackRead (см. §3.1)
Ответ 404: если трек не найден
curl http://localhost:8080/v1/tracks/local:seed-1
5.5 GET /v1/playback/{track_id} — Получить инструкции воспроизведения
Auth: не нужен
Path params: track_id
Ответ 200: объект PlaybackRead (см. §3.3)
В зависимости от провайдера вернётся один из типов:
| playback_type | stream_url | embed_url | Пример UI |
|---|---|---|---|
local_stream |
✅ прямой URL файла | — | <audio src="..."> |
embed |
— | ✅ youtube.com/embed/... |
<iframe src="..."> |
official_stream |
✅ временный URL | — | <audio src="..."> (с учётом expires) |
unavailable |
— | — | "Невоспроизводимо" |
curl http://localhost:8080/v1/playback/local:seed-1
Пример ответа для YouTube:
{
"track_id": "youtube_official:abc123",
"playback_type": "embed",
"provider_id": "youtube_official",
"stream_url": null,
"embed_url": "https://www.youtube.com/embed/abc123",
"expires_in_seconds": null,
"attribution": "YouTube embed playback; metadata not resolved without API key in this MVP.",
"capabilities": { "embed_playback": true, "raw_audio_stream": false, "..." : "..." },
"policy": { "cache_allowed": false, "download_allowed": false, "..." : "..." }
}
5.6 POST /v1/favorites — Добавить в избранное
Auth: X-API-Key ✅
Request body:
{
"track_id": "local:seed-1"
}
track_id: 3–300 символов.
Ответ 201:
{
"track_id": "local:seed-1",
"created_at": "2026-07-10T12:00:00+00:00"
}
Идемпотентно: повторный POST того же track_id → 200 с тем же объектом (не создаёт дубликат).
curl -X POST http://localhost:8080/v1/favorites \
-H 'X-API-Key: dev-local-key-change-me' \
-H 'Content-Type: application/json' \
-d '{"track_id": "local:seed-1"}'
5.7 GET /v1/favorites — Список избранных
Auth: X-API-Key ✅
Query params: limit (1–100, default 50), offset (default 0)
Ответ 200:
{
"limit": 50,
"offset": 0,
"total": 1,
"items": [
{ "track_id": "local:seed-1", "created_at": "2026-07-10T12:00:00+00:00" }
]
}
curl http://localhost:8080/v1/favorites -H 'X-API-Key: dev-local-key-change-me'
5.8 DELETE /v1/favorites/{track_id} — Удалить из избранного
Auth: X-API-Key ✅
Ответ: 204 No Content (идемпотентно — если не было в избранном, тоже 204)
curl -X DELETE http://localhost:8080/v1/favorites/local:seed-1 \
-H 'X-API-Key: dev-local-key-change-me'
5.9 POST /v1/playlists — Создать плейлист
Auth: X-API-Key ✅
Request body:
{
"name": "My Playlist",
"description": "Optional description"
}
name: 1–120 символов. description: до 500 символов, опционально.
Ответ 201:
{
"id": "uuid-...",
"name": "My Playlist",
"description": "Optional description",
"track_count": 0,
"created_at": "2026-07-10T12:00:00+00:00",
"updated_at": "2026-07-10T12:00:00+00:00",
"tracks": []
}
curl -X POST http://localhost:8080/v1/playlists \
-H 'X-API-Key: dev-local-key-change-me' \
-H 'Content-Type: application/json' \
-d '{"name": "Chill Vibes", "description": "Relaxing music"}'
5.10 GET /v1/playlists — Список плейлистов
Auth: X-API-Key ✅
Query params: limit (1–100, default 50), offset (default 0)
Ответ 200:
{
"limit": 50,
"offset": 0,
"total": 1,
"items": [
{
"id": "uuid-...",
"name": "Chill Vibes",
"description": "Relaxing music",
"track_count": 3,
"created_at": "2026-07-10T12:00:00+00:00",
"updated_at": "2026-07-10T12:30:00+00:00"
}
]
}
curl http://localhost:8080/v1/playlists -H 'X-API-Key: dev-local-key-change-me'
5.11 GET /v1/playlists/{playlist_id} — Плейлист с треками
Auth: X-API-Key ✅
Ответ 200:
{
"id": "uuid-...",
"name": "Chill Vibes",
"description": "Relaxing music",
"track_count": 2,
"created_at": "2026-07-10T12:00:00+00:00",
"updated_at": "2026-07-10T12:30:00+00:00",
"tracks": [
{ "track_id": "local:seed-1", "position": 1, "added_at": "2026-07-10T12:10:00+00:00" },
{ "track_id": "local:seed-2", "position": 2, "added_at": "2026-07-10T12:20:00+00:00" }
]
}
Ответ 404: если плейлист не найден или принадлежит другому пользователю.
curl http://localhost:8080/v1/playlists/uuid-... -H 'X-API-Key: dev-local-key-change-me'
5.12 POST /v1/playlists/{playlist_id}/tracks — Добавить трек в плейлист
Auth: X-API-Key ✅
Request body:
{
"track_id": "local:seed-2"
}
Ответ 201: возвращает обновлённый PlaylistRead (с обновлённым списком треков).
Идемпотентно: если трек уже в плейлите, не создаёт дубликат.
curl -X POST http://localhost:8080/v1/playlists/uuid-.../tracks \
-H 'X-API-Key: dev-local-key-change-me' \
-H 'Content-Type: application/json' \
-d '{"track_id": "local:seed-2"}'
5.13 POST /v1/jobs — Создать фоновую задачу
Auth: X-API-Key ✅
Request body:
{
"type": "resolve",
"track_id": "youtube_official:abc123",
"payload": {}
}
type: "resolve" | "metadata_refresh" | "local_ingest"
Ответ 202:
{
"id": "uuid-...",
"type": "resolve",
"status": "queued",
"track_id": "youtube_official:abc123",
"payload": {},
"result": null,
"error": null,
"created_at": "2026-07-10T12:00:00+00:00",
"updated_at": "2026-07-10T12:00:00+00:00"
}
MVP note: задачи создаются и сохраняются со статусом
queued, но не запускают реальных unsafe extractors.
curl -X POST http://localhost:8080/v1/jobs \
-H 'X-API-Key: dev-local-key-change-me' \
-H 'Content-Type: application/json' \
-d '{"type": "metadata_refresh", "track_id": "local:seed-1"}'
5.14 GET /v1/jobs — Список задач
Auth: X-API-Key ✅
Query params: limit (1–100, default 50), offset (default 0)
Ответ 200: JobList с массивом JobRead
curl http://localhost:8080/v1/jobs -H 'X-API-Key: dev-local-key-change-me'
5.15 GET /v1/jobs/{job_id} — Получить задачу
Auth: X-API-Key ✅
Ответ 200: JobRead
Ответ 404: если задача не найдена
curl http://localhost:8080/v1/jobs/uuid-... -H 'X-API-Key: dev-local-key-change-me'
6. User Flows
6.1 Поиск → Прослушивание → Избранное → Плейлист
1. Пользователь вводит запрос в поисковую строку
2. GET /v1/search?q={query} → список треков
3. Клик на трек → GET /v1/playback/{track_id}
4. В зависимости от playback_type:
- local_stream → <audio src="{stream_url}">
- embed → <iframe src="{embed_url}">
- official_stream → <audio src="{stream_url}"> (с отслеживанием expires)
- unavailable → показать "Невоспроизводимо"
5. Кнопка ❤️ → POST /v1/favorites {track_id}
6. Кнопка "+ в плейлист" → POST /v1/playlists/{id}/tracks {track_id}
6.2 Просмотр библиотеки → Прослушивание
1. GET /v1/favorites → список избранных треков
2. Для каждого track_id → GET /v1/tracks/{track_id} (метаданные)
3. Клик → GET /v1/playback/{track_id} → воспроизведение
4. Аналогично для плейлистов: GET /v1/playlists → GET /v1/playlists/{id}
6.3 Скачивание/кэширование трека
1. Проверить policy.cache_allowed == true и policy.download_allowed == true
2. Только тогда показывать кнопку "Скачать"
3. GET /v1/playback/{track_id} → получить stream_url
4. Фронтенд: <a href="{stream_url}" download> или fetch + blob
5. (Опционально) POST /v1/jobs { type: "local_ingest", track_id, payload: { source_url } }
⚠️ Кнопки скачивания скрыты для YouTube и SoundCloud (policy.download_allowed == false).
6.4 Управление плейлистами
1. Создать: POST /v1/playlists { name, description? }
2. Список: GET /v1/playlists
3. Детали: GET /v1/playlists/{id} → список треков с позициями
4. Добавить трек: POST /v1/playlists/{id}/tracks { track_id }
5. (MVP: нет удаления трека из плейлиста, нет переупорядочивания)
7. Обработка ошибок
Формат ошибки
Все ошибки возвращают JSON в едином формате:
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"details": null
}
}
Коды ошибок
| HTTP статус | code | Когда |
|---|---|---|
401 |
unauthorized |
Невалидный или отсутствующий X-API-Key |
404 |
http_error |
Трек/плейлист/задача не найдена |
422 |
validation_error |
Невалидные параметры (Pydantic validation) |
Для фронтенда
- Показывать toast/banner с
error.messageпользователю. - При 401 — редирект на экран ввода API key.
- При 422 — подсветить невалидное поле.
8. Пагинация
Все list-эндпоинты используют единую схему:
Запрос:
limit— количество элементов (default зависит от эндпоинта)offset— смещение
Ответ:
{
"limit": 20,
"offset": 0,
"total": 150,
"items": [...]
}
Для фронта: total показывает общее количество; для "Load More" увеличивать offset += limit. Для постраничной навигации: page = offset / limit + 1, totalPages = ceil(total / limit).
Эндпоинты с пагинацией:
GET /v1/search(limit 1–50)GET /v1/favorites(limit 1–100)GET /v1/playlists(limit 1–100)GET /v1/jobs(limit 1–100)
9. Real-time и playback
Текущий MVP
В MVP нет WebSocket или SSE. Playback статус отслеживается на стороне фронтенда:
<audio>/<iframe>элементы + JS events (play,pause,ended,timeupdate).- Для jobs: поллинг
GET /v1/jobs/{job_id}с интервалом 2–5 секунд.
Планируется
- WebSocket/SSE для real-time job status updates.
- Server-sent playback events для multi-device sync.
10. CORS и конфигурация
CORS
Сервер настроен на:
- Origins:
http://localhost:5173,http://localhost:3000(Vite dev, CRA) - Methods:
GET,POST,DELETE - Headers:
X-API-Key,Content-Type - Credentials: отключены
ENV-переменные (для справки)
| Переменная | По умолчанию | Описание |
|---|---|---|
APP_API_KEYS |
dev-local-key-change-me |
API ключи через запятую |
APP_CORS_ORIGINS |
http://localhost:5173,... |
Разрешённые origins |
APP_DATABASE_URL |
sqlite:///./data/music-orchestrator.db |
БД |
APP_YOUTUBE_API_KEY |
пусто | YouTube Data API key |
APP_SOUNDCLOUD_CLIENT_ID |
пусто | SoundCloud client_id |
APP_ENABLE_RISKY_EXTRACTORS |
false |
Включить risky extractors |
Приложение: Quick Reference — все эндпоинты
| # | Method | Path | Auth | Описание |
|---|---|---|---|---|
| 1 | GET | /health |
— | Статус сервиса |
| 2 | GET | /v1/providers |
— | Список провайдеров |
| 3 | GET | /v1/search?q=... |
— | Поиск треков |
| 4 | GET | /v1/tracks/{track_id} |
— | Трек по ID |
| 5 | GET | /v1/playback/{track_id} |
— | Инструкции воспроизведения |
| 6 | POST | /v1/favorites |
✅ | Добавить в избранное |
| 7 | GET | /v1/favorites |
✅ | Список избранных |
| 8 | DELETE | /v1/favorites/{track_id} |
✅ | Удалить из избранного |
| 9 | POST | /v1/playlists |
✅ | Создать плейлист |
| 10 | GET | /v1/playlists |
✅ | Список плейлистов |
| 11 | GET | /v1/playlists/{playlist_id} |
✅ | Плейлист с треками |
| 12 | POST | /v1/playlists/{playlist_id}/tracks |
✅ | Добавить трек в плейлист |
| 13 | POST | /v1/jobs |
✅ | Создать задачу |
| 14 | GET | /v1/jobs |
✅ | Список задач |
| 15 | GET | /v1/jobs/{job_id} |
✅ | Задача по ID |
| 16 | GET | /openapi.json |
— | OpenAPI схема |