# Especificacion de las APIs del sandbox del Sistema de Finanzas Abiertas. # # Describe lo que el sandbox EXPONE HOY, ruta por ruta, no lo que la norma # describe: si una ruta no esta implementada, no aparece aqui. Un documento que # promete endpoints inexistentes es peor que no tener documento, porque quien lo # lea va a escribir un cliente contra algo que no responde. # # Tres servidores distintos porque son tres roles distintos, y dos de ellos # exigen certificado de cliente. Eso se declara con `mutualTLS`, que es # justamente para lo que OpenAPI 3.1 lo incorporo. openapi: 3.1.0 info: title: Sandbox del Sistema de Finanzas Abiertas de Chile version: 1.0.0 summary: Directorio de Participantes, IPI y servidor de autorización de un sandbox público no oficial. description: | Sandbox **público y no oficial** del Sistema de Finanzas Abiertas chileno. Implementa el perfil del Anexo Técnico: PAR obligatorio, objeto de petición firmado y cifrado, mTLS con token ligado al certificado, y consentimiento expresado como `authorization_details` (RFC 9396). **No es un participante del sistema real.** La PKI es propia y autofirmada, el Directorio es de este sandbox y los datos son sintéticos. Sirve para probar una implementación, no para operar. Las rutas del Directorio siguen los nombres de la colección de Postman oficial de la Entidad Fiscalizadora Superior, para que un cliente escrito contra el sandbox no tenga que cambiar de forma al apuntar al real. license: name: MIT identifier: MIT contact: name: Efrain Garay url: https://efraingaray.com/lab/sfa/ servers: - url: https://directory.efraingaray.com description: Directorio de Participantes. Las rutas bajo /public no exigen certificado. - url: https://api.efraingaray.com description: APIs de recursos del IPI (Banco Sintetico SpA). Exige certificado de cliente. - url: https://mtls.efraingaray.com description: Servidor de autorización por el alias mTLS (PAR, token, registro dinámico). tags: - name: Directorio description: Quién participa, con qué estado y con qué claves. - name: Declaraciones de software description: Emisión de la SSA y registro de certificados, que es lo que habilita el DCR. - name: Consentimientos description: Ciclo de vida del consentimiento en el IPI. - name: Cuentas description: Datos del cliente final, solo con un token que traiga el consentimiento. security: - mtls: [] paths: /public/v1/participants: get: tags: [Directorio] operationId: listarParticipantes summary: Lista de participantes activos description: | Punto de partida de todo el flujo. Devuelve cada organización con sus servidores de autorización y las APIs que expone, para que ningún participante tenga que cablear a otro. security: [] responses: '200': description: Participantes registrados. content: application/json: schema: type: array items: { $ref: '#/components/schemas/Organizacion' } '503': { description: 'El Directorio no está disponible.' } /public/v1/participants/estado: get: tags: [Directorio] operationId: estadoParticipantes summary: Estado de cada participante description: | Estados de la especificación: `Activo` con submotivo `Normal` o `Mecanismo alternativo`; `Inactivo` con `Desconectado`, `Mantenimiento sistema`, `Suspendido` o `En proceso de incorporacion`. Un participante Inactivo no debe recibir tráfico. security: [] responses: '200': description: Estado por organización. content: application/json: schema: type: array items: { $ref: '#/components/schemas/EstadoParticipante' } '503': { description: 'El Directorio no está disponible.' } /organisations: get: tags: [Directorio] operationId: listarOrganizaciones summary: Organizaciones, paginadas parameters: - { name: page, in: query, schema: { type: integer, minimum: 0, default: 0 } } - { name: sort, in: query, schema: { type: string } } - { name: direction, in: query, schema: { type: string, enum: [ASC, DESC] } } - { name: filterBy, in: query, schema: { type: string } } responses: '200': description: Página de organizaciones. content: application/json: schema: { type: object } '400': { description: 'Parámetros de paginación inválidos.' } '401': { description: 'Falta el certificado de cliente.' } /organisations/{oid}/softwarestatements: post: tags: [Declaraciones de software] operationId: crearDeclaracion summary: Crear una declaración de software description: | Primer paso para incorporarse. Declara la aplicación y sus URLs de redirección. Todavía no emite la SSA: antes hay que subir los certificados de firma y de cifrado. parameters: - { name: oid, in: path, required: true, schema: { type: string } } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/DeclaracionSoftware' } responses: '201': description: Declaración creada. content: application/json: schema: { $ref: '#/components/schemas/DeclaracionSoftware' } '400': { description: 'Faltan campos obligatorios.' } '404': { description: 'La organización no existe.' } /organisations/{oid}/softwarestatements/{ssid}/certificates/{uso}: post: tags: [Declaraciones de software] operationId: registrarCertificado summary: Registrar un certificado de firma o de cifrado description: | Se sube el certificado en multipart bajo el campo `publicKeyFile`. El Directorio lo publica en el JWKS de la declaración, que es contra lo que los demás participantes verifican las firmas. Un certificado registrado pasa por tres estados: `active`, `deprecated` durante una ventana de 30 días, y `revoked`. parameters: - { name: oid, in: path, required: true, schema: { type: string } } - { name: ssid, in: path, required: true, schema: { type: string } } - name: uso in: path required: true description: '`sig` para firma, `enc` para cifrado.' schema: { type: string, enum: [sig, enc] } requestBody: required: true content: multipart/form-data: schema: type: object required: [publicKeyFile] properties: publicKeyFile: type: string format: binary description: Certificado en PEM. responses: '201': { description: 'Certificado registrado y publicado en el JWKS.' } '400': { description: 'El archivo no es un certificado PEM válido.' } '404': { description: 'La declaración no existe.' } /organisations/{oid}/softwarestatements/{ssid}/assertion: get: tags: [Declaraciones de software] operationId: obtenerSSA summary: Obtener la SSA firmada description: | Devuelve la Software Statement Assertion como JWS compacto firmado en PS256 por el Directorio. Es la credencial que se presenta al registrarse dinámicamente en un servidor de autorización. parameters: - { name: oid, in: path, required: true, schema: { type: string } } - { name: ssid, in: path, required: true, schema: { type: string } } responses: '200': description: JWS compacto. content: application/jwt: schema: { type: string } '409': { description: 'Todavía no hay certificados de firma y cifrado registrados.' } '404': { description: 'La declaración no existe.' } /public/v1/organisations/{oid}/softwarestatements/{ssid}/jwks: get: tags: [Declaraciones de software] operationId: jwksDeclaracion summary: Claves públicas de una declaración security: [] parameters: - { name: oid, in: path, required: true, schema: { type: string } } - { name: ssid, in: path, required: true, schema: { type: string } } responses: '200': description: JWKS. content: application/json: schema: { $ref: '#/components/schemas/Jwks' } '404': { description: 'La declaración no existe.' } /jwks: get: tags: [Directorio] operationId: jwksDirectorio summary: Claves públicas del Directorio description: Contra estas se verifica la firma de cualquier SSA que emita. security: [] responses: '200': description: JWKS del Directorio. content: application/json: schema: { $ref: '#/components/schemas/Jwks' } '503': { description: 'El Directorio no está disponible.' } /open-finance/consents/v1/consents: post: tags: [Consentimientos] operationId: crearConsentimiento summary: Crear un consentimiento description: | **Primer paso del flujo, antes del PAR.** El consentimiento nace en estado `AWAITING_AUTHORISATION`: existe antes de que el cliente final diga que sí, y por eso puede ser rechazado. `notificationEmail` es la dirección a la que el IPI manda el segundo factor. Si no viaja, el banco usa la que tenga registrada. parameters: - $ref: '#/components/parameters/FapiInteractionId' - $ref: '#/components/parameters/JwsSignature' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SolicitudConsentimiento' } responses: '201': description: Consentimiento creado. content: application/json: schema: { $ref: '#/components/schemas/RespuestaConsentimiento' } '400': { description: 'Permisos o cuentas inválidos.' } '401': { description: 'Falta el certificado de cliente.' } /open-finance/consents/v1/consents/{consentId}: get: tags: [Consentimientos] operationId: leerConsentimiento summary: Leer un consentimiento description: El IPI es la fuente de verdad de su estado; el PSBI no debe recordarlo. parameters: - $ref: '#/components/parameters/ConsentId' - $ref: '#/components/parameters/FapiInteractionId' responses: '200': description: Consentimiento. content: application/json: schema: { $ref: '#/components/schemas/RespuestaConsentimiento' } '404': { description: 'No existe.' } /open-finance/consents/v1/consents/{consentId}/authorization-details: get: tags: [Consentimientos] operationId: detallesDeAutorizacion summary: authorization_details del consentimiento description: | Devuelve el arreglo RAR (RFC 9396) que el PSBI tiene que meter **dentro** del objeto de petición firmado y cifrado del PAR. Es lo que convierte un alcance de una palabra en una lista de cuentas y acciones. parameters: - $ref: '#/components/parameters/ConsentId' responses: '200': description: Arreglo de authorization_details. content: application/json: schema: type: array items: { $ref: '#/components/schemas/DetalleAutorizacion' } '404': { description: 'El consentimiento no existe.' } /open-finance/consents/v1/consents/{consentId}/authorise: post: tags: [Consentimientos] operationId: autorizarConsentimiento summary: Marcar el consentimiento como autorizado description: | Se llama con el token del cliente final después del canje, y el `subject` sale de ese token. El IPI tiene que registrar QUIÉN autorizó, y eso solo puede salir de ahí. parameters: - $ref: '#/components/parameters/ConsentId' requestBody: required: true content: application/json: schema: type: object required: [subject] properties: subject: type: string description: Identificador del cliente final, tomado del token. examples: ['11111111-1'] responses: '200': description: Consentimiento en AUTHORISED. content: application/json: schema: { $ref: '#/components/schemas/RespuestaConsentimiento' } '401': { description: 'Token ausente o no ligado al certificado del canal.' } '404': { description: 'El consentimiento no existe.' } '409': { description: 'El consentimiento ya fue rechazado o revocado.' } security: - mtls: [] portador: [] /open-finance/accounts/v1/accounts: get: tags: [Cuentas] operationId: listarCuentas summary: Cuentas del cliente final description: | Solo devuelve las cuentas que el `authorization_details` del token nombra. El IPI compara además la huella del certificado del canal contra el `cnf.x5t#S256` del token: si no coinciden, rechaza aunque el token sea válido. parameters: - $ref: '#/components/parameters/ConsentIdHeader' - $ref: '#/components/parameters/FapiInteractionId' responses: '200': description: Cuentas. content: application/json: schema: { $ref: '#/components/schemas/RespuestaCuentas' } '401': { description: 'Token inválido, o su certificado no calza con el del canal.' } '403': { description: El consentimiento no cubre lo solicitado. } security: - mtls: [] portador: [] /open-finance/accounts/v1/accounts/{accountId}/balance: get: tags: [Cuentas] operationId: saldoDeCuenta summary: Saldo de una cuenta parameters: - { name: accountId, in: path, required: true, schema: { type: string } } - $ref: '#/components/parameters/ConsentIdHeader' responses: '200': description: Saldo. content: application/json: schema: { type: object } '401': { description: 'Token inválido o certificado que no calza.' } '403': { description: 'El consentimiento no cubre esta cuenta.' } security: - mtls: [] portador: [] /open-finance/accounts/v1/accounts/{accountId}/transactions: get: tags: [Cuentas] operationId: movimientosDeCuenta summary: Movimientos de una cuenta parameters: - { name: accountId, in: path, required: true, schema: { type: string } } - { name: pageSize, in: query, schema: { type: integer, default: 25 } } - $ref: '#/components/parameters/ConsentIdHeader' responses: '200': description: Movimientos. content: application/json: schema: { type: object } '401': { description: 'Token inválido o certificado que no calza.' } '403': { description: 'El consentimiento no cubre esta cuenta.' } security: - mtls: [] portador: [] components: securitySchemes: mtls: type: mutualTLS description: | Certificado de transporte emitido por la CA del sandbox. Sin él, el borde corta la conexión antes de que llegue a la aplicación. portador: type: http scheme: bearer bearerFormat: JWT description: | Token del cliente final, ligado al certificado por `cnf.x5t#S256` y con `authorization_details` propagado por el mapeador propio de Keycloak. parameters: ConsentIdHeader: name: x-consent-id in: header required: true description: | Consentimiento bajo el que se piden los datos. En las APIs de recursos viaja como cabecera, no en la ruta: la ruta identifica la cuenta y la cabecera dice con qué permiso se la pide. schema: { type: string } ConsentId: name: consentId in: path required: true description: 'Identificador con forma `urn:sfa:consent:`.' schema: { type: string } examples: propio: value: 'urn:sfa:consent:f686ef3f-6044-48b0-9b56-f198cd0cde65' FapiInteractionId: name: x-fapi-interaction-id in: header required: false description: Correlación de la petición de punta a punta, para poder seguirla en los registros. schema: { type: string } JwsSignature: name: x-jws-signature in: header required: false description: | Firma separada del cuerpo (RFC 7515 Apéndice F / RFC 7797). Va aparte para que el receptor verifique exactamente los bytes recibidos sin volver a serializar el JSON, que cambiaría el resultado. schema: { type: string } schemas: Organizacion: type: object properties: OrganisationId: { type: string, examples: ['org-ipi-0000001'] } LegalEntityName: { type: string, examples: ['Banco Sintetico SpA'] } Status: { type: string, examples: ['Activo'] } authorisationServers: type: array items: type: object properties: AuthorisationServerId: { type: string, examples: ['as-ipi-001'] } CustomerFriendlyName: { type: string } Issuer: { type: string, format: uri } ApiResources: type: array items: type: object properties: ApiEndpoint: { type: string, format: uri } ApiFamilyType: { type: string, examples: ['accounts'] } ApiVersion: { type: string, examples: ['1.0.0'] } EstadoParticipante: type: object properties: OrganisationId: { type: string } Status: { type: string, enum: [Activo, Inactivo] } SubStatus: type: string enum: [Normal, Mecanismo alternativo, Desconectado, Mantenimiento sistema, Suspendido, En proceso de incorporacion] DeclaracionSoftware: type: object required: [ClientName, RedirectUri] properties: SoftwareStatementId: { type: string } ClientName: { type: string, examples: ['Fintech de Pruebas SpA'] } RedirectUri: type: array items: { type: string, format: uri } Status: { type: string } Jwks: type: object properties: keys: type: array items: type: object properties: kty: { type: string } use: { type: string, enum: [sig, enc] } alg: { type: string } kid: { type: string } DetalleAutorizacion: type: object description: Una entrada de RAR (RFC 9396). required: [type] properties: type: { type: string, examples: ['Accounts'] } consentId: { type: string } accountId: { type: string, examples: ['CL-ACC-000001'] } actions: type: array items: { type: string, examples: ['ReadAccounts'] } datatypes: type: array items: { type: string } level: { type: string, examples: ['level1'] } SolicitudConsentimiento: type: object required: [permissions, accountIds] properties: permissions: type: array items: type: string enum: [ACCOUNTS_READ, BALANCES_READ, TRANSACTIONS_READ] accountIds: type: array items: { type: string } expirationDateTime: { type: string, format: date-time } notificationEmail: type: string format: email description: Dirección a la que el IPI manda el código del segundo factor. RespuestaConsentimiento: type: object properties: data: type: object properties: consentId: { type: string } status: type: string enum: [AWAITING_AUTHORISATION, AUTHORISED, REJECTED, REVOKED, CONSUMED] subject: { type: string } permissions: { type: array, items: { type: string } } accountIds: { type: array, items: { type: string } } creationDateTime: { type: string, format: date-time } expirationDateTime: { type: string, format: date-time } links: { type: object } meta: { type: object } RespuestaCuentas: type: object properties: data: type: object properties: accounts: type: array items: type: object properties: accountId: { type: string } type: { type: string, examples: ['CURRENT_ACCOUNT'] } nickname: { type: string } currency: { type: string, examples: ['CLP'] } links: { type: object } meta: { type: object }