FinBaze for Platforms

Guides

A data source is one of your feeds into FinBaze: a bank feed, a card or payout feed, a sales or purchase feed, an hours feed or custom records. You register it once, attach it to companies, and deliver. FinBaze tells you whether each account is healthy.

Register and attach#

mutation {
  registerDataSource(input: {
    key: "acme-bank"
    type: bank_feed
    name: "Acme Bank"
    cadence: daily
    expectedBy: "07:00"
    gapThresholdMinutes: 1500
    historyMonths: 12
    balanceAssertions: true
  }) { id key status cadence gapThresholdMinutes }
}

cadence is realtime, hourly, daily or business_daily; with daily and business_daily, expectedBy (HH:MM, Amsterdam time) is when the day's data should have arrived. Together with gapThresholdMinutes it decides when an account counts as delayed or in a gap.

mutation Attach($company: CompanyRef!) {
  attachDataSource(key: "acme-bank", company: $company, backfillFrom: "2025-04-01") { id status backfillStatus }
}

Use autoAttach: true on the source to attach every new company automatically. pauseDataSourceAttachment and detachDataSource stop deliveries for one company. Attaching with backfillFrom tells FinBaze how far back you will deliver history.

Deliver#

CallWhat it does
upsertFeedBankAccountA bank, card or PSP account. An existing Lunch Flow account with the same IBAN is taken over with its history.
ingestBankTransactionsUp to 500 booked transactions per call. Result per line.
reportBankBalancesDaily balances (closing_booked, opening_booked, closing_available, interim_booked); FinBaze checks them against the transactions.
reportFeedWatermark"Everything up to completeThrough is delivered", also the heartbeat of a quiet account.
ingestPayoutTransactionsPayouts of a payout_feed onto a PSP account; each is paired with its bank credit (see E-commerce).
ingestSalesInvoices, ingestPurchaseInvoicesInvoices of a sales_feed or purchase_feed, same fields as upsertSalesInvoice / upsertPurchaseInvoice.
ingestWorkedHoursHours of an hours_feed.
mutation Balances($company: CompanyRef!) {
  reportBankBalances(source: "acme-bank", company: $company, account: "acc-77", balances: [
    { date: "2026-03-02", type: closing_booked, amountMinor: 1254300, currency: EUR }
  ]) { date status differenceMinor }
}
  • The result of ingestBankTransactions is one entry per line (line, status, error). A bad line fails alone.
  • Errors worth handling: PENDING_NOT_BOOKED (you sent pending), IMMUTABLE_TRANSACTION (a booked line changed: send a reversal), UNKNOWN_ACCOUNT, NOT_ATTACHED (the company is not attached to the source), SOURCE_INACTIVE, CURRENCY_MISMATCH.
  • The first closing balance after attaching sets the opening balance. Later balances are assertions.
  • Delivered bank rows, feed invoices and feed hours are read-only for the company's users (PARTNER_SOURCED_READ_ONLY); they can still process, split and book them.

Statement files#

If your core system exports files, not an API, upload them:

curl -X POST "https://sandbox.api.platform.finbaze.com/v2/platform/data-sources/acme-bank/files?format=camt053" \
  -H "authorization: Bearer $TOKEN" \
  -H "content-type: application/xml" \
  --data-binary @statement.xml

format is camt053, camt052 or mt940. The answer is 202 with the file id; parsing is in the background. One file can hold accounts of many companies: FinBaze routes by account IBAN to the attached companies, and lists accounts it could not route in unroutedAccounts. The same booking arriving as camt.052, camt.053 and MT940 is stored once. Follow a file with dataSourceFiles and the events data_source_file.processed and data_source_file.failed.

Over SFTP#

Register the source with delivery: { sftp: true } and ask FinBaze for an SFTP account for your platform. Put each file in the folder of its source, <source key>/ in your SFTP home. FinBaze picks a file up within a minute once it has stopped changing (upload under a .part name and rename it when done), imports it like an upload (receivedVia: sftp) and moves it to processed/<date>/. A file it cannot take moves to failed/, with the reason in <file>.error.txt.

Feed health#

query { feedHealth(status: [delayed, gap, balance_mismatch], first: 50) { nodes { externalId health statusSince lastDeliveryAt completeThrough } nextCursor } }
HealthMeaning
healthyA delivery or heartbeat arrived when expected and balances match.
delayedNothing arrived when expected, but the silence is shorter than gapThresholdMinutes: past expectedBy for daily feeds, more than 7.5 minutes (real-time) or 90 minutes (hourly) for the others.
gapSilence longer than gapThresholdMinutes, or a statement number is missing. Send the missing days, or a watermark if there was nothing to send.
balance_mismatchTransactions and the reported balance disagree; nothing in the books changed.
stoppedThe attachment or source is paused or retired.

A delivery or a watermark counts as a sign of life.

Events: feed.delayed, feed.gap_detected, feed.balance_mismatch, feed.recovered. Real-time and hourly feeds should send reportFeedWatermark at least once per gap threshold, otherwise a quiet account looks like a gap.

Try it in the sandbox#

A seeded company already has a simulated bank account with a feed scenario (steady, retail_cards, and so on) that advances with a test clock. To exercise your own feed, create a second company without a clock, register a source and push to it.

Pitfalls#

  • Sending the same data under a new externalId: it becomes a second transaction.
  • Not sending balances: the books cannot be checked, and balance_mismatch never fires.
  • Forgetting to attach: deliveries answer NOT_ATTACHED.