FINORA Core · How-to guide
Integrate the bank's satellite systems with FINORA Core as the system of record
A bank has many systems around its core: the branch teller, the ERP for corporate accounting, internet banking and mobile banking, the reconciliation system, a corporate customer’s payroll system. This guide explains how to connect them with FINORA Core so that the core is the system of record: the only place where an account balance lives, where every transaction is recorded and where every journal entry is generated. By the end you will know which door each system uses, which credential and scopes to request, which routes to call and how to learn what happens in the core without polling it all the time.
1. Split the work correctly: what belongs to the core and what belongs to the satellite
Section titled “1. Split the work correctly: what belongs to the core and what belongs to the satellite”There is only one rule: the satellite never keeps its own balance and never posts accounting on its own. It captures the operation, asks the core to perform it and shows what the core answers.
| FINORA Core does | The satellite system does |
|---|---|
| Keeps the balance of each account: ledger, held and available | Shows the balance the core returns at that moment |
| Validates funds, account status, holds and blocks | Requests the operation and honors a rejection |
| Records the transaction with its stable ledger reference | Stores the core’s reference next to its own voucher |
| Generates the journal entry from the product’s and channel’s accounting concept | Never builds a journal entry by hand for a customer movement |
| Closes the day and produces the trial balance | Reads the general ledger to reconcile or report |
| Notifies by webhook or through the event stream | Reacts to the notice instead of polling in a loop |
flowchart LR T["Teller<br/>(FINORA Sucursal or another)"] -->|"accounts, checks, payments"| K E["Bank ERP"] -->|"apply-concept, ledger queries"| K C["Channels<br/>(internet, mobile)"] -->|"/v1 through the gateway"| G["API gateway"] G --> K["FINORA Core<br/>system of record"] K -->|"signed webhooks<br/>and events"| E K -->|"signed webhooks<br/>and events"| C
2. Choose the door: the gateway or the core API
Section titled “2. Choose the door: the gateway or the core API”FINORA Core has two doors for external systems. They are not mutually exclusive: each system uses the one that fits it.
API gateway (/v1) | Core API (/api/v1) | |
|---|---|---|
| For whom | Systems outside the core’s network: channels, partners, fintechs | The bank’s internal systems: teller, ERP, reconciliation |
| Identity | A gateway credential per system (client_id and client_secret, by OAuth token or apiKey) | A core API credential per system (OAuth Bearer or X-Api-Key header) |
| Scopes | By domain: clients:*, deposits:*, loans:*, gl:*, webhooks:*, events:read, config:*, license:read, fx:* | Fine-grained: account:read, payment:write, credit:payment, gl:read, accounting:write… |
| What it adds | Institution and environment by subdomain or X-Finora-Tenant, per-client limits, circuit breaker, uniform errors | Access to routes the gateway does not publish: checks, apply-concept, checkbooks |
| Where it is described | The core API gateway (in Spanish) | This guide and the accounting API (in Spanish) |
A practical rule: if the system faces the internet or is operated by a third party, it goes through the gateway. If it lives inside the bank and needs checks or posting by concept, it uses the core API with its own credential.
3. One credential per system, with just the scopes it needs
Section titled “3. One credential per system, with just the scopes it needs”Each satellite gets its own credential. Never share one between the teller and the ERP: if one leaks, it is revoked
without shutting down the other, and the core’s audit trail records which system performed each operation
(apikey:<id> in the created-by field).
| System | Scopes in the core API | Scopes in the gateway |
|---|---|---|
| Teller | account:read, payment:write | Not applicable: checks are not published on /v1 |
| Bank ERP | gl:read, accounting:write (if it posts by concept) | gl:read |
| Internet or mobile banking | account:read, payment:write, credit:read, credit:payment | clients:read, deposits:read, deposits:write, loans:read, loans:write |
| Notice receiver | webhook:read, events:read | webhooks:read, events:read |
Some core scopes are critical (gl:write, credit:payment, credit:approve, credit:disburse,
account:status, account:authorize, events:read, config:write, license:write): a credential with general access
does not receive them unless they are granted explicitly. Request only the scopes your system uses and review them every
month.
4. Accounts: find the account and read its balance
Section titled “4. Accounts: find the account and read its balance”The teller knows the account by its 20-digit number; the rest of the API asks for its UUID. The first step is always to resolve one into the other.
| Method and route in the core | Scope | What it does |
|---|---|---|
GET /api/v1/ext/accounts/by-code/{accountCode} | account:read | Resolves the account number: UUID, holder, product, currency, status |
GET /api/v1/ext/accounts/{accountId} | account:read | Account data, status and balances |
GET /api/v1/ext/accounts/{accountId}/balance | account:read | Ledger, held and available balance, with the balances by phase |
GET /api/v1/ext/accounts/{accountId}/transactions | account:read | Movements from newest to oldest, by cursor (size 1-100, from, to) |
GET /api/v1/ext/accounts?customerId=… | account:read | All the accounts of a customer |
POST /api/v1/ext/accounts | account:write | Opens an account for an existing customer, with Idempotency-Key |
GET, POST /api/v1/ext/accounts/{accountId}/blocks | account:read, account:status | Blocks, with four-eyes approval (account:authorize) |
GET /api/v1/ext/accounts/by-code/00010001010000000002X-Api-Key: <teller credential>{ "accountId": "3d0c4f0e-9c7f-4b8e-8c1a-0a5b5f1d2e11", "accountCode": "00010001010000000002", "customerId": "b4c6c1d8-bedd-4d05-ae70-a3152d4e1417", "holderName": "Cliente Uno", "productCode": "PRD-CC-VES", "currency": "VES", "statusCode": "CUENTA ACTIVA", "closed": false}Each movement carries ledgerRef, the stable ledger reference, and voucher, the accounting voucher, which is null
until the core posts it. Store the ledgerRef in your system: it is what ties your voucher to the core’s journal entry.
5. Payments: deposits, withdrawals and transfers
Section titled “5. Payments: deposits, withdrawals and transfers”Money enters and leaves an account through three routes. All three require the Idempotency-Key header and the
payment:write scope, and the core generates the journal entry from the product’s accounting concept.
| Method and route in the core | In the gateway | What it does |
|---|---|---|
POST /api/v1/payments/deposit | POST /v1/deposits/{depositId}/deposit-transactions | Credits from an external source (cash at the teller, incoming transfer) |
POST /api/v1/payments/withdrawal | POST /v1/deposits/{depositId}/withdrawal-transactions | Debits to pay out of the bank; validates funds first |
POST /api/v1/payments/transfer | POST /v1/deposits/{depositId}/transfer-transactions | Transfers between two accounts of the same bank |
POST /api/v1/payments/depositX-Api-Key: <teller credential>Idempotency-Key: 6f1c2b8e-0d4a-4c1e-9a77-2f0b9d3e5a10Content-Type: application/json
{ "destinationAccountId": "3d0c4f0e-9c7f-4b8e-8c1a-0a5b5f1d2e11", "amount": "1000.00", "currency": "VES", "externalReference": "CAJA-20261024-0042", "description": "Depósito en efectivo por taquilla", "channel": "AGENCIA", "channelCode": "CAJA"}The first time it answers 201; if you repeat the request with the same key, 200 with the same response and
without moving the balance again. channelCode is optional: if the bank defined that transaction channel, the
counterpart of the journal entry uses the channel’s account (for example, the branch cash drawer). See
Configure a transaction channel (in Spanish).
6. The bank’s own checks at the teller: validate and pay
Section titled “6. The bank’s own checks at the teller: validate and pay”Checks are only available in the core API (not in the gateway). There are two ways to pay them; choose one and do not mix them.
This is the contract used by FINORA Sucursal: the teller shows the cashier whether the check is payable, the cashier confirms and then the withdrawal is made.
-
Validate with
POST /api/v1/checks/validate(scopeaccount:read):{ "accountCode": "00010001010000000002", "checkNumber": "000101", "amount": "250.00" }It always answers 200. If
payableisfalse,reasonsays why (CHEQUE_ANULADO,FONDOS_INSUFICIENTES,CUENTA_BLOQUEADA,CHEQUE_EN_PROCESO…). If it istrue, the check is reserved for your credential untilreservedUntil(15 minutes by default) and another validation of the same check answersCHEQUE_EN_PROCESO. Validating does not move the balance. -
Withdraw with
POST /api/v1/payments/withdrawal, with the same credential, the same account, the same amount and a check-payment reference that starts withPCH-. The core marks the check as paid (CLEARED) in the same transaction as the debit.
POST /api/v1/checks/pay (scope payment:write, Idempotency-Key required) validates, debits and marks the check as
paid in a single transaction: a check is never paid twice, even if two cashiers try at the same time.
{ "accountCode": "00010001010000000002", "checkNumber": "000101", "amount": "250.00", "currency": "VES", "reference": "PCH-000123", "description": "Pago de cheque por taquilla", "channel": "AGENCIA"}It answers 201 with paymentId, checkId, checkStatus: "CLEARED" and ledgerRef. If the check is not payable,
422 with reason; if another payment of the same check is in progress, 409.
Checkbooks and the life cycle of each check (issue, delivery, voiding, stop payment, return) are under
/api/v1/ext/accounts/{accountId}/checkbooks and /api/v1/ext/accounts/{accountId}/checks.
7. Loans: query, collect and pay off
Section titled “7. Loans: query, collect and pay off”| Method and route in the core | Scope | What it does |
|---|---|---|
GET /api/v1/ext/credit-products | credit:read | Loan products |
POST /api/v1/ext/credits/simulations | credit:read | Simulates the amortization schedule |
POST /api/v1/ext/credits | credit:write | Creates the application |
POST /api/v1/ext/credits/{creditId}/approval | credit:approve | Approves or rejects, with four eyes |
POST /api/v1/ext/credits/{creditId}/disbursement | credit:disburse | Disburses the approved loan |
GET /api/v1/ext/credits/{creditId}, …/schedule | credit:read | Status and payment schedule |
POST /api/v1/ext/credits/{creditId}/payments | credit:payment | Collects installments |
GET /api/v1/ext/credits/{creditId}/payoff-quote | credit:read | Quotes the full payoff |
POST /api/v1/ext/credits/{creditId}/payoff | credit:payment | Pays off the loan |
A payment is applied in the legal order: fees, late interest, interest and principal, from the oldest installment
to the newest. Without amount, it collects what remains of the next installment. A larger amount is an
advance payment: it covers the following installments in order, without recalculating the schedule. If the last
installment is paid, the loan moves to CLOSED.
{ "amount": "1185.60", "originAccountId": "3d0c4f0e-9c7f-4b8e-8c1a-0a5b5f1d2e11", "channelCode": "CAJA" }8. Accounting: post by concept and read the general ledger
Section titled “8. Accounting: post by concept and read the general ledger”A satellite that needs to record an accounting event that is not an account movement (a fee collected in the ERP, an expense, an adjustment) does not build the journal entry: it tells the core which accounting concept to apply and for what amount. The concept is defined by the bank’s accountant and fixes the accounts, the legs and the currency.
Apply a concept: apply-concept
Section titled “Apply a concept: apply-concept”POST /api/v1/accounting/apply-concept (scope accounting:write):
{ "cia": "001", "conceptCode": "COMISION-SERVICIO", "baseAmount": 100.00, "currency": "VES", "description": "Comisión por servicio", "reference": "ERP-000123", "idempotencyKey": "erp-000123"}It answers { "ok": true, "status": "success", "anchorId": "…" }. If the concept does not exist or validation fails,
400 with ok: false and the reason in status. The idempotency key goes in the body: always use it, with your
document number, so that a retry does not duplicate the entry. customerAccount (UUID) is optional and ties the entry
to a customer’s account.
Read the general ledger
Section titled “Read the general ledger”The general-ledger queries are under /api/v1/ext/gl (scope gl:read) and the gateway publishes them on /v1:
| In the core | In the gateway | What it returns |
|---|---|---|
GET /api/v1/ext/gl/accounts | GET /v1/glaccounts | Chart of accounts, filterable by type, currency and whether it is postable |
GET /api/v1/ext/gl/accounts/{glCode}?balanceDate=… | GET /v1/glaccounts/{glCode} | One account with its balance by currency at a date |
POST /api/v1/ext/gl/journal-entries:search | POST /v1/gljournalentries:search | Ledger lines by criteria (date, account, reference) |
GET /api/v1/ext/gl/journal-entries/{voucher} | GET /v1/gljournalentries/{voucher} | A complete journal entry by voucher |
GET /api/v1/ext/gl/trial-balance | GET /v1/gltrialbalance | Trial balance |
POST /api/v1/ext/gl/reports | POST /v1/accounting/reports | Queues an asynchronous accounting report |
Field details, criteria and errors are in the accounting API reference (in Spanish). The balance of a general-ledger account includes what is pending in the close; the trial balance only counts what has already been posted.
Manual journal entries by API
Section titled “Manual journal entries by API”POST /api/v1/ext/gl/journal-entries (critical scope gl:write, Idempotency-Key) posts a balanced entry with several
legs and branches. It works only if the installation configured the manual journal-entry concept; without it, it
answers 422 manual-concept-unavailable.
9. Learn what happens: signed webhooks and events
Section titled “9. Learn what happens: signed webhooks and events”Instead of asking the core every minute whether a balance changed, subscribe to its notices.
- Signed webhooks. The core sends a
POSTto your URL for each event (a posted journal entry, an account change). Each notice carriesX-Finora-Signature(t=<seconds>,v1=<hex>, an HMAC of the raw body) andX-Finora-Webhook-Id, which is the same across all retries: verify the signature and deduplicate by that id. Registration, deliveries and redelivery are under/api/v1/ext/webhooks(core) or/v1/webhooks(gateway). See Receive and verify the core webhooks (in Spanish). - Event stream. If you need every event in order and the ability to resume where you left off, read
/api/v1/ext/eventsor create a subscription with a committed cursor at/api/v1/ext/event-subscriptions(in the gateway,/v1/eventsand/v1/subscriptions). See Consume the event stream (in Spanish).
The usual pattern: the ERP listens to AccEntryPosted to reconcile, and the channels listen to AccountChanged to
refresh the balance they show.
10. Module licensing: what your system may use
Section titled “10. Module licensing: what your system may use”FINORA is licensed by module. Before bringing a satellite online, check that the modules it uses are enabled in that installation:
GET /api/v1/ext/license/modulesX-Api-Key: <credential with license:read>Each module carries code, enabled, licensed, enforced and the API prefixes it covers. The ones that matter for
this guide are ACCOUNTS, TRANSACTIONS, CREDITS, CHECKBOOKS, ACCOUNTING, API (the external API in general),
GL_API (the accounting API) and GATEWAY. If the core has a registered license and a module is switched off, every
route of that module answers:
HTTP/1.1 403 Forbidden
{ "error": "MODULE_DISABLED", "module": "GL_API" }A 403 MODULE_DISABLED is not fixed with more scopes: it is resolved by whoever administers the subscription. See
Subscription and module licenses (in Spanish).
Checklist before going live
Section titled “Checklist before going live”- Each satellite has its own credential, with only the scopes it uses.
- Every write carries an idempotency key generated once per operation and stored before calling.
- The satellite stores the
ledgerRefor the voucher returned by the core next to its own document. - No balance is calculated in the satellite: it is always read from the core.
- The webhook receiver verifies the signature over the raw body and deduplicates by
X-Finora-Webhook-Id. - The modules the system uses appear as enabled in
/license/modules. - Testing was done against the institution’s sandbox, never against real data.
Common errors
Section titled “Common errors”| What happens | Why | What to do |
|---|---|---|
| A deposit appears twice | The retry carried a new Idempotency-Key | Reuse the same key on every retry of the same operation |
CHEQUE_EN_PROCESO when validating | Another credential or an earlier validation reserved the check | Wait until reservedUntil or pay with the credential that reserved it |
| The withdrawal does not mark the check as paid | The credential, account or amount changed, or the reference does not start with PCH- | Use the same data as the validation |
403 MODULE_DISABLED | The module is not licensed in that installation | Ask whoever administers the subscription to enable it |
422 manual-concept-unavailable | The installation did not configure the manual journal-entry concept | Post through apply-concept or ask for the concept to be configured |
apply-concept answers 400 | The concept does not exist in that company or the amount does not validate | Check conceptCode and cia with the bank’s accountant |
| The channel shows a stale balance | It stored the balance in its own database and does not listen to notices | Read the balance from the core or subscribe to AccountChanged |
Keep learning
Section titled “Keep learning”- Core integrator path (in Spanish): the complete order to master the integration.
- Your first call to the core API (in Spanish): token, chart of accounts and a journal entry in the sandbox.
- Declarative accounting by product and channel (in Spanish): why the satellite requests a concept instead of building the entry.
- The interactive OpenAPI references are on FINORA Developers.