Error Handling
The TreasuryHub API returns structured error objects on every non-2xx response. Always react to the errorCode field — the title and detail strings are human-readable and may change between releases.
Error response shapes
Section titled “Error response shapes”Single error
Section titled “Single error”Returned for authentication failures, header validation, and infrastructure errors:
{ "status": 401, "errorCode": "ERR-1002", "title": "Invalid authentication token."}Validation failure
Section titled “Validation failure”Returned when the request body fails validation. The top-level errorCode is the code of the first failure; the errors array lists every failure in the request:
{ "status": 400, "errorCode": "ERR-3001", "title": "One or more validation errors occurred.", "errors": [ { "field": "ExternalId", "errorCode": "ERR-3001", "message": "ExternalId is required." }, { "field": "Amount", "errorCode": "ERR-3005", "message": "Amount must be greater than 0." } ]}Error code registry
Section titled “Error code registry”| Code | HTTP | When it occurs |
|---|---|---|
ERR-1001 | 401 | Authentication header absent |
ERR-1002 | 401 | Authentication token invalid |
ERR-1003 | 403 | Authenticated but not authorized for the resource |
ERR-2001 | 400 | Tenant header absent |
ERR-2002 | 400 | Tenant header is not a valid UUID |
ERR-3001 | 400 | externalId is empty or missing |
ERR-3002 | 400 | type is not INTERNAL_PAYIN or INTERNAL_PAYOUT |
ERR-3003 | 400 | countryCode is not a valid ISO 3166-1 alpha-2 code |
ERR-3004 | 400 | currencyCode is not a valid ISO 4217 code |
ERR-3005 | 400 | amount is zero or negative |
ERR-3006 | 400 | status is not PENDING, REJECTED, or BOOKED |
ERR-3007 | 400 | A multiCurrencyAmounts[].currencyCode is not a valid ISO 4217 code |
ERR-3008 | 400 | A multiCurrencyAmounts[].amount is zero or negative |
ERR-3009 | 400 | Payout-only field provided in a payin payload |
ERR-3010 | 400 | Payin-only field provided in a payout payload |
ERR-3011 | 400 | payload.channel is not CASH, ONLINE, or CRYPTO |
ERR-4001 | 400 | externalId already exists for this tenant |
ERR-4002 | 404 | No financial event found for the given UUID and tenant |
ERR-5001 | 500 | Unexpected internal error |
Troubleshooting by error code
Section titled “Troubleshooting by error code”ERR-1001 / ERR-1002 — Authentication errors
Section titled “ERR-1001 / ERR-1002 — Authentication errors”Check that every request includes all three required headers:
X-Client-Id: {{tenant_id}}X-Access-Token: {{access_token}}Content-Type: application/jsonERR-1001 means a required authentication header is absent. ERR-1002 means the token value is present but invalid — verify the token matches the environment (th_live_ for Production, th_test_ for Sandbox). Tokens from one environment do not work in the other.
ERR-3002 — Unsupported event type
Section titled “ERR-3002 — Unsupported event type”type must be exactly INTERNAL_PAYIN or INTERNAL_PAYOUT — uppercase, no other variants accepted.
ERR-3009 / ERR-3010 — Payload field mismatch
Section titled “ERR-3009 / ERR-3010 — Payload field mismatch”The payload object has distinct allowed fields per event type:
INTERNAL_PAYIN: usepayerName,payerPersonalIdType,payerPersonalId.INTERNAL_PAYOUT: usebeneficiaryName,beneficiaryPersonalIdType,beneficiaryPersonalId.
Sending a field that belongs to the other type causes a 400. Unknown fields are also rejected — the payload schema does not allow additionalProperties.
ERR-4001 — Duplicate externalId
Section titled “ERR-4001 — Duplicate externalId”externalId must be unique per tenant. The API has no idempotency header — if a POST already returned 201, do not retry it with the same externalId.
If the intent is to update an existing event, use PUT /api/h2h/ingestion/financial-events/{uuid} with the UUID returned by the original POST.
ERR-4002 — Financial event not found
Section titled “ERR-4002 — Financial event not found”Verify:
- The UUID in the path is the one returned in the
POSTresponse body (uuidfield), not theexternalId. - The
X-Client-Idis the same tenant that created the event — cross-tenant lookups always return 404.
ERR-5001 — Unexpected internal error
Section titled “ERR-5001 — Unexpected internal error”Contact TreasuryHub support with the full request and the timestamp. Do not retry automatically — submit the event again only after confirming the original was not persisted.
Handling errors in code
Section titled “Handling errors in code”async function callApi(url, options) { const response = await fetch(url, options);
if (response.ok) return response.json();
const error = await response.json();
// Always branch on errorCode, not on HTTP status or title switch (error.errorCode) { case 'ERR-4001': throw new Error(`Duplicate externalId — event already exists.`); case 'ERR-1001': case 'ERR-1002': throw new Error(`Authentication failed — check X-Client-Id and X-Access-Token.`); default: if (error.errors?.length) { // Validation failure — log each field error for (const e of error.errors) { console.error(`[${e.errorCode}] ${e.field}: ${e.message}`); } } throw new Error(`API error ${error.errorCode}: ${error.title}`); }}Retries and idempotency
Section titled “Retries and idempotency”The Financial Events API uses externalId for deduplication — there is no Idempotency-Key header. A request that returns 201 must not be retried with the same externalId. A request that returns 5xx or times out may be retried with the same externalId only if you have confirmed the event was not created (use GET to check by UUID if one was returned).
For 429 responses, retry with a fixed or exponential delay. Rate limit response headers are not yet returned.
La API de TreasuryHub devuelve objetos de error estructurados en toda respuesta no-2xx. Siempre reacciona al campo errorCode — los strings title y detail son legibles por humanos y pueden cambiar entre versiones.
Formatos de respuesta de error
Section titled “Formatos de respuesta de error”Error simple
Section titled “Error simple”Devuelto en fallos de autenticación, validación de encabezados y errores de infraestructura:
{ "status": 401, "errorCode": "ERR-1002", "title": "Invalid authentication token."}Fallo de validación
Section titled “Fallo de validación”Devuelto cuando el cuerpo de la solicitud no pasa la validación. El errorCode de nivel raíz corresponde al primer fallo; el array errors lista todos los fallos de la solicitud:
{ "status": 400, "errorCode": "ERR-3001", "title": "One or more validation errors occurred.", "errors": [ { "field": "ExternalId", "errorCode": "ERR-3001", "message": "ExternalId is required." }, { "field": "Amount", "errorCode": "ERR-3005", "message": "Amount must be greater than 0." } ]}Registro de códigos de error
Section titled “Registro de códigos de error”| Código | HTTP | Cuándo ocurre |
|---|---|---|
ERR-1001 | 401 | Encabezado de autenticación ausente |
ERR-1002 | 401 | Token de autenticación inválido |
ERR-1003 | 403 | Autenticado pero sin autorización para el recurso |
ERR-2001 | 400 | Encabezado de tenant ausente |
ERR-2002 | 400 | Encabezado de tenant no es un UUID válido |
ERR-3001 | 400 | externalId está vacío o ausente |
ERR-3002 | 400 | type no es INTERNAL_PAYIN ni INTERNAL_PAYOUT |
ERR-3003 | 400 | countryCode no es un código ISO 3166-1 alpha-2 válido |
ERR-3004 | 400 | currencyCode no es un código ISO 4217 válido |
ERR-3005 | 400 | amount es cero o negativo |
ERR-3006 | 400 | status no es PENDING, REJECTED ni BOOKED |
ERR-3007 | 400 | Un multiCurrencyAmounts[].currencyCode no es un código ISO 4217 válido |
ERR-3008 | 400 | Un multiCurrencyAmounts[].amount es cero o negativo |
ERR-3009 | 400 | Campo exclusivo de payout enviado en un payload de payin |
ERR-3010 | 400 | Campo exclusivo de payin enviado en un payload de payout |
ERR-3011 | 400 | payload.channel no es CASH, ONLINE ni CRYPTO |
ERR-4001 | 400 | externalId ya existe para este tenant |
ERR-4002 | 404 | No se encontró ningún evento financiero con el UUID y tenant indicados |
ERR-5001 | 500 | Error interno inesperado |
Resolución por código de error
Section titled “Resolución por código de error”ERR-1001 / ERR-1002 — Errores de autenticación
Section titled “ERR-1001 / ERR-1002 — Errores de autenticación”Verifica que toda solicitud incluya los tres encabezados requeridos:
X-Client-Id: {{tenant_id}}X-Access-Token: {{access_token}}Content-Type: application/jsonERR-1001 indica que falta un encabezado de autenticación requerido. ERR-1002 indica que el valor del token está presente pero es inválido — verifica que el token corresponde al entorno (th_live_ para Producción, th_test_ para Sandbox). Los tokens de un entorno no funcionan en el otro.
ERR-3002 — Tipo de evento no soportado
Section titled “ERR-3002 — Tipo de evento no soportado”type debe ser exactamente INTERNAL_PAYIN o INTERNAL_PAYOUT — en mayúsculas, no se aceptan otras variantes.
ERR-3009 / ERR-3010 — Campos de payload incorrectos
Section titled “ERR-3009 / ERR-3010 — Campos de payload incorrectos”El objeto payload tiene campos permitidos distintos según el tipo de evento:
INTERNAL_PAYIN: usapayerName,payerPersonalIdType,payerPersonalId.INTERNAL_PAYOUT: usabeneficiaryName,beneficiaryPersonalIdType,beneficiaryPersonalId.
Enviar un campo del otro tipo causa un 400. Los campos desconocidos también son rechazados — el esquema del payload no permite additionalProperties.
ERR-4001 — externalId duplicado
Section titled “ERR-4001 — externalId duplicado”externalId debe ser único por tenant. La API no tiene encabezado de idempotencia — si un POST ya devolvió 201, no lo reintentes con el mismo externalId.
Si la intención es actualizar un evento existente, usa PUT /api/h2h/ingestion/financial-events/{uuid} con el UUID devuelto por el POST original.
ERR-4002 — Evento financiero no encontrado
Section titled “ERR-4002 — Evento financiero no encontrado”Verifica:
- El UUID en el path es el devuelto en el cuerpo del
POST(campouuid), no elexternalId. - El
X-Client-Idcorresponde al mismo tenant que creó el evento — las búsquedas entre tenants siempre devuelven 404.
ERR-5001 — Error interno inesperado
Section titled “ERR-5001 — Error interno inesperado”Contacta al soporte de TreasuryHub con la solicitud completa y el timestamp. No reintentes automáticamente — vuelve a enviar el evento solo después de confirmar que el original no fue persistido.
Manejo de errores en código
Section titled “Manejo de errores en código”async function llamarApi(url, opciones) { const respuesta = await fetch(url, opciones);
if (respuesta.ok) return respuesta.json();
const error = await respuesta.json();
// Siempre ramifica por errorCode, no por estado HTTP ni por title switch (error.errorCode) { case 'ERR-4001': throw new Error(`externalId duplicado — el evento ya existe.`); case 'ERR-1001': case 'ERR-1002': throw new Error(`Autenticación fallida — verifica X-Client-Id y X-Access-Token.`); default: if (error.errors?.length) { for (const e of error.errors) { console.error(`[${e.errorCode}] ${e.field}: ${e.message}`); } } throw new Error(`Error de API ${error.errorCode}: ${error.title}`); }}Reintentos e idempotencia
Section titled “Reintentos e idempotencia”La API de Financial Events usa externalId para deduplicación — no existe encabezado Idempotency-Key. Una solicitud que devuelve 201 no debe reintentarse con el mismo externalId. Una solicitud que devuelve 5xx o agota el tiempo de espera puede reintentarse con el mismo externalId solo si confirmaste que el evento no fue creado (usa GET para verificar por UUID si se devolvió alguno).
Para respuestas 429, reintenta con un retardo fijo o exponencial. Los encabezados de respuesta de límite de tasa aún no están disponibles.