status that Sintropix computes on read: unbooked, booked_pending_allocation, reconciled, or excluded. This page covers listing, editing, booking, and matching Bank Movements, individually or in bulk. It also covers assigning the rest of an overpaid Match, and recording an internal transfer between two Bank Accounts.
Status
Read endpoints enrich each Movement with astatus field computed from its booking and its Suspense line:
You can also filter (
status=...) and group (groupBy=status) the GET /movements listing by this value. Only unbooked and booked_pending_allocation are pending work. A listing with no status filter lists every Movement, excluded ones included. Use status=notExcluded to list every Movement headed for the books, and status=excluded to list only the excluded ones.
Credit card movements
Erwin can load the movements of a credit card into the card’s Bank Accounts. To run it, ask Erwin in a Slack channel bound to the Entity’s Organization through an Agent Channel. There are two ways to load a card:- From a statement (estado de cuenta facturado). Use this for each monthly statement. The statement is final, and after the load the Bank Account’s balance equals the statement’s billed balance.
- From unbilled movements (movimientos no facturados). Use this every few days during the month, so your team can reconcile card spend before the statement arrives. These movements are provisional.
- Each section of a card is a separate
cardBank Account. The national (CLP) and international (USD) sections are two Bank Accounts. Erwin finds each one by the card’s last 4 digits and the currency. Erwin never creates a Bank Account. It stops and asks if it finds none or more than one. - Erwin loads every movement the bank shows, except one the bank marks as a pending authorization. It copies the
movementDate,amount, andsideexactly as the bank shows them. A charge is acredit, and a payment or refund is adebit. - The
descriptionis the merchant when the bank shows it apart from the movement’s text. Otherwise it is the bank’s text for the movement. Erwin copies either one exactly as the bank shows it. - Every movement gets a key in
externalRefthat never changes. metadataholds only these labels, in this order, and only when the bank shows the fact:Tarjeta,Rubro,Ciudad,País,Monto origen,Cuota,Monto total compra,Fecha de compra,Referencia banco, andLeído de. Erwin attaches no files.Tarjetanames the card that made the movement when the section has more than one card: its last 4 digits andtitularoradicional, oradicionalalone when the bank hides the card’s digits.- Erwin sets
partnerIdonly when it is sure of the Partner. It never creates a Partner or changes one a movement already has. - Erwin never creates adjustment movements to make a balance agree, never deletes a movement, and never books one. It reports each difference instead.
From a statement
Send the statement file (PDF or Excel) in the Slack channel, or ask Erwin to download it from the bank’s site. Before it writes anything, Erwin checks that:- the statement adds up: the previous balance, plus charges, minus payments and refunds, equals the billed balance.
- the Bank Account’s
runningBalanceon the day before the period starts is minus the previous statement’s balance. A debt reads negative. If the Bank Account has no movements yet, Erwin first creates aSaldo inicialmovement for the previous balance. - the period has no movements from other loads, such as unbilled movements. If it does, Erwin stops and reports them for a person to reconcile.
Fecha de compra. It dates installments (cuotas) at the statement’s billing date (fecha de facturación). The key of each line is estado:<billing date>:<line number>, for example estado:2026-08-25:001. A statement movement is final, and later loads never change it. After the load, Erwin reports whether the closing runningBalance equals minus the billed balance.
From unbilled movements
Erwin reads the bank’s site, so the Entity needs a login Credential for the bank. BCI, BICE, and Santander are supported. For Santander, a person signs in by hand and Erwin continues in their session. See Sign in to a bank by hand. If Santander can’t load a card’s movements, Erwin retries up to 3 times. If it still fails, it goes on with the next card or section, keeps what it already read, and reports the one that failed with the site’s message. Each run rereads the whole open period, from the day after the last billing date to 2 days before today. It skips today and yesterday, because the bank can still reverse those movements. The key isno-facturado:<bank ID>. When the bank shows no ID, the key is no-facturado:<date>:<amount in minor units>:<side>:<n>, where n counts the movements with the same date, amount, and side. Erwin pairs each site movement with an existing Movement by key first. If there is no key match, it pairs with the one Movement that has the same amount and side, dated at most 3 days away. Then:
- An unpaired site movement is created.
- A paired
unbookedMovement gets the site’s current date, description, amount, and metadata. A booked one gets only new metadata and a Partner. Erwin replaces the wholemetadatalist rather than adding to it. - Erwin changes only Movements whose key starts with
no-facturado:. It reports pairs with Movements from other loads without changing them. - A
no-facturado:Movement the site no longer shows is reported, not deleted. It is often a reversed authorization.
Bank account movements
Erwin can load the movements of a checking account, in pesos or in dollars, from the bank’s site. Each movement carries what the site knows beyond the bank’s description: who paid or received the money, the operation number, and often the invoice the payment is for. Your team can then reconcile from Sintropix without opening the bank. To run it, ask Erwin in a Slack channel bound to the Entity’s Organization through an Agent Channel. Erwin reads the bank’s site, so the Entity needs a login Credential for the bank. If the login reaches more than one company at the bank, Erwin picks the company whose RUT is the Entity’staxId, and stops if none matches. At BCI, Erwin reads the counterpart of each transfer of a CLP account. For a USD account, it reads the payment order behind each movement, with the ordenante and the payment reference of its SWIFT message. It also reports payment orders that reached the bank but aren’t in the account yet.
Erwin follows these rules:
- Erwin finds the
bankBank Account by its account number and currency. It never creates a Bank Account, and stops and asks if it finds none or more than one. - Each run reads from 3 days before the newest Movement of the Bank Account to today. If the Bank Account has no Movements, Erwin asks from which date to load.
- It copies the
movementDateandamountexactly as the bank shows them. A credit to the account (abono) is adebit, and a charge (cargo) is acredit. - The
descriptionis the bank’s text, then the counterpart’s name and RUT when the bank shows them, joined with·, the same format a bank feed writes. For example:ORDEN DE PAGO RECIBIDA · BARRIOS Y FUENTES BAFUR. - The key in
externalRefis<bank>:<account number>:op:<operation number>, for examplebci:19864345:op:3211420. When the bank shows no operation number, the key is<bank>:<account number>:<date>:<amount in minor units>:<side>:<n>, wherencounts the movements with the same date, amount, and side, from1. The key never changes. metadataholds only these labels, in this order, and only when the bank shows the fact:Contraparte,RUT de contraparte,Cuenta de contraparte,Institución de contraparte,País de la contraparte,Comentario,N° de operación,Fecha valor(only when it differs from the movement’s date), andReferencia de pago. Dates areYYYY-MM-DD.- Erwin sets
partnerIdonly when it is sure of the Partner: the counterpart’s RUT is the RUT of exactly one Partner, earlier Movements with the same counterpart all have the same Partner, or the counterpart names exactly one Partner. It never creates a Partner or changes one a Movement already has.
- An unpaired site movement is created.
- A paired
unbookedMovement gets the site’s description and metadata. A booked one gets only new metadata and a Partner, and Erwin reports any other difference. Erwin replaces the wholemetadatalist rather than adding to it. - Erwin changes only Movements whose key starts with
<bank>:<account number>:. It reports pairs with Movements from other loads without changing them.
N° de operación, Fecha valor, and Referencia de pago lines after the lines the Movement already has.
Ontop wallet
Ontop pays a company’s foreign contractors from a prepaid USD wallet. Erwin loads the wallet like any other bank account, with the rules above, from the wallet history of Ontop’s site. Ask “carga Ontop” to load the wallet and then the Ontop invoices in one go. Before the first load:- Add a login Credential for Ontop to the Entity in Integraciones. If Ontop shows a captcha or asks for a code, a person signs in by hand.
- Create a USD
bankBank Account for the wallet. The wallet has no account number, so Erwin uses the company’s client id in Ontop instead. Ask Erwin to note the client id and the wallet’s Bank Account in his wiki.
- The
amountis what entered or left the wallet. For a top-up by card, that excludes the card’s fee. - The
descriptionis the row’s type, payment method code, and reference, joined with·, for exampleTOP_UP · MAN · Cashback. - Ontop shows no operation number, so the key is
ontop:<client id>:<date>:<amount in minor units>:<side>:<n>. - After the load, Erwin reports the wallet’s balance in Ontop next to the Bank Account’s balance.
Most routes on this page are nested under a Bank Account. The base path is
/api/entities/:entityId/bank-accounts/:bankAccountId/movements, and :movementId is a UUID. The one exception is GET /bank-movements/pages, which sits directly under the Entity and lists the Movements of every Bank Account.The Bank Movement object
Every read and write on this page returns Movements in this shape. Amounts are non-negative integers in the account currency’s minor units; the direction is carried byside.
Order of movements
Movements are in date order: bymovementDate, then by providerTime, then by createdAt, then by id. Within one day, a Movement without a providerTime comes before every Movement of that day that has one. The running balance follows this order, so a conversion reads after the deposits that funded it earlier that day.
A Movement keeps the providerTime it first landed with. A later sync can fill it on a Movement that has none, booked or not, but never changes one the Movement already holds.
Write answers also return the Bank Account with its computed summary:
GET /movements
List Movements on a Bank Account. Returns a paginated { movements, nextCursor } object.
Query parameters
Response
Each row is a Bank Movement object plus
attachmentCount (integer, the number of attachments on the Movement), hasErpThread (boolean, true when the Movement has an ERP Thread), and group: null when groupBy=none, otherwise { by, value, key, movementCount, startsGroup } where by is the groupBy value, value is the Partner name (nullable), the YYYY-MM month, or the status, movementCount is the size of the group across the whole result, and startsGroup is true on the first row of the group. nextCursor is null on the last page.
erpThread=without:
GET /api/entities/:entityId/bank-movements/pages
List the Movements of every Bank Account of the Entity in one listing, grouped by Bank Account by default. Use it to build one queue of the work still to reconcile instead of reading each Bank Account in turn. It returns the same paginated { movements, nextCursor } object as GET /movements.
Query parameters
It takes the parameters of GET /movements except amountMin and amountMax, because the Bank Accounts can hold different currencies. groupBy also takes bankAccount, and defaults to it. Any other parameter, including amountMin and amountMax, is refused with 400.
Response
Each row has the shape of a GET /movements row. Read bankAccountId to know which account a Movement sits on. runningBalance is the balance of that Movement’s own Bank Account.
With groupBy=bankAccount, group.value is the Bank Account name and group.key is bankAccount:<bankAccountId>. Groups come in alphabetical order of the account name.
To list every Movement still to reconcile, across every account:
/{entityId}/banks/movements. See Link to the app.
GET /movements/:movementId/match-candidates
List the open Ledger Entry Lines and the unbooked Invoices that could settle this Movement, ranked by Sintropix. The order is: exact amount first, then Partner evidence, then the distance between the dates, then the newest entry or issue date, then id. At most 200 candidates, cut after the ranking.
Query parameters
Response:
{ candidates: [...], highConfidenceCandidateId }. highConfidenceCandidateId names the recommended candidate (its line.id, or its invoice.id for an unbooked Invoice), or is null when no single candidate stands out. Each candidate has a kind:
line: an open Ledger Entry Line on the side that settles the Movement.unbookedInvoice: an Invoice of kindinvoicethat isn’t booked yet, so it has no line to settle. Book the Invoice first; it then appears as alinecandidate. A credit note or atax_onlydocument is never anunbookedInvoicecandidate. It carrieskind,invoice({ id, documentNumber, issueDate, total, currency, direction, documentKind, isExpenseRendition }),partner, andevidence.
evidence: partnerEvidence ({ assigned, name, taxId }, booleans that say whether the Partner is the Movement’s Partner, or its name or tax ID appears in the description), amountRelation (exact | underpays | overpays, where underpays means the Movement is smaller; null on a foreign-currency Bank Account), and dateDistanceDays (integer).
A line candidate has these fields:
Pass the candidate’s
line.id as lineId to POST /movements/:movementId/match.
GET /movements/:movementId/reconciliation
Return the Movement’s current booking or match state and the Ledger Lines that discharge it. Only a booked or matched Movement has one.
Response
POST /movements
Create a manual Bank Movement on the account. Requires x-audit-actor for API-key callers.
Body
Response:
{ movement, bankAccount }, the new Movement (as a list row, without group) and the Bank Account with its refreshed summary.
POST /movements/bulk
Create up to 100 Movements on the Bank Account in a single request and transaction. Each element carries its own client-supplied id (required and unique within the batch) so a retry collides on the primary key rather than double-inserting.
- Body:
{ movements: [...] }(1 to 100 elements), each in thePOST /movementsbody shape. - Response: array of
{ movement, bankAccount }, in input order.
bulk, book, and match directly under /movements are the bulk routes, never a Movement id.POST /movements/book
Book up to 100 already-created Movements in a single transaction. Each element carries a movementId plus the booking instructions used by the single-movement endpoint.
- Body:
{ bookings: [...] }(1 to 100 elements), each in thePOST /movements/:movementId/bookbody shape plusmovementId. - Response: array of
{ movement, bankAccount, reconciliation }, in input order.
POST /movements/match
Match up to 100 already-created Movements to existing documents in a single transaction. Each element carries a movementId plus the match instructions used by the single-movement endpoint.
- Body:
{ matches: [...] }(1 to 100 elements), each in thePOST /movements/:movementId/matchbody shape plusmovementId. - Response: array of
{ movement, bankAccount, reconciliation }, in input order.
Bulk endpoints run as one database transaction. If any element fails validation or a domain rule, the whole batch is rejected and the response identifies the offending element by its zero-based
elementIndex.PATCH /movements/:movementId
Edit an existing Movement (for example, correct a description or counterparty).
Body: any subset of the create fields except id. Every field is editable while the Movement is unbooked; once booked or matched, only partnerId and metadata may change.
Response:
{ movement, bankAccount }.
DELETE /movements/:movementId
Remove a Movement. Booked or matched Movements are refused; unbook or unmatch first.
Response: { movementId, bankAccount }, the id of the deleted Movement and the Bank Account with its refreshed summary.
POST /movements/:movementId/book
Book a Movement to a Ledger Entry. Sintropix generates the balanced Ledger Entry from the Movement plus the booking instructions in the body. You supply the entry header and the counterpart lines; the bank line on the Bank Account’s mapped Ledger Account is built for you, so it can never be on the wrong side or for the wrong amount.
Body
Counterpart line object
Response:
{ movement, bankAccount, reconciliation }, where reconciliation has the same shape as GET /movements/:movementId/reconciliation.
POST /movements/:movementId/match
Match a Movement to an existing document (Invoice or Ledger Entry) rather than booking a new entry. Sintropix records an Allocation between the Movement’s counter-account line and the matched document’s open-item line.
The settlement line can settle at most the target’s Open Amount. When the Movement is larger than your counterpart lines, Sintropix books the rest on the Entity’s Suspense Account. See Overpayments and the Suspense Account.
Body: the same fields as POST /book without movementPartnerId, plus the line to settle:
Response:
{ movement, bankAccount, reconciliation }. When a rest is left, reconciliation.entry.lines includes the Suspense line and movement.status is booked_pending_allocation.
Overpayments and the Suspense Account
A Movement can pay more than the document you match it to. For example, a customer transfers 1,000,000 against an invoice with 700,000 open. The extra 300,000 might pay another invoice, belong to another Partner, or cover an invoice that has not arrived yet. Sintropix doesn’t credit it to the invoice’s Partner. It books the rest on the Entity’s Suspense Account instead. The Suspense Account is anasset Open-Item Account with sub-type suspense. Each Entity has at most one. See Chart of Accounts to create or mark it. Its lines carry no Partner, and only Match, Assign, and Unassign write them.
The flow for the example above:
Keep these rules in mind:
- A Match that leaves a rest is refused with
BANK_MOVEMENT_SUSPENSE_ACCOUNT_MISSING_ERRORwhen the Entity has no Suspense Account. A Match with no rest works without one. - Assign and Unassign edit the Match’s Ledger Entry in place. They do not create a new Entry. They work only on an active Entry dated after the Lock Date.
- To undo the whole Match, unbook the Movement. Reversing the Entry is refused while it still has a Suspense line.
- On a foreign-currency Bank Account, the Suspense line is stated in the Bank Account’s currency at
bankLineFxRate. - The Suspense Account cannot be a Match or Assign target, and you cannot post to it through manual entries or Reclassify.
POST /movements/:movementId/assign
Move part or all of a Movement’s Suspense line into new lines of the same Ledger Entry. Use it when you find out what the rest of an overpaid Match pays. Requires x-audit-actor for API-key callers.
Assign works in two ways:
- Settle an open item. Set
lineIdto the open line to settle, such as another Invoice’s control line. Include a settlement line on that line’s account. Sintropix allocates it against the target, up to the target’s Open Amount, the same way as Match. - Categorize to accounts. Set
lineIdtonulland send plain lines on any account, for example other income or a bank fee.
reconciled.
Body
Response:
{ movement, bankAccount, reconciliation }, the same shape as POST /match.
POST /movements/:movementId/unassign
Move one counterpart line of the Movement’s Ledger Entry back to the Suspense line. Sintropix removes the line’s Allocations, so any document it settled reopens. Use it to correct an Assign or a Match line without unbooking the whole Movement. Requires x-audit-actor for API-key callers.
The Entity must have a Suspense Account. Sintropix creates the Suspense line if the Entry has none. You cannot unassign the bank line, the Suspense line, or a line that another record holds, such as a Fixed Asset.
Body
Response:
{ movement, bankAccount, reconciliation }.
Match, Assign, and Unassign errors
Each error answers409 with the code and the params shown. Amounts are functional amounts in minor units.
Record an internal transfer
An Internal Transfer moves money between two Bank Accounts of the same Entity, for example from checking to savings or through a currency conversion. It is never income or expense. There is no dedicated transfer endpoint. You record a transfer withPOST /book and an Allocation.
Each leg of the transfer passes through a bridge account. A bridge account is an open-item Ledger Account with sub-type internal_transfer (see Chart of Accounts). An open line on the bridge account is money in transit. The Allocation that joins the two bridge lines shows that both legs arrived.
- Book each leg with
POST /movements/:movementId/book. Put one counterpart line on the bridge account, on the side opposite to the bank line. Add extra lines, such as a bank fee or realized FX, as needed. The Entry must balance. Keep the id of the bridge line from the response. - Join the two bridge lines with an Allocation, in one of two ways:
- Call Match Lines (
POST /api/entities/:entityId/ledger/allocations/match) with the two bridge line ids inlineIds. Use this for cross-currency transfers. - Book only the first leg, then call
POST /movements/:movementId/matchon the second Movement withlineIdset to the open bridge line. The settlement line and the bridge line must have the same currency.
- Call Match Lines (
Exclude a movement from the books
Some Movements should never be booked, and deleting a fed Movement doesn’t help because the next sync lands it again. Exclude it instead. An Exclusion records, with a reason, that the Movement will never be booked. It takes the Movement out of the pending work, and you can lift it at any time. An excluded Movement:- Has
status: "excluded". A listing with nostatusfilter still lists it, and the app crosses those rows out. It’s left out ofstatus=notExcluded, ofstatus=unreconciled, and ofunreconciledCount. - Can’t be booked, matched, or assigned, single or bulk, and can’t hold a Follow-up. Excluding the Movement clears its Follow-up. These writes are refused with
409andBANK_MOVEMENT_EXCLUDED_ERROR. - Stays landed. A bank feed sync skips it.
viewer is refused with 403. Each Exclusion and Inclusion appears in the Movement’s audit trail.
PUT /movements/:movementId/exclusion
Exclude an unbooked Movement from the books.
Body
Response:
{ movement, bankAccount }, with excludedAt and exclusionReason set on the Movement.
DELETE /movements/:movementId/exclusion
Include an excluded Movement in the books again. It goes back to unbooked.
Response: { movement, bankAccount }, with excludedAt and exclusionReason null on the Movement.
Exclusion errors
A blank
reason, or one longer than 1,000 characters, is refused with 400. Each error carries the movementId in params; BANK_MOVEMENT_ALREADY_RECONCILED_ERROR also carries the ledgerEntryId that books it.

