Skip to Content
Catálogo de Errores

Catálogo de Errores

Todos los errores siguen el mismo formato de respuesta:

{ "success": false, "error": { "message": "Descripción legible del error", "code": "CODIGO_ERROR" } }

Códigos de error

HTTPCódigoCuándo ocurre
400INVALID_JSONEl body no es JSON válido
400VALIDATION_ERRORCampos faltantes o con formato incorrecto
422MUNICIPIO_NO_CUBIERTOCiudad de destino sin cobertura activa (ver GET /municipios)
400INVALID_URLURL de webhook usa HTTP, es localhost, o IP privada
400LIMIT_EXCEEDEDYa existen 10 webhooks activos para la empresa
401AUTH_REQUIREDHeader Authorization ausente, mal formado, o token inválido
402PAYMENT_REQUIREDSin créditos disponibles ni plan activo para crear guías
404NOT_FOUNDRecurso no existe o pertenece a otra empresa
409INVALID_STATEOperación no permitida en el estado actual (ej: editar guía que no está en SIN_RECOLLECTAR)
403SANDBOX_NOT_ALLOWEDOperación de escritura intentada con token test (usa token live)
409ALREADY_INACTIVEEl webhook ya estaba desactivado
409WEBHOOK_INACTIVEIntento de test en webhook inactivo
429RATE_LIMIT_EXCEEDED100 requests/minuto superados
429DAILY_GUIDE_LIMIT500 guías/día superadas
500INTERNAL_ERRORError interno del servidor

Detalle por código

VALIDATION_ERROR (400)

El mensaje incluye el detalle de cada campo inválido:

{ "success": false, "error": { "message": "[{\"path\":[\"costoProducto\"],\"message\":\"costoProducto es requerido para guías COD.\"}]", "code": "VALIDATION_ERROR" } }

El message es un array JSON serializado con objetos { path, message } — parsea el string para obtener todos los errores de validación.

AUTH_REQUIRED (401)

{ "success": false, "error": { "message": "Token de autenticación requerido.", "code": "AUTH_REQUIRED" } }

Causas comunes:

  • Header Authorization ausente
  • Formato incorrecto (debe ser Bearer <token>, con espacio)
  • Token revocado o inválido

PAYMENT_REQUIRED (402)

{ "success": false, "error": { "message": "Sin créditos disponibles. Adquiere más créditos en el portal.", "code": "PAYMENT_REQUIRED" } }

Solo aplica a POST /guias y POST /guias/batch. Adquiere créditos en el portal de clientes o contacta a TPC para habilitar un plan personalizado.

NOT_FOUND (404)

{ "success": false, "error": { "message": "Guía no encontrada.", "code": "NOT_FOUND" } }

Las guías y webhooks solo son visibles para la empresa que los creó. Un NOT_FOUND puede indicar que el recurso existe pero pertenece a otra empresa (no diferenciamos por seguridad).

INVALID_STATE (409)

{ "success": false, "error": { "message": "No se puede anular una guía con estado \"EN_RUTA\". Solo es posible anular guías en estado SIN_RECOLLECTAR.", "code": "INVALID_STATE" } }

Cuándo ocurre:

  • PATCH /guias/{id} — la guía no está en SIN_RECOLLECTAR
  • DELETE /guias/{id} — la guía no está en SIN_RECOLLECTAR

RATE_LIMIT_EXCEEDED (429)

{ "success": false, "error": { "message": "Rate limit excedido. Intenta en un minuto.", "code": "RATE_LIMIT_EXCEEDED" } }

Espera hasta que X-RateLimit-Reset expire. Ver Rate Limits para estrategias de manejo.

DAILY_GUIDE_LIMIT (429)

{ "success": false, "error": { "message": "Límite diario de guías alcanzado (500/día). Reinicia mañana.", "code": "DAILY_GUIDE_LIMIT" } }

Este límite se reinicia a medianoche hora Guatemala (UTC-6), no en minutos. No reintentes hasta el día siguiente. El límite diario es configurable por cuenta — contacta a TPC si necesitas aumentarlo.


Estrategia de reintentos

HTTP¿Reintentar?Estrategia
400NoCorrige el request antes de reintentar
401NoVerifica y/o rota tu token
402NoAdquiere créditos en el portal
404NoVerifica el numeroGuia o id
409NoRevisa el estado del recurso con un GET
429 RATE_LIMIT_EXCEEDEDEspera hasta X-RateLimit-Reset
429 DAILY_GUIDE_LIMITNo hoyEspera al día siguiente (medianoche hora Guatemala)
500Exponential backoff: 1s → 2s → 4s (máx 3 intentos)
async function requestConReintentos(url, options, maxIntentos = 3) { for (let intento = 0; intento < maxIntentos; intento++) { const res = await fetch(url, options) // Éxito if (res.ok) return res const body = await res.clone().json().catch(() => ({})) // No reintentar en errores del cliente if (res.status < 500 && res.status !== 429) { throw new Error(body.error?.message ?? `HTTP ${res.status}`) } // Rate limit: esperar hasta reset if (res.status === 429 && body.error?.code === 'RATE_LIMIT_EXCEEDED') { const resetAt = parseInt(res.headers.get('X-RateLimit-Reset') ?? '0') const waitMs = Math.max((resetAt * 1000) - Date.now() + 500, 1000) if (intento < maxIntentos - 1) { await new Promise(r => setTimeout(r, waitMs)) continue } } // Error 5xx: exponential backoff if (res.status >= 500 && intento < maxIntentos - 1) { await new Promise(r => setTimeout(r, 1000 * Math.pow(2, intento))) continue } throw new Error(body.error?.message ?? `HTTP ${res.status}`) } }

Headers de diagnóstico

Todas las respuestas incluyen:

X-Request-Id: f47ac10b-58cc-4372-a567-0e02b2c3d479

Incluye el valor de X-Request-Id al contactar a soporte para diagnóstico inmediato.

Last updated on