Primeros pasos
1. Obtener un token
El gateway emite un JWT de 15 minutos con OAuth 2.0 client_credentials. La credencial puede ir en Authorization: Basic o en el cuerpo, en formulario o JSON.
curl -s -X POST https://gateway.banco.example/v1/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentials \
-d scope="clients:read deposits:read"
{
"access_token": "<JWT>",
"token_type": "Bearer",
"expires_in": 900,
"scope": "clients:read deposits:read",
"tenant": "banco-demo",
"environment": "sandbox"
}
scopees opcional: pide un subconjunto de los alcances de tu cliente. Si pides uno que no tienes,400conerror: invalid_scope.- El token queda atado al inquilino y al entorno de tu credencial: una credencial de pruebas solo obtiene tokens de pruebas.
- Tras varios intentos fallidos seguidos, la credencial (o la dirección de origen) queda bloqueada temporalmente:
429. - Si el banco rotó tu secreto y entras con el anterior, la respuesta trae
X-Finora-Secret-Expirescon la fecha en que deja de valer.
Para pruebas puntuales, el gateway acepta también la cabecera apiKey: <clientId>.<secreto>. En producción usa el token: el secreto viaja una vez cada 15 minutos y no en cada petición.
2. Hacer la primera llamada
curl -s "https://gateway.banco.example/v1/deposit-products?paginationDetails=ON" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.finora.v1+json" \
-i
Cabeceras de la petición
| Cabecera | Obligatoria | Uso |
|---|---|---|
Authorization: Bearer <JWT> | sí | Token del paso 1. |
Accept | no | application/json o application/vnd.finora.v1+json. Pedir otra versión (v2…) responde 406 VERSION_NO_SOPORTADA. |
X-Finora-Tenant | no | Inquilino. Si el banco usa subdominios por inquilino, se deduce del host; si la envías, debe coincidir con el de tu credencial. |
X-Finora-Environment | no | sandbox o production. Debe coincidir con el entorno de tu credencial. |
Idempotency-Key | en escrituras | UUID. Ver más abajo. |
X-Request-Id | no | Tu identificador de traza ([A-Za-z0-9._-], de 8 a 64 caracteres); el gateway lo devuelve. Si no lo envías o no cumple el formato, genera uno. |
Cabeceras de la respuesta
| Cabecera | Contenido |
|---|---|
X-Request-Id | Identificador de la petición; cítalo al pedir soporte. |
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset | Límite de tu cliente, lo que queda y los segundos hasta reiniciarse. Al excederlo, 429 LIMITE_EXCEDIDO con Retry-After. |
Items-Total, Items-Offset, Items-Limit | Solo con ?paginationDetails=ON. |
Items-Next-Cursor | En los movimientos de una cuenta, que se paginan por cursor. |
3. Paginar
Las listas devuelven un arreglo JSON y se paginan con offset (desde 0) y limit (50 por defecto, hasta 1000). Con paginationDetails=ON recibes el total en las cabeceras Items-*.
Los movimientos de una cuenta (GET /v1/deposits/{id}/transactions) se paginan por cursor: usa el valor de Items-Next-Cursor en ?cursor=; offset no aplica y limit va de 1 a 100.
4. Escribir con idempotencia
Toda escritura exige Idempotency-Key con un UUID (v4 recomendado). El gateway guarda el resultado 6 horas.
Las respuestas 4xx y 5xx no se guardan: puedes corregir y reintentar con la misma clave.
| Error | HTTP | Cuándo |
|---|---|---|
IDEMPOTENCY_KEY_REQUERIDA | 400 | Falta la cabecera en una escritura. |
IDEMPOTENCY_KEY_INVALIDA | 400 | La clave no es un UUID. |
5. Interpretar errores
Los errores siguen RFC 7807 (application/problem+json) con type del espacio urn:finora:core-gateway: y siempre traen codigo, requestId y timestamp. Por compatibilidad con otros cores, incluyen además errorCode (el estado HTTP), errorReason (categoría) y, cuando se conoce, errorSource (el campo culpable).
{
"type": "urn:finora:core-gateway:no-soportado-por-core",
"title": "Operación no disponible en el core",
"status": 501,
"detail": "El core no expone la modificación de créditos por API.",
"instance": "/v1/loans/215",
"codigo": "CORE_NO_SOPORTA",
"requestId": "4f1c9a2b7e8d4c6a9b0e1f2a3b4c5d6e",
"timestamp": "2026-10-20T14:05:11.204-04:00",
"errorCode": 501,
"errorReason": "METHOD_NOT_IMPLEMENTED",
"operacion": "PATCH /v1/loans/{loanId}",
"alternativa": "…"
}
Decide siempre por codigo.
| HTTP | errorReason | Ejemplos de codigo |
|---|---|---|
| 400 | INVALID_PARAMETERS | IDEMPOTENCY_KEY_REQUERIDA, PAGINACION_POR_CURSOR, DETAILS_LEVEL_INVALIDO |
| 401 | INVALID_CREDENTIALS | GW_CREDENCIAL_INVALIDA |
| 403 | PERMISSION_DENIED | TENANT_CRUZADO, ENTORNO_CRUZADO |
| 406 | INVALID_API_OPERATION | VERSION_NO_SOPORTADA |
| 409 | CONFLICT | IDEMPOTENCIA_EN_CURSO |
| 422 | BUSINESS_RULE_VIOLATION | IDEMPOTENCIA_CUERPO_DISTINTO, reglas de negocio del core |
| 423 | TENANT_SUSPENDED | TENANT_SUSPENDIDO |
| 429 | TOO_MANY_REQUESTS | LIMITE_EXCEDIDO, GW_LOGIN_BLOQUEADO |
| 501 | METHOD_NOT_IMPLEMENTED | CORE_NO_SOPORTA |
6. Nivel de detalle
Algunas consultas aceptan ?detailsLevel=FULL para incluir datos de consultas adicionales al core: el saldo por fases en GET /v1/deposits/{id}, el plan de pagos en GET /v1/loans/{id} o el estado de la ficha en GET /v1/clients/{id}. El valor por defecto es BASIC.