Skip to content

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.

Returned for authentication failures, header validation, and infrastructure errors:

{
"status": 401,
"errorCode": "ERR-1002",
"title": "Invalid authentication token."
}

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."
}
]
}
CodeHTTPWhen it occurs
ERR-1001401Authentication header absent
ERR-1002401Authentication token invalid
ERR-1003403Authenticated but not authorized for the resource
ERR-2001400Tenant header absent
ERR-2002400Tenant header is not a valid UUID
ERR-3001400externalId is empty or missing
ERR-3002400type is not enabled for this tenant
ERR-3003400countryCode is not a valid ISO 3166-1 alpha-2 code
ERR-3004400currencyCode is not a valid ISO 4217 code
ERR-3005400amount is zero or negative (only ACCOUNT_BALANCE accepts zero)
ERR-3006400status is not PENDING, REJECTED, or BOOKED
ERR-3007400A multiCurrencyAmounts[].currencyCode is not a valid ISO 4217 code
ERR-3008400A multiCurrencyAmounts[].amount is zero or negative
ERR-3009400Payout-only field provided in a payin payload
ERR-3010400Payin-only field provided in a payout payload
ERR-3011400payload.channel is not CASH, ONLINE, or CRYPTO
ERR-3017400payload is missing a field its type requires (for example, invoiceNumber on SALE_INVOICE_LINE), or a field has the wrong format
ERR-3047400An amountBreakdown[].amountType is not allowed for the event’s type
ERR-3048400amountBreakdown repeats an amountType + currencyCode pair
ERR-3049400An amountBreakdown[].amount is zero or negative
ERR-3050400An amountBreakdown[].currencyCode is not a valid ISO 4217 code
ERR-4001400externalId already exists for this tenant
ERR-4002404No financial event found for the given UUID and tenant
ERR-5001500Unexpected internal error

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/json

ERR-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.


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: use payerName, payerPersonalIdType, payerPersonalId.
  • INTERNAL_PAYOUT: use beneficiaryName, 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.


These codes come from amountBreakdown. Each failure is listed in errors, and its field carries the index of the offending item:

CodeNamefield
ERR-3047AmountTypeNotAllowedAmountBreakdown[<index>].AmountType
ERR-3048AmountBreakdownDuplicateAmountBreakdown[<index>] (the repeated item)
ERR-3049AmountBreakdownAmountMustBePositiveAmountBreakdown[<index>].Amount
ERR-3050InvalidAmountBreakdownCurrencyCodeAmountBreakdown[<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-3047 AmountTypeNotAllowed — the amountType is not one of the amount types allowed for the event’s type (on PUT, the type of the stored event). For example, TAX belongs to invoices; a settlement uses FEE_TAX for the tax on the PSP’s fee. Types with no amount type enabled reject every item: omit amountBreakdown for them.
  • ERR-3048 AmountBreakdownDuplicate — the same amountType + currencyCode pair appears twice. Send one item per component, with its amounts already added up.
  • ERR-3049 AmountBreakdownAmountMustBePositive — a component’s amount is zero or negative. Send a positive amount and put the direction in isNegative (true for deductions such as FEE or DISCOUNT). Leave out components that are zero.
  • ERR-3050 InvalidAmountBreakdownCurrencyCode — a component’s currencyCode is 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.


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.


Verify:

  1. The UUID in the path is the one returned in the POST response body (uuid field), not the externalId.
  2. The X-Client-Id is the same tenant that created the event — cross-tenant lookups always return 404.

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.

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}`);
}
}

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.

Devuelto en fallos de autenticación, validación de encabezados y errores de infraestructura:

{
"status": 401,
"errorCode": "ERR-1002",
"title": "Invalid authentication token."
}

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."
}
]
}
CódigoHTTPCuándo ocurre
ERR-1001401Encabezado de autenticación ausente
ERR-1002401Token de autenticación inválido
ERR-1003403Autenticado pero sin autorización para el recurso
ERR-2001400Encabezado de tenant ausente
ERR-2002400Encabezado de tenant no es un UUID válido
ERR-3001400externalId está vacío o ausente
ERR-3002400type no está habilitado para este tenant
ERR-3003400countryCode no es un código ISO 3166-1 alpha-2 válido
ERR-3004400currencyCode no es un código ISO 4217 válido
ERR-3005400amount es cero o negativo (solo ACCOUNT_BALANCE acepta cero)
ERR-3006400status no es PENDING, REJECTED ni BOOKED
ERR-3007400Un multiCurrencyAmounts[].currencyCode no es un código ISO 4217 válido
ERR-3008400Un multiCurrencyAmounts[].amount es cero o negativo
ERR-3009400Campo exclusivo de payout enviado en un payload de payin
ERR-3010400Campo exclusivo de payin enviado en un payload de payout
ERR-3011400payload.channel no es CASH, ONLINE ni CRYPTO
ERR-3017400Al payload le falta un campo que su type exige (por ejemplo, invoiceNumber en SALE_INVOICE_LINE), o un campo tiene un formato incorrecto
ERR-3047400Un amountBreakdown[].amountType no está permitido para el type del evento
ERR-3048400amountBreakdown repite un par amountType + currencyCode
ERR-3049400Un amountBreakdown[].amount es cero o negativo
ERR-3050400Un amountBreakdown[].currencyCode no es un código ISO 4217 válido
ERR-4001400externalId ya existe para este tenant
ERR-4002404No se encontró ningún evento financiero con el UUID y tenant indicados
ERR-5001500Error interno inesperado

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/json

ERR-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.


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: usa payerName, payerPersonalIdType, payerPersonalId.
  • INTERNAL_PAYOUT: usa beneficiaryName, 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ódigoNombrefield
ERR-3047AmountTypeNotAllowedAmountBreakdown[<índice>].AmountType
ERR-3048AmountBreakdownDuplicateAmountBreakdown[<índice>] (el componente repetido)
ERR-3049AmountBreakdownAmountMustBePositiveAmountBreakdown[<índice>].Amount
ERR-3050InvalidAmountBreakdownCurrencyCodeAmountBreakdown[<í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-3047 AmountTypeNotAllowed — el amountType no es uno de los tipos de monto permitidos para el type del evento (en un PUT, el tipo del evento almacenado). Por ejemplo, TAX es de las facturas; un settlement usa FEE_TAX para el impuesto sobre la comisión del PSP. Los tipos sin tipos de monto habilitados rechazan cualquier componente: omite amountBreakdown en ellos.
  • ERR-3048 AmountBreakdownDuplicate — el mismo par amountType + currencyCode aparece dos veces. Envía un componente por concepto, con sus montos ya sumados.
  • ERR-3049 AmountBreakdownAmountMustBePositive — el amount de un componente es cero o negativo. Envía un monto positivo y pon el sentido en isNegative (true para deducciones como FEE o DISCOUNT). Omite los componentes que valen cero.
  • ERR-3050 InvalidAmountBreakdownCurrencyCode — el currencyCode de 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.


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:

  1. El UUID en el path es el devuelto en el cuerpo del POST (campo uuid), no el externalId.
  2. El X-Client-Id corresponde al mismo tenant que creó el evento — las búsquedas entre tenants siempre devuelven 404.

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.

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}`);
}
}

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.