Códigos de error
La API usa los códigos HTTP estándar. El cuerpo de respuesta siempre es JSON con la clave detail.
Códigos de éxito
| Código | Cuándo |
|---|---|
| 200 OK | Petición correcta, devuelve datos. |
| 201 Created | Recurso creado correctamente. |
| 204 No Content | Operación correcta sin cuerpo de respuesta. |
Códigos de error del cliente (4xx)
400 Bad Request
Los parámetros enviados son válidos en formato pero incoherentes con el estado actual del sistema.
{ "detail": "Ya existe un cliente con ese CIF." }
Qué hacer: mostrar el mensaje al usuario y permitir corregir.
401 Unauthorized
Falta autenticación o las credenciales son inválidas.
Causas posibles:
| Causa | Cómo se ve en detail |
|---|---|
| Sin cabecera de auth | "Falta autenticación. Envía Authorization: Bearer ‹jwt› o X-API-Key." |
| JWT expirado | "Token has expired" o "Could not validate credentials" |
| JWT inválido o malformado | "Could not validate credentials" |
| API Key inexistente, pausada o revocada | "API Key inválida o revocada." |
| Empresa suspendida (impago) | "API Key inválida o revocada." |
Qué hacer:
- Si usas JWT: intenta refresh con el
refresh_token. Si falla, vuelve a pedir login. - Si usas API Key: la clave necesita atención del cliente. No reintentes — notifícale.
403 Forbidden
Estás autenticado, pero no tienes permiso para este recurso o acción.
Causas posibles:
| Causa | Cuándo |
|---|---|
| API Key sin scope necesario | "La API Key no tiene permiso 'X:Y'..." |
| Usuario JWT sin rol suficiente | "El usuario no tiene permisos para esta acción." |
| Recurso de otra empresa | "Sin empresa asociada." |
Qué hacer:
- Scope faltante: avisar al cliente con instrucciones claras para que añada el permiso. Ver Scopes.
- Rol insuficiente: el usuario tiene que pedir a su admin que le suba de rol.
- No reintentes — repetir la misma petición dará el mismo error.
404 Not Found
El recurso solicitado no existe o no pertenece a tu empresa.
{ "detail": "Cliente no encontrado." }
Qué hacer:
- Verificar que el token UUID es correcto.
- Comprobar que el recurso no ha sido borrado.
- No reintentes con el mismo identificador.
409 Conflict
La operación entra en conflicto con el estado actual.
{ "detail": "No se puede eliminar: el cliente tiene facturas asociadas." }
Qué hacer: mostrar el mensaje al usuario; la operación no es posible en ese momento.
422 Unprocessable Entity
Los datos enviados no cumplen el schema esperado.
{
"detail": [
{
"loc": ["body", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
},
{
"loc": ["body", "telefono"],
"msg": "ensure this value has at most 20 characters",
"type": "value_error.any_str.max_length"
}
]
}
Qué hacer:
- Mostrar los errores campo a campo.
- Permitir corregir y reenviar.
429 Too Many Requests
Reservado para rate limiting — no está aplicado actualmente, pero tu código debe manejarlo de cara al futuro.
Qué hacer: esperar el tiempo indicado en la cabecera Retry-After (en segundos) antes de reintentar.
Códigos de error del servidor (5xx)
500 Internal Server Error
Error inesperado en el servidor. No es culpa de tu petición.
Qué hacer:
- Reintentar con backoff exponencial (1s, 2s, 4s, 8s…).
- Si persiste tras 3-5 intentos, registrar y notificar al equipo de TPVReady (
soporte@extremanet.com).
502 Bad Gateway / 503 Service Unavailable / 504 Gateway Timeout
La API está temporalmente no disponible (despliegue, mantenimiento, sobrecarga).
Qué hacer: reintentar con backoff. Es probable que se resuelva en minutos.
Patrón de manejo robusto
import time
import requests
class APIError(Exception): pass
class APIKeyInvalida(APIError): pass
class ScopeInsuficiente(APIError): pass
class RecursoNoEncontrado(APIError): pass
def call_api(method, path, max_reintentos=3, **kwargs):
for intento in range(max_reintentos):
try:
resp = requests.request(
method,
f"https://api.tpvready.es/api{path}",
headers={"X-API-Key": API_KEY},
timeout=20,
**kwargs,
)
# Errores del cliente: no reintentar
if resp.status_code == 401:
raise APIKeyInvalida(resp.json().get("detail"))
if resp.status_code == 403:
raise ScopeInsuficiente(resp.json().get("detail"))
if resp.status_code == 404:
raise RecursoNoEncontrado(resp.json().get("detail"))
if 400 <= resp.status_code < 500:
raise APIError(f"{resp.status_code}: {resp.json().get('detail')}")
# Errores del servidor: reintentar con backoff
if resp.status_code >= 500:
if intento < max_reintentos - 1:
time.sleep(2 ** intento)
continue
raise APIError(f"Servidor no disponible: {resp.status_code}")
return resp.json()
except requests.Timeout:
if intento < max_reintentos - 1:
time.sleep(2 ** intento)
continue
raise APIError("Timeout repetido")
Resumen visual
✅ 2xx → procesa la respuesta
🟡 401 → renueva JWT o avisa de API Key inválida
🟡 403 → avisa de falta de scope/rol
🟡 404 → recurso no existe
❌ 4xx restantes → error del cliente; corregir y reenviar
🔄 5xx → backoff exponencial y reintentar