Errores
El formato de los errores, cada estado y código que responde la API, qué errores conviene reintentar y un gestor de errores en Node listo para copiar.
Actualizado el
Cuando una solicitud falla, la API responde con un estado 4xx o 5xx y un cuerpo JSON con un code estable. Decide según code. El texto de error es para personas y puede cambiar.
El formato
{
"error": "This API key doesn't have the earnings:read scope.",
"code": "insufficient_scope",
"scope": "earnings:read"
}
| Campo | Siempre | Significado |
|---|---|---|
error |
Sí | Qué ha fallado, en palabras. Se puede registrar o mostrar a un operador sin problema. |
code |
Sí | Un código estable. Consulta las tablas de abajo. |
field |
Con invalid |
El parámetro o campo del cuerpo que falla. |
scope |
Con insufficient_scope |
El ámbito que necesita la clave. |
Algunos errores añaden sus propios campos. Por ejemplo, 409 upload_incomplete enumera las partes missing y wrong_size.
Errores que puede responder cualquier endpoint
| Estado | code |
Significado | ¿Reintentar? |
|---|---|---|---|
| 400 | invalid |
Un parámetro es incorrecto. field indica cuál. |
No. Corrige la solicitud. |
| 401 | unauthenticated |
Falta la cabecera Authorization: Bearer. |
No |
| 401 | invalid_key |
La clave no existe. | No |
| 401 | key_revoked |
La clave se revocó. Crea una nueva. | No |
| 401 | key_expired |
La clave ha caducado. Crea una nueva. | No |
| 401 | test_keys_retired |
Una clave hm_test_, retirada el 12 de octubre de 2026. Usa una clave hm_live_. |
No |
| 403 | insufficient_scope |
A la clave le falta el ámbito que necesita este endpoint. scope indica cuál. |
No |
| 403 | account_inactive |
Tu cuenta no está activa. | No |
| 403 | api_not_in_plan |
Tu plan no incluye la API. Se incluye en Enterprise y Custom. | No |
| 404 | not_found |
El endpoint no existe, o el recurso no es tuyo. | No |
| 429 | rate_limited |
Has superado tu límite por minuto con esta clave. | Sí, tras los segundos de Retry-After |
| 500 | server_error |
Algo ha fallado por nuestra parte. | Sí, con espera progresiva |
404 not_found cubre tanto «no existe» como «pertenece a otra cuenta». La API nunca te revela que existe un lanzamiento de otra persona.
Errores de endpoints concretos
| Estado | code |
Dónde | Significado |
|---|---|---|---|
| 409 | exists |
POST /members/invite |
Ya hay una cuenta con este correo. |
| 413 | too_large |
POST /ddex/validate |
Supera el tamaño que admite una prueba. Entrégalo con POST /ddex. |
| 413 | too_large |
POST /ddex |
Más de 20 GB (cuerpo en bruto) o de 512 MB (multipart). Usa una subida directa. |
| 429 | busy |
POST /ddex/validate |
Demasiadas pruebas a la vez. Vuelve a intentarlo en breve. |
| 409 | upload_incomplete |
POST /ddex/uploads/{id}/commit |
Faltan partes o su tamaño es incorrecto. Súbelas y vuelve a confirmar. |
| 409 | already_committed |
Subidas directas | Ya se confirmó. status_url apunta a la entrega. |
| 409 | commit_in_progress |
POST /ddex/uploads/{id}/commit |
Hay otra confirmación de esta subida en curso. |
| 400 | invalid_url |
POST /webhooks, PATCH /webhooks/{id} |
No es una URL https pública en el puerto 443. |
| 409 | too_many |
POST /webhooks |
Ya tienes 20 endpoints. |
| 409 | endpoint_disabled |
Prueba y reintento de webhooks | El endpoint está desactivado. Actívalo primero. |
Cada página de la referencia enumera los errores de cada endpoint. Ámbitos y errores recoge los comunes.
Errores de validación
Un 400 invalid indica el primer problema que encontró:
{
"error": "updated_since is an ISO date or time.",
"code": "invalid",
"field": "updated_since"
}
Solo se informa de un campo cada vez. Corrígelo y vuelve a enviar. Si construyes un formulario sobre la API, muestra error junto al campo indicado en field.
Con los archivos DDEX es distinto. Un lote DDEX con problemas no da un 400. La prueba responde 200 con cada comprobación y un veredicto, y una entrega responde 202 e informa de los problemas por lanzamiento. Consulta Entrega DDEX.
Qué reintentar
- Reintenta los
429y5xx, y los errores de red, como los tiempos de espera agotados y las conexiones restablecidas. - No reintentes el resto de
4xx. La misma solicitud recibe la misma respuesta. Corrige antes la solicitud, la clave o sus ámbitos.
Para un 429, espera los segundos que indique Retry-After. Para un 5xx, espera más tras cada intento (1 s, 2 s, 4 s…) y desiste tras unos pocos. Consulta Límites de solicitudes.
Las lecturas se pueden reintentar sin riesgo. Piénsalo antes de reintentar una escritura. Si reintentas un POST /ddex tras perder la respuesta, el lote puede quedar en cola dos veces. Búscalo antes en GET /ddex.
Un gestor de errores en Node
export class HillsError extends Error {
constructor(status, body, headers) {
super(body.error ?? `HTTP ${status}`);
this.status = status;
this.code = body.code;
this.field = body.field;
this.scope = body.scope;
this.retryAfter = Number(headers.get("Retry-After")) || null;
}
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function hills(path, init = {}, tries = 4) {
for (let i = 1; ; i++) {
const res = await fetch("https://api.hillsmusic.com/v1" + path, {
...init,
headers: { ...init.headers, Authorization: `Bearer ${process.env.HILLS_API_KEY}` },
});
const body = await res.json().catch(() => ({}));
if (res.ok) return body;
const err = new HillsError(res.status, body, res.headers);
const retryable = res.status === 429 || res.status >= 500;
if (!retryable || i >= tries) throw err;
await sleep(err.retryAfter ? err.retryAfter * 1000 : 1000 * 2 ** (i - 1));
}
}
try {
await hills("/earnings");
} catch (err) {
if (err.code === "insufficient_scope") console.error(`Add ${err.scope} to this key.`);
else if (err.code === "api_not_in_plan") console.error("The plan doesn't include the API.");
else throw err;
}
Con curl, añade -w '%{http_code}' para ver el estado, o -i para ver las cabeceras:
curl -i "https://api.hillsmusic.com/v1/releases/999999" \
-H "Authorization: Bearer $HILLS_API_KEY"
# HTTP/2 404
# {"error":"…","code":"not_found"}