For the complete documentation index, see llms.txt. This page is also available as Markdown.

Banking

Deliver account transactions into the ERP and send payments out.

A banking app delivers account transactions into the ERP and optionally sends payments out. PayPal and file-based banking (CAMT/PAIN) are the reference implementations.

Read App Backends first; this page only covers what is specific to banking.

Registering the bank backend

const bankBackend = await methods.createBankBackend('PayPal', 'APP');

await methods.changeBankBackend(bankBackend.id, {
  ...bankBackend,
  description: 'Manages PayPal transactions, refunds and open-item matching.',
});

Two calls, because createBankBackend has no description parameter. changeBankBackend is a full PUT — spread the created object so label and version survive.

backendType is APP for apps. Other values exist for built-in providers.

You register a backend — the customer creates the bank

This is the key decision when you start:

Registering the backend makes your app selectable in the ERP's "create bank" dialog. The customer then creates their own bank — with the correct name, IBAN and BIC — and picks your backend.

Do not create banks on install. The customer owns that master data. The one exception is a provider where the "bank" is the provider itself and there is nothing for the customer to name — the PayPal app creates a single PayPal bank, one account per connected merchant.

Letting the ERP call you

The bank backend record carries three URL fields. Set them if the ERP should drive the interaction:

Field
Purpose

appTransactionUrl

Fetch transactions

appPaymentUrl

Send a payment or direct debit

appStatusUrl

Poll the status of a payment

If your app pulls on a schedule instead, you don't need them — use a cron webhook.

Delivering transactions

One POST per transaction. There is no bulk endpoint:

The fields that matter:

Field
Notes

accountId

The ERP bank account this belongs to

bookingDate, valueDate

Date-time

amount

Always positive — the sign lives in direction

direction

INCOMING or OUTGOING

currency

ISO code

endToEndId

Your stable identifier — this is what deduplication keys on

purpose, name, iban, accountNumber

Counterparty and reference data

matchingState and isPotentialDuplicate are set by the ERP — don't try to write them.

Balances are updated separately, and the value goes in the query string:

Matching against open items

You do not implement matching. Hand it to the ERP after importing:

Call it once per account after a batch, not per transaction. Only call it when something was actually imported.

Sending payments

Outbound is the mirror image: read payments the ERP has prepared, send them, then report back.

  • Only payments with status CREATED or PREPARED are pending.

  • Credit transfers are MONEY_TRANSFER / REALTIME_MONEY_TRANSFER; direct debits are SEPA_CORE_DIRECT_DEBIT / SEPA_B2B_DIRECT_DEBIT.

  • Confirm with POST /erp/bank/payments/{id}/update_status/SENT — and only after the bank or provider has actually accepted it.

What to watch out for

Deduplication is your responsibility. The ERP flags potential duplicates but does not reject them. Key on endToEndId. Two traps:

  • If you only check for existing ids within the date window you just queried, a transaction whose booking date falls outside that window will be imported again. Overlapping sync windows make this likely.

  • Transactions without an endToEndId can never be deduplicated. Derive a stable identifier for them or accept duplicates.

For file-based imports, layer the checks: hash of the whole file, then statement identity, then per-entry id. A file that failed to import must still be retryable — treat an import as "already done" only if it actually created rows.

Provider data lags. Reporting APIs often do not have the last few hours. Ending a sync window at "now" returns empty results or an error. End it deliberately in the past and track how far you have synced.

Windows have maximum lengths. Many providers cap a query at around a month. Split a long back-fill into chunks rather than requesting the whole range.

Watch the sync cursor. If a manual sync with an explicit date range writes the same "synced to" value the scheduled job reads, a user investigating an old transaction can move the cursor backwards or forwards and skip days. Keep the cursor separate from ad-hoc queries.

Dates as strings. If you compare dates lexicographically, never mix date-only (2026-01-15) and date-time (2026-01-15T10:30:00Z) values in the same field — the comparison silently breaks.

Amounts. Send the absolute value and set direction. Losing the direction flag silently inverts a booking. Keep two decimals and be careful with floating-point sums over large batches.

SEPA is EUR-only and its character set is restricted — transliterate umlauts and accents, and strip anything outside the permitted set before generating a file, or the bank rejects it. The SEPA creditor identifier (Gläubiger-ID) lives on the ERP bank record, not in your app configuration.

Credentials per connected account. A tenant may connect several merchants or accounts to one backend. Key credentials by account so they don't overwrite each other, and never log them.

Last updated

Was this helpful?