Delivery API · v1.3.6

Entrega tu catálogo
a 150+ DSPs por API

Crea lanzamientos, sube audio y portada, asigna créditos y splits, y sigue el estado de entrega — todo como REST. Dharell asigna ISRC y UPC automáticamente y te notifica con webhooks firmados.

OAuth2 + HMAC URLs firmadas Webhooks en vivo Sandbox incluido
~/dharell — quickstart
# 1. Autenticar
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_...","client_secret":"dhs_..."}'

# 2. Crear lanzamiento
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"label_id":"...","title":"Midnight Bloom","artists":[{"name":"Aurora","role":"primary"}],"release_type":"single","stores":["spotify","apple_music"]}'

# 3. Enviar a revisión → Dharell asigna UPC/ISRC y notifica por webhook
curl -X POST https://app.dharell.music/api/public/delivery/v1/releases/$RELEASE_ID/submit -H "Authorization: Bearer $TOKEN"

El ciclo de un lanzamiento

Crear release
POST /releases
Subir audio + portada
URL firmada → PUT
Agregar tracks
créditos + splits
Enviar a revisión
QC técnico automático
Dharell aprueba
asigna UPC + ISRC
Entregado a 150+ DSPs
webhook firmado

Cada cambio de estado dispara un webhook firmado — tu sistema siempre sabe en qué paso está el lanzamiento, sin hacer polling.

Cómo empezar

  1. Solicita acceso a soporte@dharell.music indicando tu sello y volumen mensual.
  2. Recibirás client_id, client_secret y webhook_secret.
  3. Empieza en modo sandbox: los lanzamientos se validan pero no se entregan.
  4. Cuando la integración esté lista, activamos producción.
URL base
https://app.dharell.music/api/public/delivery/v1

Todo 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.

Paso 1 · Obtener el token (válido 1 hora)
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_..."
Paso 2 · Verificar cuenta, sellos y límites
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 permisos
Paso 3 · Crear (o reutilizar) el artista
curl -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" } }
Paso 4 · Crear el lanzamiento (borrador)
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
Paso 5 · Subir portada y audio (URLs firmadas)
# 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.
Paso 6 · Crear las pistas con créditos y splits
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>
Paso 7 · Control de calidad antes de enviar
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.
Paso 8 · Enviar a revisión de Dharell
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)
Paso 9 · Seguir el estado (webhook o polling)
# 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
Paso 10 · Informes y bajadas
# 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"}'
Manejo de errores recomendado (pseudocódigo)
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
}
Buenas prácticas
· 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.

GET/Índice de la API con la lista de endpoints
GET/healthEstado del servicio (no requiere token)
Respuesta correcta (objeto)
{
  "success": true,
  "data": { "...": "..." },
  "message": "OK"
}
Respuesta correcta (lista)
{
  "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.

POST/oauth/tokenEmite un access token (grant_type=client_credentials)
Solicitar token
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_..."
  }'
Respuesta
{
  "success": true,
  "data": {
    "access_token": "dht_XXXXXXXXXX_...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "catalog:read releases:read releases:write"
  },
  "message": "OK"
}
Usar el token
curl https://app.dharell.music/api/public/delivery/v1/me \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Scopes disponibles
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ón

3. 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.

GET/mePerfil del cliente, scopes, límites y sellos autorizados
GET/labelsSellos que puedes usar como label_id
catalog:read
GET/genresGéneros y subgéneros aceptados
catalog:read
GET/storesDSPs habilitadas (Spotify, Apple Music, etc.)
catalog:read
GET/artistsArtistas del sello (q, page, page_size, label_id)
catalog:read
POST/artistsCrea un artista (idempotente por nombre + sello)
releases:write
Códigos de tienda válidos (campo stores)
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/stores
Crear artista
curl -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).

POST/uploadsGenera una URL firmada (kind: audio | cover)
releases:write
GET/uploads?path=…&kind=audio|coverVerifica que el archivo llegó al almacenamiento (exists, size_bytes) antes de enviar a revisión
releases:write
Pedir URL firmada
curl -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"
  }
}
Subir el archivo
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ú.

GET/releasesLista de lanzamientos del cliente
releases:read
POST/releasesCrea un lanzamiento en draft
releases:write
GET/releases/{id}Detalle completo con tracks y tiendas
releases:read
PATCH/releases/{id}Actualiza (sólo en draft o rejected)
releases:write
GET/releases/{id}/tracksLista las pistas del lanzamiento
releases:read
POST/releases/{id}/tracksAgrega una pista (un objeto por llamada)
releases:write
PATCH/releases/{id}/tracksActualiza una pista: adjunta el audio subido (audio_path), metadata, contribuidores y splits. Requiere track_id en el body
releases:write
DELETE/releases/{id}/tracks?track_id=…Elimina una pista con sus contribuidores y splits
releases:write
POST/releases/{id}/submitValida, corre el QC técnico y envía a revisión de Dharell
releases:write
GET/releases/{id}/qcFicha de control de calidad técnico (misma que ve el equipo)
releases:read
POST/releases/{id}/qcRe-ejecuta el QC de archivos sin enviar a revisión
releases:write
GET/releases/{id}/statusHistorial de estado y entregas por DSP
releases:read
POST/releases/{id}/takedownSolicita retirada del catálogo
releases:write
GET/releases/{id}/takedownEstado e historial de solicitudes de bajada
releases:read
DELETE/releases/{id}/takedownCancela una solicitud de bajada pendiente
releases:write
DELETE/releases/{id}Descarta un lanzamiento en draft o rejected (elimina pistas, tiendas y territorios)
releases:write
Crear lanzamiento
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).
Agregar una pista
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).
Enviar a revisión
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": [...] }
Solicitar bajada (takedown)
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.

1. Respuesta inmediata al crear
# 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",
    ...
  }
}
2. Webhook automático al cambiar de estado
# 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.
3. Consulta on-demand (cuando quieras)
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": "..." }, ... ]
  }
}
Resumen del flujo
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.

Formas aceptadas
# 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
Dónde los puedes enviar
# 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"]
Cómo ubicamos el perfil (orden de prioridad)
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.
Qué devuelve la API
# 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.

GET/webhooksHistorial de entregas (status, event, release_id, page, page_size). Incluye last_status_code y last_error
releases:read
POST/webhooksReintenta entregas: { "delivery_id": "…" } o { "status": "failed" } para reencolar todas
releases:write
POST/webhooks/pingEnvía un evento de prueba firmado a tu Webhook URL y devuelve código HTTP, latencia y firma. Úsalo antes de producción
releases:write
¿Cómo lo configuro?
1. 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.
Headers de cada entrega
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.0
Eventos y payload
release.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"
  }
}
Verificar la firma (Node)
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.

GET/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 totales
catalog:read
Ver mis últimos errores
curl "https://app.dharell.music/api/public/delivery/v1/logs?status=error&page=1&page_size=25" \
  -H "Authorization: Bearer $TOKEN"
Filtrar por rango de fecha
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"
Respuesta de ejemplo (error)
{
  "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).

GET/analytics?label_id=…&days=7|30|90|365Resumen por plataforma: streams, oyentes, top tracks y tendencias
analytics:read
GET/royalties?month_from=2026-01&month_to=2026-06&dsp=spotifyIngresos, statements y desglose por DSP y lanzamiento
analytics:read
Analytics de los últimos 30 días
curl "https://app.dharell.music/api/public/delivery/v1/analytics?days=30" \
  -H "Authorization: Bearer $TOKEN"
Regalías de un rango de meses
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.

Descargar especificación y colección
# OpenAPI 3.1
curl -O https://app.dharell.music/delivery-openapi.json

# Colección de Postman
curl -O https://app.dharell.music/delivery-postman.json
Generar un SDK (ejemplo TypeScript)
npx @openapitools/openapi-generator-cli generate \
  -i https://app.dharell.music/delivery-openapi.json \
  -g typescript-fetch \
  -o ./dharell-sdk

9. 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.

Formato de error
{
  "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": []
  }
}
Códigos HTTP
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 interno
Cabeceras de límite en cada respuesta
X-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.

Crear un lanzamiento de forma segura ante reintentos
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.
Reglas
· 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.

POST/releases/{id}/simulateSolo sandbox. Body: { "status": "approved|rejected|delivered|live|takedown", "reason": "…" }
releases:write
Recorrido completo de prueba
# 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.
Probar tu endpoint de webhooks antes de nada
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.

GET/changelogRegistro público de cambios y política de versionado (sin credenciales)
GET/statusEstado del servicio, disponibilidad y latencia de las últimas 24 h y 7 días (sin credenciales)
Consultar cambios y estado
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/status

12b. 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.

v1.3.2 — Códigos ISRC y UPC automáticos (8 ago 2026)
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.
v1.3.6 — Catálogo de tiendas ampliado (13 ago 2026)
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.
v1.3.5 — IDs de artista de Spotify y Apple (10 ago 2026)
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.
Versiones anteriores
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.

Antes de producción
[ ] 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.
Soporte
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.