Cómo empezar
- Solicita acceso a soporte@dharell.music indicando tu sello y volumen mensual.
- Recibirás
client_id,client_secretywebhook_secret. - Empieza en modo sandbox: los lanzamientos se validan pero no se entregan.
- Cuando la integración esté lista, activamos producción.
https://app.dharell.music/api/public/delivery/v1Todo lanzamiento enviado por API entra en revisión de Dharell antes de distribuirse a las DSPs.
0. Guía de integración paso a paso
Este es el recorrido completo, en orden, desde que recibes tus credenciales hasta que el lanzamiento queda en revisión de Dharell. Cada paso muestra el request y la respuesta esperada.
curl -s -X POST https://app.dharell.music/api/public/delivery/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"client_credentials","client_id":"dhc_...","client_secret":"dhs_..."}'
# → { "success": true, "data": { "access_token": "dht_...", "expires_in": 3600 } }
export TOKEN="dht_..."curl -s https://app.dharell.music/api/public/delivery/v1/me -H "Authorization: Bearer $TOKEN"
# → data.labels[] son los únicos label_id que puedes usar
# → data.scopes[] y data.rate_limits{} confirman tus permisoscurl -s -X POST https://app.dharell.music/api/public/delivery/v1/artists \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"label_id":"<LABEL_ID>","name":"Mi Artista","spotify_id":null}'
# → { "success": true, "data": { "id": "<ARTIST_ID>", "name": "Mi Artista" } }curl -s -X POST https://app.dharell.music/api/public/delivery/v1/releases \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"label_id": "<LABEL_ID>",
"title": "Mi Sencillo",
"primary_artist_id": "<ARTIST_ID>",
"release_type": "single",
"genre": "Latin",
"language": "es",
"release_date": "2026-09-12",
"copyright_line": "2026 Mi Sello",
"production_line": "2026 Mi Sello"
}'
# → data.id = <RELEASE_ID>, data.upc se genera automático si no lo envías# 5a. Pedir la URL de subida
curl -s -X POST https://app.dharell.music/api/public/delivery/v1/uploads \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"release_id":"<RELEASE_ID>","kind":"cover","filename":"cover.jpg","content_type":"image/jpeg"}'
# → data.upload_url (PUT directo) y data.path
# 5b. Subir el archivo real
curl -s -X PUT "<UPLOAD_URL>" -H "Content-Type: image/jpeg" --data-binary @cover.jpg
# 5c. Confirmar que el archivo existe en almacenamiento
curl -s "https://app.dharell.music/api/public/delivery/v1/uploads?path=<PATH>" -H "Authorization: Bearer $TOKEN"
# Repite con kind=audio (WAV 16/24-bit, 44.1kHz+) para cada pista.curl -s -X POST https://app.dharell.music/api/public/delivery/v1/releases/<RELEASE_ID>/tracks \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"title": "Mi Sencillo",
"track_number": 1,
"audio_path": "<PATH_DEL_AUDIO>",
"explicit": false,
"contributors": [{ "name": "Juan Pérez", "role": "composer", "ipi": "00123456789" }],
"splits": [{ "email": "juan@ejemplo.com", "percentage": 100 }]
}'
# Ajustes posteriores: PATCH /releases/<RELEASE_ID>/tracks (body con track_id)
# Eliminar pista: DELETE /releases/<RELEASE_ID>/tracks?track_id=<TRACK_ID>curl -s https://app.dharell.music/api/public/delivery/v1/releases/<RELEASE_ID>/qc -H "Authorization: Bearer $TOKEN"
# → data.errors[] bloquea el envío; data.warnings[] es informativo.
# Corrige los errores y vuelve a consultar hasta que errors esté vacío.curl -s -X POST https://app.dharell.music/api/public/delivery/v1/releases/<RELEASE_ID>/submit \
-H "Authorization: Bearer $TOKEN"
# → 200 { "data": { "status": "in_review" } }
# → 422 si el QC de archivos falla (ver details.errors)# Recomendado: webhook firmado con HMAC-SHA256 (ver sección de webhooks)
# Alternativa: polling cada 15-30 min
curl -s https://app.dharell.music/api/public/delivery/v1/releases/<RELEASE_ID> -H "Authorization: Bearer $TOKEN"
# Estados: draft → in_review → approved → delivered → live
# rejected / changes_requested si necesita correcciones# Analytics (scope analytics:read)
curl -s "https://app.dharell.music/api/public/delivery/v1/analytics?days=30" -H "Authorization: Bearer $TOKEN"
# Regalías por periodo
curl -s "https://app.dharell.music/api/public/delivery/v1/royalties?from=2026-01-01&to=2026-06-30" -H "Authorization: Bearer $TOKEN"
# Solicitar bajada de un lanzamiento publicado
curl -s -X POST https://app.dharell.music/api/public/delivery/v1/releases/<RELEASE_ID>/takedown \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"reason":"Fin de contrato con el artista"}'async function call(path, init) {
const res = await fetch(path, init);
const json = await res.json();
if (res.status === 401) return refreshTokenAndRetry(); // token vencido
if (res.status === 429) return retryAfter(60_000); // respeta el límite
if (res.status >= 500) return retryWithBackoff([1, 5, 15, 60]); // error transitorio
if (!json.success) throw new ApiError(json.error_code, json.message, json.details);
return json.data; // los datos SIEMPRE viven en json.data
}· Cachea el access_token y renuévalo ~5 min antes de expirar (no pidas uno por request).
· Guarda nuestro release_id junto a tu id interno para conciliar estados.
· Empieza en sandbox: valida el flujo completo antes de pedir producción.
· Verifica la firma HMAC de cada webhook antes de procesarlo.
· No reintentes errores 4xx de validación: corrige el payload.
· Sube el audio en WAV; nosotros generamos los formatos de cada tienda.1. Formato de respuesta
Todas las respuestas usan el mismo sobre JSON. Los datos siempre vienen dentro de «data»; las listas incluyen «pagination». Recuerda leer siempre response.data en tu cliente.
/Índice de la API con la lista de endpoints/healthEstado del servicio (no requiere token){
"success": true,
"data": { "...": "..." },
"message": "OK"
}{
"success": true,
"data": [ { "...": "..." } ],
"pagination": { "page": 1, "limit": 50, "total": 128, "total_pages": 3 }
}2. Autenticación (OAuth2)
Cambia tu client_id y client_secret por un access token de corta duración. Acepta JSON, form-urlencoded o Basic auth. Envía el token en el header Authorization en todas las demás llamadas.
/oauth/tokenEmite un access token (grant_type=client_credentials)curl -X POST https://app.dharell.music/api/public/delivery/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "dhc_tusello_XXXXXXXXXXXX",
"client_secret": "dhs_tusello_XXXXXXXXXX_..."
}'{
"success": true,
"data": {
"access_token": "dht_XXXXXXXXXX_...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "catalog:read releases:read releases:write"
},
"message": "OK"
}curl https://app.dharell.music/api/public/delivery/v1/me \
-H "Authorization: Bearer $ACCESS_TOKEN"catalog:read · sellos, géneros, tiendas y artistas
releases:read · leer lanzamientos, tracks, QC, estado y takedowns
releases:write · crear/editar lanzamientos, subir archivos, enviar a revisión3. Catálogo y referencias
Antes de crear un lanzamiento, obtén los sellos autorizados para tu cliente, los géneros DDEX válidos y las tiendas disponibles.
/mePerfil del cliente, scopes, límites y sellos autorizados/labelsSellos que puedes usar como label_id/genresGéneros y subgéneros aceptados/storesDSPs habilitadas (Spotify, Apple Music, etc.)/artistsArtistas del sello (q, page, page_size, label_id)/artistsCrea un artista (idempotente por nombre + sello)CÓDIGO NOMBRE REGIÓN ALIAS ACEPTADOS
spotify Spotify Global —
apple_music Apple Music Global apple, itunes, applemusic, apple music
youtube_music YouTube Music Global youtube, yt, ytmusic, youtube music, yt_music
amazon_music Amazon Music Global amazon, amazonmusic, amazon music
tidal Tidal Global —
deezer Deezer Global —
tiktok TikTok Global tiktok_resso, resso, tik tok
instagram_facebook Instagram / Facebook Global facebook, fb, instagram, ig, meta, instagram/facebook, facebook/instagram
audiomack Audiomack Hip-Hop —
soundcloud SoundCloud Global soundcloud_go, sc
pandora Pandora USA —
napster Napster Global rhapsody
anghami Anghami MENA —
beatport Beatport Electrónica —
traxsource Traxsource House / Tech —
boomplay Boomplay África —
kkbox KKBOX Asia —
jiosaavn JioSaavn India jio, saavn
qobuz Qobuz Hi-Res —
iheartradio iHeartRadio USA iheart, iheart radio
lissen Lissen Web3 —
joox JOOX Asia —
tuned_global Tuned Global Global tunedglobal, line_music, line_music_japan, gabb_music, gabb, waw_musik, waw, fanlabel
alibaba Alibaba China xiami, alibaba_music
audible_magic_fulfillment360 Audible Magic — Fulfillment360 Global fulfillment360, audible_magic
audible_magic_rights360 Audible Magic — Rights360 Global rights360
supernatural Supernatural Fitness / VR —
imusica iMusica (Claro Music) LatAm claro_music, claro, i_musica
sevendigital 7digital Global 7digital, seven_digital
flo FLO Corea —
mixcloud Mixcloud Global —
awa AWA Japón —
peloton Peloton Fitness —
netease NetEase Cloud Music China netease_cloud_music, netease_music
acrcloud ACRCloud Reconocimiento acr_cloud, music_recognition, audio_fingerprinting
trebel Trebel LatAm / USA —
tencent_music Tencent Music (QQ, Kugou, Kuwo, WeSing) China tencent, qq_music, kugou, kugou_music, kuwo, kuwo_music, wesing
touchtunes TouchTunes Jukebox touchtunes_jukebox, touchtunes_mobile
leaplay Leaplay Global —
ami_entertainment AMI Entertainment Jukebox ami, ami_music, ami_jukeboxes
adaptr Adaptr Sync / Apps —
# Envía SIEMPRE el CÓDIGO en "stores". Los alias se aceptan y se
# convierten automáticamente (ej. "facebook" -> "instagram_facebook").
# Cualquier otro valor devuelve 422 VALIDATION_ERROR con la lista válida.
# Lista viva y actualizada: GET https://app.dharell.music/api/public/delivery/v1/storescurl -X POST https://app.dharell.music/api/public/delivery/v1/artists \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label_id": "uuid-del-sello",
"name": "Nombre Artístico",
"legal_name": "Nombre Legal",
"country": "DO",
"spotify_id": "3TVXtAsR1Inumwj472S9r4",
"apple_artist_id": 271256,
"spotify_url": "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4",
"apple_music_url": "https://music.apple.com/artist/271256",
"isni": null,
"ipi": null
}'
# Si el artista ya existe con ese nombre en el sello, se devuelve
# el existente con "created": false (no se duplica).4. Subida de audio y portada
Los archivos se suben directo a nuestro almacenamiento con URLs firmadas. Pide la URL, haz PUT del archivo y guarda el «path» devuelto para usarlo en cover_art_path (lanzamiento) o audio_path (track). Audio: wav, flac, aif, aiff. Portada: jpg, jpeg, png (mínimo 3000×3000).
/uploadsGenera una URL firmada (kind: audio | cover)/uploads?path=…&kind=audio|coverVerifica que el archivo llegó al almacenamiento (exists, size_bytes) antes de enviar a revisióncurl -X POST https://app.dharell.music/api/public/delivery/v1/uploads \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "audio",
"filename": "01-track.wav",
"label_id": "uuid-del-sello",
"content_type": "audio/wav",
"size_bytes": 48211002
}'
# Respuesta
{
"success": true,
"data": {
"kind": "audio",
"bucket": "release-audio",
"path": "uuid-del-sello/api/<client>/xxxxx-01-track.wav",
"upload_url": "https://...",
"method": "PUT",
"token": "...",
"expires_at": "2026-08-07T06:00:00.000Z"
}
}curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: audio/wav" \
-H "x-upsert: true" \
--upload-file 01-track.wav
# La URL firmada caduca en 120 minutos.5. Lanzamientos
Crea el lanzamiento en estado draft, agrega los tracks uno a uno y luego envíalo a revisión. UPC e ISRC se asignan automáticamente al enviar si no los envías tú.
/releasesLista de lanzamientos del cliente/releasesCrea un lanzamiento en draft/releases/{id}Detalle completo con tracks y tiendas/releases/{id}Actualiza (sólo en draft o rejected)/releases/{id}/tracksLista las pistas del lanzamiento/releases/{id}/tracksAgrega una pista (un objeto por llamada)/releases/{id}/tracksActualiza una pista: adjunta el audio subido (audio_path), metadata, contribuidores y splits. Requiere track_id en el body/releases/{id}/tracks?track_id=…Elimina una pista con sus contribuidores y splits/releases/{id}/submitValida, corre el QC técnico y envía a revisión de Dharell/releases/{id}/qcFicha de control de calidad técnico (misma que ve el equipo)/releases/{id}/qcRe-ejecuta el QC de archivos sin enviar a revisión/releases/{id}/statusHistorial de estado y entregas por DSP/releases/{id}/takedownSolicita retirada del catálogo/releases/{id}/takedownEstado e historial de solicitudes de bajada/releases/{id}/takedownCancela una solicitud de bajada pendiente/releases/{id}Descarta un lanzamiento en draft o rejected (elimina pistas, tiendas y territorios)curl -X POST https://app.dharell.music/api/public/delivery/v1/releases \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label_id": "uuid-del-sello",
"external_id": "REL-2026-0142",
"title": "Mi Álbum",
"version": null,
"release_type": "album",
"artists": [
{ "name": "Artista Uno", "role": "primary", "spotify_id": "3TVXtAsR1Inumwj472S9r4", "apple_artist_id": 271256 },
{ "name": "Artista Dos", "role": "primary", "spotify_url": "https://open.spotify.com/artist/1uNFoZAHBGtllmzznpCI3s" },
{ "name": "Invitado", "role": "featured" }
],
"genre": "Latin",
"subgenre": "Reggaeton",
"language": "es",
"release_date": "2026-09-18",
"original_release_date": "2026-09-18",
"c_line": "2026 Mi Sello",
"p_line": "2026 Mi Sello",
"copyright_year": 2026,
"cover_art_path": "uuid-del-sello/api/<client>/cover.jpg",
"territories": { "mode": "worldwide" },
"stores": ["spotify", "apple_music", "youtube_music"]
}'
# También puedes enviar "primary_artist_id" (uuid) si ya creaste el artista,
# y "upc" / "catalog_number" si los gestionas tú.
# Límite: máximo 4 artistas con role "primary". Los demás deben ir como "featured".
#
# REENVÍOS SIN DUPLICADOS
# Si vuelves a enviar el mismo lanzamiento, la API lo reconoce y lo ACTUALIZA
# en vez de crear otro. Se busca en este orden: external_id → upc → título+versión.
# Respuesta: "deduplicated": true, "action": "updated", "matched_by": "external_id",
# y el HTTP es 200 (en vez de 201). Lo mismo aplica a POST /releases/{id}/tracks
# (se reconoce la pista por ISRC, nº de pista o título).
# Se puede sobrescribir en estados draft, in_review y rejected (actualización directa).
# Si está approved (aprobado pero todavía NO entregado a DSPs) la API lo actualiza
# y lo REVIERTE a in_review con "reverted_to_review": true — cualquier cambio tras
# la aprobación obliga a re-revisión del equipo Dharell antes de entregar.
# Si ya está delivered/live/takedown la API responde 409 con el id existente
# (usa el endpoint /takedown).curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/tracks \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"track_number": 1,
"disc_number": 1,
"title": "Primer Tema",
"audio_path": "uuid-del-sello/api/<client>/01-track.wav",
"explicit": false,
"audio_language": "es",
"lyrics_language": "es",
"contributors": [
{ "display_name": "Jonás Ortiz", "role": "composer", "ipi": "00000000000" },
{ "display_name": "Productor X", "role": "producer" }
],
"splits": [
{ "recipient_name": "Artista", "recipient_email": "artista@correo.com", "share_percent": 70 },
{ "recipient_name": "Productor", "recipient_email": "productor@correo.com", "share_percent": 30 }
]
}'
# Una pista por llamada. Repite la llamada para cada track.
# Si no envías track_number, se numera automáticamente.
# Los splits de cada pista deben sumar 100 (o quedar vacíos: 100% al sello).curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/submit \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Respuesta
{
"success": true,
"data": {
"release": { "id": "...", "status": "in_review", "upc": "0197...", "submitted_at": "..." },
"warnings": [ { "code": "short_lead_time", "message": "..." } ]
}
}
# Si falta metadata o los archivos no cumplen el QC técnico:
# HTTP 422 con "details": { "errors": [...], "warnings": [...] }curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/takedown \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "Fin de contrato con el artista", "effective_date": "2026-10-01" }'5b. Códigos ISRC y UPC automáticos
No necesitas solicitar ni gestionar los códigos ISRC (pistas) y UPC (lanzamiento) por separado. Dharell los asigna y te los devuelve por tres canales, así tu sistema los recibe sin consultas manuales. Si ya envías tus propios códigos, los respetamos tal cual. Si no los envías, los generamos automáticamente con el prefijo QT9AJ (ISRC) y el pool de UPC del sello.
# Al crear el lanzamiento (POST /v1/releases) la respuesta incluye el UPC:
{
"data": {
"id": "cf5acb81-...",
"status": "draft",
"upc": "882100212909",
...
}
}
# Al crear cada pista (POST /v1/releases/{id}/tracks) la respuesta incluye su ISRC:
{
"data": {
"id": "...",
"track_number": 1,
"isrc": "QT9AJ2400001",
...
}
}# Cuando Dharell aprueba/rechaza/entrega, el webhook incluye UPC e ISRC:
{
"event": "release.approved",
"data": {
"release_id": "cf5acb81-...",
"upc": "882100212909",
"tracks": [
{ "isrc": "QT9AJ2400001", "title": "Pista 1" },
{ "isrc": "QT9AJ2400002", "title": "Pista 2" }
]
}
}
# No necesitas una segunda consulta — los códigos llegan solos.curl "https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/status" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Respuesta:
{
"data": {
"status": "approved",
"upc": "882100212909",
"tracks": [
{ "isrc": "QT9AJ2400001", "title": "Pista 1" },
{ "isrc": "QT9AJ2400002", "title": "Pista 2" }
],
"history": [ { "status": "submitted", "at": "..." }, ... ]
}
}Tú envías el release → Dharell asigna ISRC/UPC
↓
Nuestro equipo revisa y aprueba
↓
Webhook automático te llega con los códigos
↓
Códigos visibles en tu sistema sin acción manual
# • Si envías tus propios ISRC/UPC, respetamos los que asignas.
# • Si no los envías, los generamos con prefijo QT9AJ.
# • Los códigos también están visibles en la consola web (/console).5c. Artistas: IDs de Spotify y Apple
Para que cada lanzamiento quede vinculado al perfil correcto en Spotify y Apple Music (y no se dupliquen o se asignen mal por nombres parecidos), puedes enviar los identificadores de plataforma del artista. Los aceptamos en crudo, como URI o como URL pública; los extraemos automáticamente. Si el artista ya existe en tu sello sin esos IDs, los completamos con los que envíes.
# Spotify (spotify_id) — aceptamos cualquiera de estas formas:
"3TVXtAsR1Inumwj472S9r4" # ID crudo
"spotify:artist:3TVXtAsR1Inumwj472S9r4" # URI
"https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4" # URL
"https://open.spotify.com/intl-es/artist/3TVXtAsR1Inumwj472S9r4" # con locale
# Apple Music (apple_artist_id) — número o string:
271256
"271256"
"https://music.apple.com/artist/271256"
"https://music.apple.com/es/artista/exit5fiv/271256"
# Alias también válidos (los reconocemos y los traducimos):
# spotify: spotify_id, spotify_artist_id, spotify_uri, spotify_url
# apple: apple_artist_id, apple_id, apple_music_url, apple_url# 1) Al crear el artista explícitamente:
POST /artists
{ "label_id": "...", "name": "Exit5fiv",
"spotify_id": "3TVXtAsR1Inumwj472S9r4",
"apple_artist_id": 271256 }
# 2) Dentro del array "artists[]" del lanzamiento:
POST /releases (y PATCH /releases/{id})
{
"artists": [
{ "name": "Exit5fiv", "role": "primary",
"spotify_id": "3TVXtAsR1Inumwj472S9r4",
"apple_artist_id": 271256 },
{ "name": "Otro", "role": "featured",
"spotify_url": "https://open.spotify.com/artist/1uNFoZAHBGtllmzznpCI3s" }
]
}
# 3) En los arrays planos (menos explícito, sin IDs de plataforma):
primary_artist_ids: ["uuid"] # por UUID del sello
primary_artist_names: ["Exit5fiv"]Para cada artista del array, buscamos en tu sello en este orden:
1. id (UUID) → si lo envías, se usa tal cual (debe pertenecer a tu sello).
2. spotify_id → buscamos un artista con ese Spotify ID en el sello.
3. apple_artist_id → buscamos por Apple Music ID.
4. name → match case-insensitive por nombre.
5. Si ninguno coincide → creamos el artista NUEVO ya vinculado a
Spotify/Apple (guardamos spotify_id, apple_artist_id y spotify_url).
Backfill: si el perfil existente NO tenía los IDs pero tú los envías,
los completamos automáticamente en el registro del artista.
# ¿Por qué importa? Evita perfiles duplicados y que un lanzamiento
# quede asignado a otro artista por tener un nombre parecido. Envía
# los IDs siempre que los conozcas — sobre todo en colaboraciones.# GET /artists/{id} y GET /releases/{id} devuelven los IDs:
{
"data": {
"id": "uuid-del-artista",
"name": "Exit5fiv",
"spotify_id": "3TVXtAsR1Inumwj472S9r4",
"apple_artist_id": 271256,
"spotify_url": "https://open.spotify.com/artist/3TVXtAsR1Inumwj472S9r4"
}
}
# GET /releases/{id} incluye el array "artists" con cada uno:
"artists": [
{ "artist_id": "uuid", "name": "Exit5fiv", "role": "primary" },
{ "artist_id": "uuid", "name": "Otro", "role": "featured" }
]
# Los IDs de plataforma se usan en las entregas a los DSPs para
# enlazar el perfil correcto de cada tienda.6. Webhooks
¿Qué es el Webhook URL? Es una dirección HTTPS que TÚ configuras en tu servidor para que Dharell te avise automáticamente cada vez que un lanzamiento cambia de estado. Por ejemplo: https://api.tusello.com/dharell/webhooks. La registras en tu panel de consola (/console → API → Webhook URL) o nos la indicas al crear el cliente. Cada vez que un release es aprobado, rechazado, entregado o retirado, Dharell envía un POST a esa URL con los datos del evento, firmado con HMAC-SHA256 usando tu webhook_secret para que puedas verificar que realmente viene de Dharell. Si no tienes un servidor o no necesitas notificaciones automáticas, deja el campo vacío: podrás seguir el estado de tus lanzamientos en el portal /console o por correo. Reintentamos hasta 6 veces con backoff (1, 5, 15, 60, 180 y 720 minutos) hasta recibir un 2xx.
/webhooksHistorial de entregas (status, event, release_id, page, page_size). Incluye last_status_code y last_error/webhooksReintenta entregas: { "delivery_id": "…" } o { "status": "failed" } para reencolar todas/webhooks/pingEnvía un evento de prueba firmado a tu Webhook URL y devuelve código HTTP, latencia y firma. Úsalo antes de producción1. Crea un endpoint en tu servidor que acepte POST, por ejemplo:
https://api.tusello.com/dharell/webhooks
2. Regístralo en tu consola /console → API → Webhook URL,
o indícanoslo al crear tu cuenta de API.
3. Te entregaremos un webhook_secret (guárdalo seguro).
Lo usarás para verificar la firma de cada notificación.
4. Tu endpoint debe responder 2xx (200, 201, 204) para confirmar
recepción. Si no responde, Dharell reintentará automáticamente.X-Dharell-Event · nombre del evento (p. ej. release.approved)
X-Dharell-Delivery · id único de la entrega (para deduplicar)
X-Dharell-Signature · sha256=<hex> — HMAC del cuerpo crudo
User-Agent · Dharell-Delivery-Webhooks/1.0release.submitted → el lanzamiento entró a revisión
release.approved → aprobado por Dharell
release.rejected → rechazado (incluye motivo en review_notes)
release.delivered → enviado a las DSPs
release.live → disponible en tiendas
release.takedown → retirada procesada
# Payload de ejemplo (release.approved) — incluye UPC e ISRC de cada pista:
{
"event": "release.approved",
"data": {
"release_id": "cf5acb81-...",
"upc": "882100212909",
"status": "approved",
"tracks": [
{ "isrc": "QT9AJ2400001", "title": "Pista 1" },
{ "isrc": "QT9AJ2400002", "title": "Pista 2" }
],
"review_notes": null,
"timestamp": "2026-08-08T19:13:00Z"
}
}import crypto from "node:crypto";
const expected =
"sha256=" +
crypto
.createHmac("sha256", process.env.DHARELL_WEBHOOK_SECRET)
.update(rawBody) // el cuerpo crudo, sin volver a serializar
.digest("hex");
const received = req.headers["x-dharell-signature"];
if (
!received ||
received.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
) {
return res.status(401).send("Invalid signature");
}7. Bitácora de la API (logs)
Cada petición que haces a la Delivery API queda registrada con su método, ruta, código HTTP, latencia y —si fue un error— el cuerpo de la respuesta. Puedes consultar tu propio historial por API para depurar problemas sin contactar a soporte. Requiere el scope catalog:read.
/logs?status=error|success&page=1&page_size=25Historial de tus peticiones. Filtra por status=error (≥400) o status=success (<400), por rango de fecha (since/until ISO 8601), por método HTTP y pagina resultados. Incluye response_body en errores y un summary_24h con totalescurl "https://app.dharell.music/api/public/delivery/v1/logs?status=error&page=1&page_size=25" \
-H "Authorization: Bearer $TOKEN"curl "https://app.dharell.music/api/public/delivery/v1/logs?since=2026-08-01T00:00:00Z&until=2026-08-07T23:59:59Z" \
-H "Authorization: Bearer $TOKEN"{
"success": true,
"data": [
{
"id": "a1b2c3…",
"method": "POST",
"path": "/v1/releases",
"status": 422,
"duration_ms": 12,
"error": "validation_error",
"response_body": "{\"success\":false,\"message\":\"Datos de lanzamiento inválidos\",\"error_code\":\"VALIDATION_ERROR\"}",
"created_at": "2026-08-07T22:59:13.000Z"
}
],
"pagination": { "page": 1, "limit": 25, "total": 8, "total_pages": 1 },
"summary_24h": { "total": 11, "errors": 8, "successes": 3 }
}8. Informes (analytics y regalías)
Los mismos datos que ves en la Delivery Console están disponibles por API. Requieren el scope analytics:read (los clientes con releases:read también pueden leerlos).
/analytics?label_id=…&days=7|30|90|365Resumen por plataforma: streams, oyentes, top tracks y tendencias/royalties?month_from=2026-01&month_to=2026-06&dsp=spotifyIngresos, statements y desglose por DSP y lanzamientocurl "https://app.dharell.music/api/public/delivery/v1/analytics?days=30" \
-H "Authorization: Bearer $TOKEN"curl "https://app.dharell.music/api/public/delivery/v1/royalties?month_from=2026-01&month_to=2026-06" \
-H "Authorization: Bearer $TOKEN"9. OpenAPI, Postman y SDKs
Publicamos la especificación OpenAPI 3.1 completa y una colección de Postman lista para importar, así puedes autogenerar tu SDK en cualquier lenguaje.
# OpenAPI 3.1
curl -O https://app.dharell.music/delivery-openapi.json
# Colección de Postman
curl -O https://app.dharell.music/delivery-postman.jsonnpx @openapitools/openapi-generator-cli generate \
-i https://app.dharell.music/delivery-openapi.json \
-g typescript-fetch \
-o ./dharell-sdk9. Errores y límites
Todas las respuestas de error usan el mismo sobre. Los límites de tasa son por cliente y los ves en GET /me (rate_limits); por defecto 120 req/min, 3.000/hora y 20.000/día.
{
"success": false,
"message": "Los archivos no cumplen los requisitos técnicos de las DSPs",
"error_code": "VALIDATION_ERROR",
"details": {
"errors": [{ "code": "file_qc", "message": "La portada debe ser mínimo 3000x3000" }],
"warnings": []
}
}200 OK · petición correcta
201 Created · recurso creado
401 UNAUTHORIZED · token ausente, vencido o revocado
403 FORBIDDEN · scope o sello no autorizado
404 NOT_FOUND · recurso inexistente
409 CONFLICT · el estado no permite la operación
422 VALIDATION_ERROR · JSON inválido, campos inválidos o QC fallido
429 RATE_LIMITED · límite de tasa superado
500 INTERNAL_ERROR · error internoX-RateLimit-Limit · peticiones permitidas en la ventana más ajustada
X-RateLimit-Remaining · cuántas te quedan
X-RateLimit-Reset · segundos de la ventana
X-RateLimit-Window · min | hour | day
Retry-After · solo en 429: segundos a esperar
X-Dharell-Api-Version · versión exacta de la API que respondió
# Regla práctica: si Remaining llega a 0, espera Retry-After segundos.
# Nunca reintentes un 429 de inmediato.10. Idempotencia (evitar duplicados)
Si se te corta la conexión al crear un lanzamiento no sabes si se creó o no. Envía la cabecera Idempotency-Key en toda petición de escritura (POST, PATCH, PUT, DELETE): la primera vez se ejecuta y guardamos la respuesta; cualquier reintento con la misma clave devuelve exactamente la misma respuesta, con el mismo id, sin crear nada nuevo. La clave se conserva 24 horas. Usa un UUID distinto por operación lógica.
KEY=$(uuidgen)
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d '{ "label_id": "<LABEL_ID>", "title": "Mi Sencillo", ... }'
# Reintentar con la MISMA clave devuelve la misma respuesta:
# header Idempotency-Replayed: true — no se creó un segundo release.· Solo aplica a POST, PATCH, PUT y DELETE.
· Longitud de la clave: 8 a 255 caracteres (recomendado: UUID v4).
· Misma clave + cuerpo distinto → 409 CONFLICT.
· Petición aún en curso con esa clave → 409 CONFLICT (reintenta en unos segundos).
· Los errores 5xx NO se cachean: puedes reintentar con la misma clave.
· Vencimiento: 24 horas.11. Sandbox y simulación de estados
Los clientes en modo sandbox trabajan contra el mismo endpoint y las mismas validaciones, pero sus lanzamientos nunca se envían a las DSPs. Además pueden forzar cualquier estado final para probar su integración de punta a punta sin esperar la revisión humana: cada simulación dispara el webhook real correspondiente.
/releases/{id}/simulateSolo sandbox. Body: { "status": "approved|rejected|delivered|live|takedown", "reason": "…" }# 1. Crear y enviar un release normalmente (queda in_review)
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/submit -H "Authorization: Bearer $TOKEN"
# 2. Simular la aprobación de Dharell
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/simulate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"status":"approved"}'
# 3. Simular entrega y publicación
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/simulate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"status":"delivered"}'
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/simulate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"status":"live"}'
# Tu endpoint de webhooks recibirá release.approved, release.delivered y release.live.curl -X POST https://app.dharell.music/api/public/delivery/v1/webhooks/ping -H "Authorization: Bearer $TOKEN"
# → data.status_code, data.duration_ms, data.signature_header y el payload exacto
# que enviamos, para que valides tu verificación de firma.12. Versionado y cambios
/v1 es estable. Solo añadimos campos y endpoints nuevos: tu integración no se rompe por un cambio nuestro. Cualquier cambio incompatible se publica en /v2, avisamos con 90 días de antelación al contacto técnico y mantenemos /v1 al menos 12 meses. Ignora los campos que no conozcas en las respuestas: es la forma segura de integrarse.
/changelogRegistro público de cambios y política de versionado (sin credenciales)/statusEstado del servicio, disponibilidad y latencia de las últimas 24 h y 7 días (sin credenciales)curl https://app.dharell.music/api/public/delivery/v1/changelog
curl https://app.dharell.music/api/public/delivery/v1/status
# Página de estado para humanos:
# https://app.dharell.music/status12b. Registro de cambios (Changelog)
Historial de versiones de la Delivery API. /v1 es estable: solo añadimos campos y endpoints nuevos, así que tu integración no se rompe al actualizar. La versión exacta que respondió tu petición viene en la cabecera X-Dharell-Api-Version. También puedes consultar este registro en vivo con GET /changelog.
NUEVO · Códigos devueltos automáticamente, sin consultas manuales.
ENDPOINTS AFECTADOS
─────────────────
POST /releases
· La respuesta incluye "upc" en data, generado al instante si no lo
envías (prefijo de UPC del sello). Respeta el UPC que tú asignas.
POST /releases/{id}/tracks
· La respuesta incluye "isrc" en data de cada pista, generado al
instante si no lo envías (prefijo QT9AJ).
GET /releases/{id}/status
· Ahora incluye "upc" en data y el array "tracks[]" con "isrc" y
"title" de cada pista, además del historial de estado.
WEBHOOKS AFECTADOS
──────────────────
Todos los eventos de cambio de estado (release.approved, .rejected,
.delivered, .live, .takedown) ahora incluyen en data:
· "upc": código UPC del lanzamiento
· "tracks": [ { "isrc": "…", "title": "…" }, ... ]
No se requieren cambios para seguir recibiendo webhooks: los campos
nuevos son aditivos. Tu verificador de firma HMAC sigue igual.
NOTAS
─────
· Si ya envías tus propios ISRC/UPC, se respetan tal cual.
· Si no los envías, Dharell los genera (ISRC con prefijo QT9AJ).
· Los códigos también son visibles en la consola web /console.NUEVO · 20 plataformas más disponibles en el campo "stores".
TIENDAS AÑADIDAS (extracto)
───────────────────────────
joox JOOX Asia
netease NetEase Cloud Music China
tencent Tencent Music / QQ China
wesing WeSing Asia
alibaba Alibaba Music China
tuned_global Tuned Global Global
line_music LINE Music Japón
anghami Anghami MENA
boomplay Boomplay África
… y más. Lista completa y viva: GET https://app.dharell.music/api/public/delivery/v1/stores
ENDPOINTS AFECTADOS
───────────────────
GET /stores
· Cada tienda devuelve: code, name, region, recommended, new, aliases[]
· Ya NO se expone información interna de enrutamiento de entrega.
Cómo entrega Dharell a cada tienda es un detalle operativo interno:
tú eliges las tiendas destino y nosotros hacemos el transporte.
POST /releases · PATCH /releases/{id}
· El campo "stores" acepta los códigos nuevos y sus alias
(ej. "qq_music" -> "tencent", "line" -> "line_music").
POST /releases/{id}/takedown
· Cubre todas las tiendas del lanzamiento sin importar el canal
interno de entrega. Un solo endpoint para bajar en todas partes.
NOTAS
─────
· Cambios aditivos: no requiere ajustes en integraciones existentes.
· Enviar un código desconocido sigue devolviendo 422 VALIDATION_ERROR
con la lista de códigos válidos.NUEVO · Vincula cada artista a su perfil real de Spotify y Apple Music.
ENDPOINTS AFECTADOS
───────────────────
POST /artists
· Acepta spotify_id y apple_artist_id (también spotify_url,
spotify:artist:... y apple_music_url). Se extrae el ID solo.
POST /releases · PATCH /releases/{id}
· Cada objeto de "artists[]" acepta spotify_id, apple_artist_id,
spotify_url y apple_music_url.
GET /artists/{id} · GET /releases/{id}
· Devuelven spotify_id, apple_artist_id y spotify_url del artista.
CÓMO SE RESUELVE EL PERFIL
──────────────────────────
1. id (UUID del sello) → se usa directamente
2. spotify_id → match por Spotify ID en el sello
3. apple_artist_id → match por Apple Music ID
4. name → match case-insensitive por nombre
5. ninguno → se crea el artista ya vinculado
Backfill: si el perfil existente no tenía los IDs, se completan con
los que envíes. Evita perfiles duplicados y asignaciones erróneas por
nombres parecidos (ver sección "5c. Artistas: IDs de Spotify y Apple").
No rompe integraciones existentes: los campos nuevos son opcionales.v1.3.1 — Reenvío de lanzamientos aprobados (18 jul 2026)
· Reenviar un lanzamiento en estado "approved" lo actualiza y lo
revierte a "in_review" con "reverted_to_review": true.
· Cualquier cambio tras la aprobación obliga a re-revisión de Dharell.
v1.3.0 — Deduplicación de reenvíos (10 jul 2026)
· POST /releases reconoce reenvíos por external_id → upc → título+versión
y actualiza en vez de duplicar (respuesta "deduplicated": true, HTTP 200).
· Aplica a estados draft, in_review y rejected (actualización directa).
v1.2.0 — Multi-artista principal (28 jun 2026)
· Campo "artists": [{ "name", "role": "primary"|"featured" }] en
POST /releases (máximo 4 principales).
· "primary_artist_id" sigue funcionando para compatibilidad.
v1.1.2 — Catálogo de tiendas en vivo (15 jun 2026)
· GET /stores devuelve la lista autorizada de DSPs con alias.
· Normalización de alias en el campo "stores" (ej. "fb" → "instagram_facebook").
v1.1.1 — Bitácora de llamadas (1 jun 2026)
· GET /logs con historial de peticiones, filtros por status y fecha,
y summary_24h con totales.
v1.1.0 — Suite profesional (15 may 2026)
· Idempotency-Key (24 h) en todas las escrituras.
· Cabeceras X-RateLimit-* y Retry-After.
· POST /webhooks/ping y POST /releases/{id}/simulate (sandbox).
· Versionado con X-Dharell-Api-Version y GET /changelog.
v1.0.0 — Lanzamiento inicial (1 abr 2026)
· OAuth2 client_credentials, URLs firmadas, lanzamientos, tracks,
QC, submit, takedowns, webhooks firmados con HMAC-SHA256.13. Checklist de puesta en producción
Repasa esta lista antes de enviar tu primer lanzamiento real. Si todos los puntos están marcados, tu integración está lista.
[ ] Credenciales guardadas como secretos (nunca en el repositorio).
[ ] El token se cachea y se renueva antes de los 3600 s; no pides uno por request.
[ ] Envías Idempotency-Key en todas las escrituras.
[ ] Respetas X-RateLimit-Remaining y esperas Retry-After ante un 429.
[ ] Reintentas 5xx con backoff exponencial (nunca en bucle inmediato).
[ ] Webhook probado con POST /webhooks/ping y firma HMAC verificada.
[ ] Deduplicas por X-Dharell-Delivery y respondes 2xx rápido (procesa en segundo plano).
[ ] Probado el ciclo completo en sandbox: crear → subir → submit → simulate approved/delivered/live.
[ ] QC de archivos validado: WAV 16/24-bit 44.1kHz+ y portada 3000x3000 JPG/PNG.
[ ] Manejas 422 leyendo details.errors[] y los muestras a tu equipo.
[ ] Contacto técnico y de facturación registrados con nosotros.Correo: soporte@dharell.music
Portal: https://app.dharell.music/console
Estado: https://app.dharell.music/status
Docs: https://app.dharell.music/docs/delivery-api
Incluye siempre el request_id o la hora exacta (UTC) y el endpoint
para que podamos rastrear tu petición en la bitácora.