Todos los errores de la API tienen el mismo formato JSON y un código estable que puedes usar en tu código. El texto de message está en inglés y puede cambiar, y los mensajes de details en errores de validación salen en el idioma por defecto de la plataforma (hoy catalán), no en el del club ni en inglés; programa contra code y contra el estado HTTP, no contra el mensaje.
Formato
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": {
"ends_at": ["L’hora de fi ha de ser posterior a l’hora d’inici."]
}
}
}
code: identificador estable ensnake_case.message: descripción legible en inglés. No la muestres tal cual a tus usuarios.details: siempre presente; es{}cuando no hay nada que añadir. Envalidation_failed, contiene los errores por campo; sus textos están en el idioma por defecto de la plataforma (en el ejemplo, catalán), así que no programes contra ellos: usa el nombre del campo.
Códigos
| Código | HTTP | Cuándo ocurre | Qué hacer |
|---|---|---|---|
unauthenticated |
401 | Falta el token, no existe o no es válido. La respuesta incluye WWW-Authenticate: Bearer. |
Revisa la cabecera Authorization: Bearer …. |
token_expired |
401 | La credencial ha caducado. | Crea una credencial nueva en el panel. |
token_revoked |
401 | La credencial ha sido revocada. | Crea una credencial nueva en el panel. |
feature_disabled |
403 | El club no tiene activada la función integrations. |
Pídela al equipo de Pranarp Club. |
insufficient_ability |
403 | La credencial no tiene el permiso necesario. details.required indica cuál. |
Crea una credencial con ese permiso. |
space_not_allowed |
403 | La credencial no puede usar ese espacio. details.space_id indica cuál. |
Amplía los espacios de la credencial o usa otro. |
not_owner |
403 | Intentas modificar o cancelar una reserva que no creó esta credencial, sin el permiso bookings:write_all. |
Usa la credencial que la creó o concede bookings:write_all. |
booking_not_modifiable |
403 | La reserva proviene de una actividad del club y no se puede modificar por la API. | No reintentes; gestiónala desde el panel. |
not_found |
404 | El recurso no existe, está inactivo/eliminado o es de otro club. | Revisa el identificador. |
slot_unavailable |
409 | El hueco ya no está disponible (reserva, bloqueo o aforo). | Consulta la disponibilidad y elige otro. |
idempotency_conflict |
409 | Misma Idempotency-Key con otro cuerpo, o la petición original sigue en curso. |
Ver idempotencia. |
validation_failed |
422 | Datos inválidos. details lista los errores por campo. |
Corrige los campos indicados. |
player_not_found |
422 | Alguna referencia external_ref de players no está sincronizada. details.players lista las que faltan. |
Sincronízalas antes con PUT /players/{external_ref}. |
rate_limited |
429 | Has superado 120 peticiones por minuto. Incluye Retry-After. |
Espera y reintenta; ver límites de ritmo. |
Otros errores genéricos
No forman parte de los códigos anteriores, pero tienen el mismo formato:
| Código | HTTP | Significado |
|---|---|---|
forbidden |
403 | Fallo de autorización genérico fuera de los casos anteriores. |
method_not_allowed |
405 | El método no existe para esa ruta; la cabecera Allow lista los admitidos. |
http_error |
otros 4xx | Otro error de cliente sin código específico. |
server_error |
500 | Error interno. Reintenta con backoff; si persiste, contacta con nosotros. |
Manejo recomendado
- 4xx (salvo 409 por conflicto temporal y 429): el error es tuyo; reintentar sin cambiar nada dará el mismo resultado.
- 429 y 5xx: reintenta con espera exponencial (y
Retry-Aftercuando exista), siempre con la mismaIdempotency-Keyen las creaciones. - 409
slot_unavailable: no es un fallo técnico, es un conflicto normal de negocio. Vuelve a consultar la disponibilidad.
Ejemplo de 409 al crear una reserva:
curl -s -i -X POST "$PRANARP_URL/api/v1/bookings" \
-H "Authorization: Bearer $PCK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"space_id":7,"starts_at":"2026-10-22T18:00:00+02:00","ends_at":"2026-10-22T19:00:00+02:00","holder":{"name":"Laura Pons"}}'
{
"error": {
"code": "slot_unavailable",
"message": "The requested time slot is not available.",
"details": {}
}
}