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 enabled for this tenant |
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 (only ACCOUNT_BALANCE accepts zero) |
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-3017 | 400 | payload is missing a field its type requires (for example, invoiceNumber on SALE_INVOICE_LINE), or a field has the wrong format |
ERR-3047 | 400 | An amountBreakdown[].amountType is not allowed for the event’s type |
ERR-3048 | 400 | amountBreakdown repeats an amountType + currencyCode pair |
ERR-3049 | 400 | An amountBreakdown[].amount is zero or negative |
ERR-3050 | 400 | An amountBreakdown[].currencyCode is not a valid ISO 4217 code |
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 one of the financial types enabled for your tenant, written exactly as in the catalog (uppercase, for example INTERNAL_PAYIN, PSP_SETTLEMENT, SALE_INVOICE_LINE). The error message lists the types enabled for the tenant. To enable another type, contact TreasuryHub support.
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-3047 to ERR-3050 — Amount breakdown
Section titled “ERR-3047 to ERR-3050 — Amount breakdown”These codes come from amountBreakdown. Each failure is listed in errors, and its field carries the index of the offending item:
| Code | Name | field |
|---|---|---|
ERR-3047 | AmountTypeNotAllowed | AmountBreakdown[<index>].AmountType |
ERR-3048 | AmountBreakdownDuplicate | AmountBreakdown[<index>] (the repeated item) |
ERR-3049 | AmountBreakdownAmountMustBePositive | AmountBreakdown[<index>].Amount |
ERR-3050 | InvalidAmountBreakdownCurrencyCode | AmountBreakdown[<index>].CurrencyCode |
{ "status": 400, "errorCode": "ERR-3047", "title": "One or more validation errors occurred.", "errors": [ { "field": "AmountBreakdown[1].AmountType", "errorCode": "ERR-3047", "message": "Amount type 'TAX' is not allowed for PSP_SETTLEMENT." } ]}On PUT, ERR-3047 is a single error. It can only be checked against the type of the stored event, so it comes without an errors array (no field). Read the top-level errorCode. ERR-3048 to ERR-3050 still come inside errors, as on POST.
ERR-3047AmountTypeNotAllowed — theamountTypeis not one of the amount types allowed for the event’stype(onPUT, the type of the stored event). For example,TAXbelongs to invoices; a settlement usesFEE_TAXfor the tax on the PSP’s fee. Types with no amount type enabled reject every item: omitamountBreakdownfor them.ERR-3048AmountBreakdownDuplicate — the sameamountType+currencyCodepair appears twice. Send one item per component, with its amounts already added up.ERR-3049AmountBreakdownAmountMustBePositive — a component’samountis zero or negative. Send a positive amount and put the direction inisNegative(truefor deductions such asFEEorDISCOUNT). Leave out components that are zero.ERR-3050InvalidAmountBreakdownCurrencyCode — a component’scurrencyCodeis not a valid ISO 4217 code (for example,MXN).
The event’s own amount follows the same rule as a component: amount: 0 returns ERR-3005. See Amounts for the allowed amount types and the sign convention.
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 está habilitado para este tenant |
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 (solo ACCOUNT_BALANCE acepta cero) |
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-3017 | 400 | Al payload le falta un campo que su type exige (por ejemplo, invoiceNumber en SALE_INVOICE_LINE), o un campo tiene un formato incorrecto |
ERR-3047 | 400 | Un amountBreakdown[].amountType no está permitido para el type del evento |
ERR-3048 | 400 | amountBreakdown repite un par amountType + currencyCode |
ERR-3049 | 400 | Un amountBreakdown[].amount es cero o negativo |
ERR-3050 | 400 | Un amountBreakdown[].currencyCode no es un código ISO 4217 válido |
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 uno de los tipos financieros habilitados para tu tenant, escrito exactamente como en el catálogo (en mayúsculas, por ejemplo INTERNAL_PAYIN, PSP_SETTLEMENT, SALE_INVOICE_LINE). El mensaje de error lista los tipos habilitados para el tenant. Para habilitar otro tipo, contacta al soporte de TreasuryHub.
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-3047 a ERR-3050 — Desglose de montos
Section titled “ERR-3047 a ERR-3050 — Desglose de montos”Estos códigos provienen de amountBreakdown. Cada fallo aparece en errors, y su field lleva el índice del componente con el problema:
| Código | Nombre | field |
|---|---|---|
ERR-3047 | AmountTypeNotAllowed | AmountBreakdown[<índice>].AmountType |
ERR-3048 | AmountBreakdownDuplicate | AmountBreakdown[<índice>] (el componente repetido) |
ERR-3049 | AmountBreakdownAmountMustBePositive | AmountBreakdown[<índice>].Amount |
ERR-3050 | InvalidAmountBreakdownCurrencyCode | AmountBreakdown[<índice>].CurrencyCode |
{ "status": 400, "errorCode": "ERR-3047", "title": "One or more validation errors occurred.", "errors": [ { "field": "AmountBreakdown[1].AmountType", "errorCode": "ERR-3047", "message": "Amount type 'TAX' is not allowed for PSP_SETTLEMENT." } ]}En un PUT, ERR-3047 es un error simple. Solo puede verificarse contra el tipo del evento almacenado, así que llega sin array errors (sin field). Lee el errorCode de nivel raíz. ERR-3048 a ERR-3050 siguen llegando dentro de errors, como en el POST.
ERR-3047AmountTypeNotAllowed — elamountTypeno es uno de los tipos de monto permitidos para eltypedel evento (en unPUT, el tipo del evento almacenado). Por ejemplo,TAXes de las facturas; un settlement usaFEE_TAXpara el impuesto sobre la comisión del PSP. Los tipos sin tipos de monto habilitados rechazan cualquier componente: omiteamountBreakdownen ellos.ERR-3048AmountBreakdownDuplicate — el mismo paramountType+currencyCodeaparece dos veces. Envía un componente por concepto, con sus montos ya sumados.ERR-3049AmountBreakdownAmountMustBePositive — elamountde un componente es cero o negativo. Envía un monto positivo y pon el sentido enisNegative(truepara deducciones comoFEEoDISCOUNT). Omite los componentes que valen cero.ERR-3050InvalidAmountBreakdownCurrencyCode — elcurrencyCodede un componente no es un código ISO 4217 válido (por ejemplo,MXN).
El amount del propio evento sigue la misma regla que un componente: amount: 0 devuelve ERR-3005. Consulta Montos para los tipos de monto permitidos y la convención de signo.
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.