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#
| Call | What it does |
|---|---|
upsertFeedBankAccount | A bank, card or PSP account. An existing Lunch Flow account with the same IBAN is taken over with its history. |
ingestBankTransactions | Up to 500 booked transactions per call. Result per line. |
reportBankBalances | Daily 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. |
ingestPayoutTransactions | Payouts of a payout_feed onto a PSP account; each is paired with its bank credit (see E-commerce). |
ingestSalesInvoices, ingestPurchaseInvoices | Invoices of a sales_feed or purchase_feed, same fields as upsertSalesInvoice / upsertPurchaseInvoice. |
ingestWorkedHours | Hours 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
ingestBankTransactionsis one entry per line (line,status,error). A bad line fails alone. - Errors worth handling:
PENDING_NOT_BOOKED(you sentpending),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 } }
| Health | Meaning |
|---|---|
healthy | A delivery or heartbeat arrived when expected and balances match. |
delayed | Nothing 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. |
gap | Silence longer than gapThresholdMinutes, or a statement number is missing. Send the missing days, or a watermark if there was nothing to send. |
balance_mismatch | Transactions and the reported balance disagree; nothing in the books changed. |
stopped | The 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_mismatchnever fires. - Forgetting to attach: deliveries answer
NOT_ATTACHED.