Skip to main content
A Bank Movement is a single transaction line pulled from a bank feed or entered manually against a Bank Account. Every Movement carries a workflow 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 a status 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.
Both ways follow the same rules:
  • Each section of a card is a separate card Bank 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, and side exactly as the bank shows them. A charge is a credit, and a payment or refund is a debit.
  • The description is 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 externalRef that never changes.
  • metadata holds 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, and Leído de. Erwin attaches no files.
  • Tarjeta names the card that made the movement when the section has more than one card: its last 4 digits and titular or adicional, or adicional alone when the bank hides the card’s digits.
  • Erwin sets partnerId only 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 runningBalance on 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 a Saldo inicial movement 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.
Erwin then asks a person to approve the load, with the period, line count, totals, and check results. Each statement needs its own approval, even within the same conversation. Erwin dates a line from before the period at the period’s start, and puts its own date in 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 is no-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 unbooked Movement gets the site’s current date, description, amount, and metadata. A booked one gets only new metadata and a Partner. Erwin replaces the whole metadata list 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.
You can run it as often as you like without creating duplicates. There is no balance check. Your team reconciles the unbilled movements against the statement at month-end close.

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’s taxId, 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 bank Bank 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 movementDate and amount exactly as the bank shows them. A credit to the account (abono) is a debit, and a charge (cargo) is a credit.
  • The description is 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 externalRef is <bank>:<account number>:op:<operation number>, for example bci:19864345:op:3211420. When the bank shows no operation number, the key is <bank>:<account number>:<date>:<amount in minor units>:<side>:<n>, where n counts the movements with the same date, amount, and side, from 1. The key never changes.
  • metadata holds 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), and Referencia de pago. Dates are YYYY-MM-DD.
  • Erwin sets partnerId only 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.
Erwin pairs each site movement with an existing Movement the same way as for unbilled card movements: by key first, then 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 unbooked Movement 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 whole metadata list 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.
Erwin never creates adjustment movements to make a balance agree, never deletes a Movement, and never books one. It reports the bank’s balance next to the Bank Account’s balance, the movements it could not pair, and the counterparts it left without a Partner. Accounts with a bank feed. On a Bank Account with a feed, the feed creates the Movements and rewrites their description and its own labels at each sync. Erwin never creates Movements there. When you ask it to add the site’s facts, it only sets a missing Partner and adds the 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 bank Bank 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.
Erwin loads only the rows that succeeded, and skips failed ones. Each row becomes one Movement:
  • The amount is what entered or left the wallet. For a top-up by card, that excludes the card’s fee.
  • The description is the row’s type, payment method code, and reference, joined with ·, for example TOP_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.
Erwin only reads Ontop. It never pays, tops up, approves, signs, or changes anything on Ontop’s site.
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.
Entities with Books. When the Entity has at least one Book, GET /movements/:movementId/match-candidates requires a bookId query parameter, and every write that posts a Ledger Entry (POST /book, POST /match) must name the target Book on the entry header. See Books for the model and the exact rule.
Open in the app. This resource has a page in the Sintropix web app at https://app.sintropix.com/{entityId}/banks/{bankAccountId}?open={movementId}, where each id in braces is the same UUID as in the API route or response. Take bankAccountId from the Movement. The panel's Conciliación tab is where the user reconciles it. When you mention a record to a user, include that link. See Link to the App for the full URL grammar.

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 by side.

Order of movements

Movements are in date order: by movementDate, 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.
To list only the unbooked Movements that nobody has written about yet, add 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:
In the app, the same listing is at /{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 kind invoice that isn’t booked yet, so it has no line to settle. Book the Invoice first; it then appears as a line candidate. A credit note or a tax_only document is never an unbookedInvoice candidate. It carries kind, invoice ({ id, documentNumber, issueDate, total, currency, direction, documentKind, isExpenseRendition }), partner, and evidence.
Every candidate carries 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 the POST /movements body 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 the POST /movements/:movementId/book body shape plus movementId.
  • 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 the POST /movements/:movementId/match body shape plus movementId.
  • Response: array of { movement, bankAccount, reconciliation }, in input order.
Each element follows the same rules as the single-movement Match, including the Suspense line for the rest.
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 an asset 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_ERROR when 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 lineId to 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 lineId to null and send plain lines on any account, for example other income or a bank fee.
The Suspense line shrinks by the net amount the new lines take. It goes away when nothing is left, and the Movement then becomes reconciled. Body Response: { movement, bankAccount, reconciliation }, the same shape as POST /match.
Assign is not idempotent. If a request may have succeeded, read GET /movements/:movementId/reconciliation before you retry it.

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 answers 409 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 with POST /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.
  1. 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.
  2. 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 in lineIds. Use this for cross-currency transfers.
    • Book only the first leg, then call POST /movements/:movementId/match on the second Movement with lineId set to the open bridge line. The settlement line and the bridge line must have the same currency.
Give both bridge lines the same functional amount. Sintropix does not refuse unequal bridge lines. The Allocation takes the smaller amount, and the difference stays as an Open Amount on the bridge account. If only one leg has arrived, book it and leave its bridge line open until the other leg arrives.

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 no status filter still lists it, and the app crosses those rows out. It’s left out of status=notExcluded, of status=unreconciled, and of unreconciledCount.
  • 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 409 and BANK_MOVEMENT_EXCLUDED_ERROR.
  • Stays landed. A bank feed sync skips it.
A booked or matched Movement can’t be excluded: unbook it first. Anyone who can book a Movement can exclude or include it. A 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.

Status codes