Autenticación y alcances
El emisor de tokens está implementado y probado, pero el servicio aún no está desplegado: todavía no hay URL pública ni sandbox.
La API del CMS v1 usa OAuth 2.0 client_credentials con private_key_jwt (RFC 7523). Tu sistema se autentica con una aserción firmada con su llave privada; FINORA solo conoce tu llave pública. No hay contraseñas que robar ni secretos compartidos que rotar.
La aserción del cliente
| Campo | Regla |
|---|---|
| Algoritmo | ES256 (recomendado) o RS256. Nunca none ni HMAC |
kid (cabecera) | El de una llave de tu JWKS registrado |
iss y sub | Tu client_id, los dos iguales |
aud | La URL exacta del token, por ejemplo https://api-cms.example.com/v1/oauth/token |
exp | Obligatorio; como máximo 5 minutos en el futuro |
iat | Recomendado |
jti | Obligatorio, hasta 128 caracteres y de un solo uso: una aserción reutilizada responde 401 |
La petición es un formulario application/x-www-form-urlencoded:
| Parámetro | Valor |
|---|---|
grant_type | client_credentials |
client_assertion_type | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
client_assertion | La aserción firmada |
scope | Opcional. Alcances separados por espacio; deben ser un subconjunto de los concedidos. Sin scope, recibes todos los concedidos |
El ejemplo completo de firma en Node.js está en el Inicio rápido.
El token de acceso
El gateway emite un JWT ES256 de 5 minutos (expires_in: 300, cabecera typ: at+jwt) que envías como Authorization: Bearer <token>. Puedes verificarlo con las llaves públicas de GET /.well-known/jwks.json. Sus claims:
| Claim | Contenido |
|---|---|
iss | URL del gateway |
sub, client_id | Tu client_id |
aud | finora-cms-api |
scope | Alcances concedidos en este token, separados por espacio |
productos | Códigos de producto a los que tienes acceso |
instalacion | Identificador de la instalación del emisor |
env | Entorno (production o sandbox) |
iat, nbf, exp, jti | Vigencia e identificador del token |
Trátalo como opaco salvo para leer exp: renueva el token un poco antes de que venza. Un token vencido responde 401 TOKEN_VENCIDO; uno ausente o inválido, 401 NO_AUTENTICADO.
Errores del endpoint de token
Esta ruta sigue el formato OAuth 2.0 (RFC 6749 §5.2), no RFC 9457:
| HTTP | error | Causa |
|---|---|---|
| 400 | unsupported_grant_type | grant_type distinto de client_credentials |
| 400 | invalid_request | Falta client_assertion o client_assertion_type es otro |
| 400 | invalid_scope | Pediste un alcance que no tienes concedido |
| 401 | invalid_client | Aserción inválida, vencida, de un cliente suspendido o reutilizada |
| 429 | — | Demasiados fallos desde tu IP (20 fallos la bloquean 15 minutos); trae Retry-After |
| 503 | — | El gateway no puede verificar la aserción (AUTENTICACION_NO_DISPONIBLE); reintenta tras Retry-After |
{ "error": "invalid_client", "error_description": "Aserción reutilizada." }
Rotación de tu llave
Tu JWKS puede tener varias llaves. Para rotar: agrega la nueva al registro, empieza a firmar con su kid y retira la anterior cuando ya no la uses. Si registraste una URL https de JWKS, basta con publicar la nueva llave allí.
Alcances
Cada operación exige un alcance. Sin él, 403 ALCANCE_INSUFICIENTE. Además, cada objeto se valida contra tus productos: un objeto de otro producto responde 404, nunca 403.
| Alcance | Permite | Operaciones |
|---|---|---|
accounts:read | Leer cuentas y productos. | GET /v1/customers/{customerId}/accounts, GET /v1/accounts/{accountId}, GET /v1/products |
balances:read | Leer saldos y la serie del IDI. | GET /v1/accounts/{accountId}/balance, GET /v1/indices/IDI |
cards:block | Bloquear y desbloquear plásticos. | POST /v1/cards/{cardId}/block, POST /v1/cards/{cardId}/unblock |
cards:links | Vincular y desvincular cuentas; solicitar cambio de principal. | POST /v1/cards/{cardId}/accounts, POST /v1/cards/{cardId}/accounts/{linkId}/deactivation, POST /v1/cards/{cardId}/accounts/{linkId}/principal-requests, POST /v1/principal-requests/{solicitudId}/cancellation |
cards:pan-lookup | Buscar un plástico por PAN (fase 2, con mTLS). | POST /v1/cards/lookup (Próximamente) |
cards:read | Leer plásticos y vínculos. | GET /v1/accounts/{accountId}/cards, GET /v1/cards/{cardId}, GET /v1/cards/{cardId}/accounts, GET /v1/principal-requests/{solicitudId} |
cards:report | Reportar pérdida o robo. | POST /v1/cards/{cardId}/reports |
credit-limit:request | Solicitar cambios de línea. | POST /v1/accounts/{accountId}/credit-limit-requests, DELETE /v1/credit-limit-requests/{requestId} |
customers:read | Leer clientes. | GET /v1/customers, GET /v1/customers/{customerId} |
events:read | Leer eventos. | GET /v1/events, GET /v1/subscriptions/{subscriptionId}/events, POST /v1/subscriptions/{subscriptionId}/cursors |
limits:bulk | Solicitar y consultar lotes. | POST /v1/limit-jobs, GET /v1/limit-jobs/{jobId}, GET /v1/limit-jobs/{jobId}/rows, POST /v1/limit-jobs/{jobId}/cancel |
limits:read | Leer topes, línea, historial y solicitudes. | GET /v1/cards/{cardId}/limits, GET /v1/products/{productCode}/limits, GET /v1/accounts/{accountId}/credit-limit, GET /v1/accounts/{accountId}/credit-limit/history, GET /v1/accounts/{accountId}/credit-limit-requests, GET /v1/credit-limit-requests/{requestId} |
limits:write | Fijar topes del plástico. | PUT /v1/cards/{cardId}/limits |
movements:adjustments | Ver los ajustes por indexación y suscribirse a sus eventos. | GET /v1/accounts/{accountId}/indexation-adjustments |
movements:read | Leer movimientos y operaciones. | GET /v1/accounts/{accountId}/movements, GET /v1/operations/{operationId} |
prepaid:adjust | Solicitar ajustes prepago. | POST /v1/accounts/{accountId}/adjustments, GET /v1/adjustments/{adjustmentId} |
prepaid:adjust-approve | Aprobar o rechazar ajustes DEBITO pendientes (nunca junto con prepaid:adjust en el mismo cliente). | GET /v1/adjustments/{adjustmentId}, POST /v1/adjustments/{adjustmentId}/decision |
prepaid:load | Recargar prepago. | POST /v1/accounts/{accountId}/loads |
statements:read | Leer estados de cuenta. | GET /v1/accounts/{accountId}/statements, GET /v1/accounts/{accountId}/statements/{statementId}, GET /v1/accounts/{accountId}/statements/{statementId}/document |
webhooks:write | Administrar suscripciones. | GET /v1/subscriptions, POST /v1/subscriptions, GET /v1/subscriptions/{subscriptionId}, DELETE /v1/subscriptions/{subscriptionId}, POST /v1/subscriptions/{subscriptionId}/secrets:rotate, POST /v1/subscriptions/{subscriptionId}/replay, GET /v1/subscriptions/{subscriptionId}/deliveries |
Reglas especiales:
movements:adjustmentstambién es necesario para pedir movimientos conincluirAjustes=trueo con la naturalezaAJUSTE_INDEXACION, y para suscribirse a los eventos opt-in de indexación. Ver Movimientos y ajustes.prepaid:adjust-approvenunca se concede al mismo cliente queprepaid:adjust: quien pide un ajuste no puede aprobarlo (cuatro ojos).cards:pan-lookupyPOST /v1/cards/lookupllegan en la fase 2 y exigirán además mTLS (Próximamente).
TLS mutuo (fase 2)
En la fase 2, los alcances de dinero, de límites y cards:pan-lookup exigirán además un certificado de cliente (mTLS, RFC 8705). Sin él responderán 403 MTLS_REQUERIDO. Hoy no se exige.