There is a recurring Sunday evening ritual. You open Qonto, export the month's CSV, import it into your spreadsheet — or worse, copy each line manually. You review the labels, wonder whether that Free Mobile payment is "telecom" or "software subscription," hesitate on an unfamiliar vendor name, and eventually put it under "miscellaneous" because it is faster.
This ritual takes you anywhere from 30 minutes to an hour and a half every week. Multiplied by 52 weeks, that is 26 to 78 hours per year — hours spent copying and pasting and deciding where a transaction belongs. Work that neither your accountant nor your bank charges you for, but work that keeps you from running your business.
The good news is that this entire process is automatable. Not in the "you would need a full-time developer" sense. With three existing software components — Qonto, n8n, and a small AI model — you can turn this Sunday ritual into a workflow that runs itself, categorises 9 out of 10 transactions correctly, and requires nothing more than a 5-minute weekly review.
This article is not an overview. It is the assembly manual, piece by piece. If you follow it, you will have a production workflow running in your own infrastructure by the end of the weekend.
What the Qonto API actually returns
Before connecting anything, you need to know what a Qonto transaction looks like through the API. This data structure is what n8n will handle, and it determines what can — and cannot — be automated.
When you query GET /v2/transactions, the Qonto API returns an array of objects. Here is a real (anonymised) example with the fields that matter:
{
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"label": "SEPA DD SUBSCRIPTION FREE MOBILE",
"amount": 19.99,
"amount_cents": 1999,
"side": "debit",
"operation_type": "direct_debit",
"currency": "EUR",
"status": "completed",
"settled_at": "2026-07-10T08:32:00.000Z",
"emitted_at": "2026-07-08T06:00:00.000Z",
"updated_at": "2026-07-10T08:32:05.000Z",
"counterparty_name": "Free Mobile",
"counterparty_iban": null,
"reference": "Invoice 072026 123456789",
"vat_amount": null,
"vat_rate": null,
"cashflow_category": {
"id": "internet_telephone",
"name": "Internet and telephone"
},
"bank_account_id": "b0a1b2c3-d4e5-6789-abcd-ef1234567890",
"attachment_required": false,
"attachment_lost": false,
"card_last_digits": null,
"card_id": null
}
Let us break down the fields that matter for your workflow.
status — three possible values: pending (transaction in progress, not yet confirmed by the bank), completed (final movement), declined (rejected). A pending transaction from today will be completed tomorrow or the day after. If your workflow only filters for completed, you may miss the status change. The rule: load all transactions, but only assign the final category once status reaches completed.
amount_cents — the amount in cents. The API returns both amount (decimal) and amount_cents (integer). Use amount_cents for all comparisons and deduplication. Floating-point rounding is not an issue with integers.
side — credit (money received) or debit (money spent). This is more reliable than the amount sign for determining flow direction.
updated_at — the last modification date. This is the field to use for polling. A transaction created today will have an updated_at matching its creation date. If you recategorise it in Qonto next week, updated_at will be updated. Your workflow will pick up the change automatically.
cashflow_category — the category Qonto assigned automatically. Watch out: Qonto categorises through internal rules that are neither documented nor configurable. A payment to OVH often goes into hosting when you want software_subscriptions. Do not trust this default category — the AI will do better.
counterparty_name — the beneficiary name. This is the most useful field for classification. Free Mobile, OVH, SNCF, EDF, Bouygues Telecom — names are distinctive enough for an AI to recognise reliably.
vat_amount and vat_rate — almost always null on debit transactions. Qonto does not decode VAT from bank entries. If you need pretax/after-tax amounts, you must derive them from the label or store them elsewhere. This is a point your accounting process will need to handle separately.
Configuring Qonto API access in n8n
Concretely, for n8n to talk to Qonto, you have two options.
Option 1: the native Qonto node (recommended to start)
The native node ships with n8n. No installation, no extra dependency. Create a Qonto credential in n8n:
- Go to Credentials > New > Qonto in the n8n interface
- Fill in the API Key field with your Qonto key in
login:secretformat (login = organisation slug, secret = key generated in Settings > Developer > API Keys) - Test the connection
The node exposes these operations: Organisation (get), Bank Account (list, get), Transaction (list, get), Attachment (list, upload, download). It handles pagination automatically — when you request transactions, it iterates pages until the last one.
Native node limitation: it does not support advanced filters. You can pass status and iban parameters, but not updated_at_from. For our workflow, this is an issue — we want to fetch only transactions modified since the last run.
Option 2: the HTTP Request node (recommended for this workflow)
The HTTP Request node gives you full control. Here is the configuration:
| Field | Value |
|---|---|
| Method | GET |
| URL | https://thirdparty.qonto.com/v2/transactions |
| Authentication | Header Auth |
| Header Name | Authorization |
| Header Value | Your key in login:secret format |
| Query Parameters | See below |
The query parameters to pass:
| Parameter | Value | Purpose |
|---|---|---|
status |
completed |
Confirmed transactions only |
updated_at_from |
{{$json.lastRun}} |
Last checkpoint date (stored variable) |
per_page |
100 |
Maximum allowed per page |
page |
1 |
Current page (iterate in loop) |
includes[] |
attachments |
Optional: fetch linked receipts |
The HTTP Request node returns raw JSON. You will need a Code or Set node to extract meta.total_pages and loop through remaining pages if volume is significant.
For an SMB with 100 monthly transactions, a single page suffices. The pagination loop is only necessary for the initial import — the first run where you pull 6 to 24 months of history.
The classification prompt: the core of the workflow
AI transaction classification is not magic. It is a well-written prompt, a constrained output format, and a category taxonomy tailored to your business. Here is the prompt I use in my own automations.
You are an accounting transaction classifier for a French SMB.
You receive bank transactions and must categorise them into one
of the categories listed below.
Strict rules:
- Reply ONLY with the category name, nothing else.
- If you hesitate between two categories, pick the most likely one.
- Never answer "I don't know". Always choose the best option.
- The confidence score must be an integer between 0 and 100.
Available categories:
- suppliers: inventory, raw materials, subcontracting
- software_subscriptions: SaaS, licenses, hosting (AWS, OVH, Figma, Notion, Slack)
- travel_expenses: transport, tolls, fuel, parking (SNCF, Uber, Total)
- telecom: phone, internet, mobile (Free, Orange, SFR, Bouygues)
- rent_and_utilities: rent, electricity, water, insurance (EDF, Veolia, property)
- bank_fees: overdraft charges, commissions, account fees
- taxes: VAT, corporate tax, social security (URSSAF, DGFiP)
- dining: restaurants, catering, groceries
- payroll: salaries, employer contributions, health insurance
- client_revenue: client payments, wire transfers, cheque deposits
- internal_transfers: transfers between accounts, expense reimbursements
- miscellaneous: anything that does not fit above
Transaction:
Label: "SEPA DD SUBSCRIPTION FREE MOBILE"
Counterparty: "Free Mobile"
Amount: -19.99 EUR
Date: 2026-07-10
Reply ONLY in the following JSON format:
{"category": "category_name", "confidence": 95}
The model's output for this example will be:
{"category": "telecom", "confidence": 98}
Why 98? Free Mobile is a known telecom operator, the subscription is monthly, and the amount is exactly a standard Free Mobile plan at €19.99. The model recognises this pattern without ambiguity.
Conversely, a transaction like:
Label: "SEPA TRANSFER"
Counterparty: null
Amount: -1,240.00 EUR
Date: 2026-07-08
The prompt will likely return:
{"category": "miscellaneous", "confidence": 40}
This is exactly the behaviour you want: rather than guessing randomly, the model admits its uncertainty. This transaction will go to Level 3 routing (manual classification required).
The cost of these API calls: Mistral Small billed at €0.20/million tokens. Each transaction consumes roughly 400 tokens (prompt + response). For 1,000 transactions: €0.08. For 100 monthly transactions: €0.008 per month. Less than the price of a stamp.
Handling duplicates: the exact code
If your workflow runs daily, it will inevitably encounter already-processed transactions. Without deduplication, every transaction would be reclassified every day — and your spreadsheet would fill with duplicate rows.
The deduplication key is Qonto's transaction_id (a v4 UUID, stable over time). Here is the JavaScript for a n8n Code node, placed right after the transaction fetch:
// Configuration: storage key name
const storageKey = 'qonto_processed_transactions';
// Retrieve already-processed IDs (stored in n8n static data)
const processedIds = new Set(
($getWorkflowStaticData('global')[storageKey] || [])
);
// Filter for new or modified transactions
const newTransactions = $input.all().filter(item => {
const txId = item.json.transaction_id;
// Keep if never seen
if (!processedIds.has(txId)) {
processedIds.add(txId);
return true;
}
return false;
});
// Save the updated list
$getWorkflowStaticData('global')[storageKey] = Array.from(processedIds);
// Limit size: keep the last 10,000 IDs
if ($getWorkflowStaticData('global')[storageKey].length > 10000) {
$getWorkflowStaticData('global')[storageKey] =
$getWorkflowStaticData('global')[storageKey].slice(-5000);
}
return newTransactions;
This code does three things:
- Stores seen
transaction_idvalues in n8n's static data (persisted between runs, even after server restart) - Filters out already-processed transactions
- Cleans up history automatically beyond 10,000 IDs to prevent memory drift
Limitation: this approach does not handle modifications. If a transaction_id was already processed but the transaction was recategorised in Qonto (its updated_at changed), the workflow will not pick it up again. To handle this case you can either:
- Use
(transaction_id + updated_at)as the deduplication key - Or systematically reimport the last 30 days, using upsert rather than append
The second option is more reliable. In that case, the Code node does not filter, and the write node uses an upsert operation (or your database's equivalent UPDATE) rather than a simple append.
Case study: a 12-person agency
Let us use a real-world example so the numbers speak for themselves.
Duval & Associés is a web agency (12 employees, €1.8 million revenue) using Qonto as their business bank. They process an average of 150 transactions per month: 80 client payments, 50 supplier invoices, 15 SaaS subscriptions, 5 bank fees.
Before automation:
- The director spends 1 hour 15 minutes every Sunday evening reconciling accounts
- The accounting assistant spends 3 hours mid-month categorising receipts
- 2-3 categorisation errors occur monthly (found during the accountant's quarterly review, billed as extra time)
- Cash decisions are made on data that is 5-8 days old
After the workflow goes live:
| Metric | Before | After | Gain |
|---|---|---|---|
| Director time/month | 5 h | 20 min | 4 h 40 |
| Assistant time/month | 6 h | 1 h | 5 h |
| Residual errors/month | 2-3 | 0-1 | -75 % |
| Accountant correction fees | ~€200/quarter | ~€40/quarter | €160/qtr |
| Cash visibility | Data J-5 | Data J | Real-time |
Breakdown by workflow level:
Out of 150 monthly transactions:
- Level 1 (auto): 115 transactions. All 80 client receipts (clean labels: "SEPA Wire Client X Invoice Y"), 15 recurring SaaS subscriptions (AWS, Figma, Notion, Slack, HubSpot), and 20 recurring suppliers (insurance, accountant, hosting). Average confidence score: 96 %.
- Level 2 (review): 25 transactions. Mostly one-off suppliers (contractor met at a conference, unexpected equipment purchase). Average confidence score: 87 %. The assistant reviews them in 7 minutes on Monday morning.
- Level 3 (manual): 10 transactions. Ambiguous labels ("SEPA Wire" with no named counterparty, a pending expense reimbursement). The director decides in 3 minutes.
The workflow has been running for 14 months at Duval & Associés. Out of 2,100 transactions processed, 3 errors slipped through the automatic level — all detected and corrected by the assistant during Level 2 within the following week. No real accounting consequences.
Pitfalls the docs will not tell you
The Qonto API and n8n work well together. But there are details the documentation does not cover, which I discovered by doing — not reading.
Pitfall #1: the page parameter starts at zero
The Qonto docs state pagination starts at page: 1. This is true. But if you omit the page parameter, the API interprets 0 by default — and page zero returns an empty successful response. Your n8n node does not error, it simply returns zero transactions. You will wonder why your workflow runs without doing anything. Always include page: 1 explicitly.
Pitfall #2: pending transactions double on arrival
A pending transaction emitted today has a specific transaction_id. When it becomes completed tomorrow, the API does not create a new transaction — it updates the same one. The transaction_id remains identical. Your deduplication key works. But updated_at has changed, so if you filter by updated_at_from, the transaction will be fetched a second time.
The fix: in your deduplication node, do not just check if transaction_id exists. Also check if the final completed status has already been processed. Store {transaction_id, status, updated_at} rather than just the ID.
Pitfall #3: Qonto's cash flow categories are unreliable
Qonto assigns a cashflow_category automatically to every transaction. The trap is that these categories are based on basic keyword heuristics, not intelligent analysis. The result: a payment to OVH for a dedicated server may be classed as hosting one day and software the next. You cannot rely on them for your accounting.
This is precisely why the workflow uses AI rather than Qonto's native category — a custom classification based on your own taxonomy will be more reliable than a black box whose rules you cannot control.
Pitfall #4: the API rate limit is fine... except for the initial import
The first run of the workflow will replay several months of history. If you import 12 months at ~150 transactions per month, that is 1,800 transactions. At 30 API requests per minute with pages of 100, you need... one request. The real concern is the number of pages: if you genuinely have 18 pages, the 5 req/s burst will swallow them in 4 seconds. No problem.
However, the 1,800 Mistral API calls that follow — at 10 req/s max — will take about 3 minutes. That is negligible execution time, but set your n8n workflow timeout to 10 minutes for the first run.
Pitfall #5: the Authorization header is not in Base64
Unlike many APIs that expect Authorization: Basic base64(login:secret), Qonto expects the login:secret in plain text, without encoding. If you use the HTTP Request node with a Basic Auth credential, n8n will automatically encode to Base64 and authentication will fail.
Solution: use Header Auth with X-User-Api-Token as the header name and your key as the value. Or, with the native Qonto node, let n8n handle it — it does things correctly under the hood.
What it actually costs
Let us walk through the real costs, because that is what every business owner wants to know before starting.
| Item | Cost |
|---|---|
| Qonto API | Included in subscription (free) |
| n8n self-hosted (Hetzner CX22) | €5.99/month |
| AI calls (Mistral Small) | ~€0.01/month for 100 transactions |
| Setup time | 2 to 3 hours (one-off) |
| Maintenance | ~15 min/month to verify everything runs |
For an SMB with 100 monthly transactions, the monthly cost is approximately €6 (server alone — the AI costs literal cents). Setup time is paid back by the end of the first month.
Compared to the €200-500 per month that manual entry costs in director time (excluding errors and accountant corrections), the ratio is 1 to 50. That kind of ratio does not need a business case.
Three-level routing: the method that prevents disasters
Instead of accepting or rejecting the AI classification wholesale, set up a three-tier confidence system.
Level 1 — High confidence (> 95 %): automatic Transactions the AI is very sure about go straight into your accounting tracking without human review. Typical examples: recurring subscriptions (Free Mobile, OVH, AWS, Netflix Pro), rent payments, taxes. These have stable labels, known counterparties and predictable amounts.
Level 2 — Moderate confidence (80-95 %): quick scan These transactions are grouped into a "review" sheet in your spreadsheet. Once a week, you spend 5 minutes on them. In most cases, you will validate the AI's choice — but the human glance prevents absurdities.
Level 3 — Low confidence (< 80 %): manual only These are anomalies: incomplete labels, unknown counterparties, unusual amounts. They require a human decision. The AI has identified that it is not sure — precisely the behaviour you want.
This three-level system is what I use in my own automations. It does not aim to eliminate all human intervention — it reduces the mental load from 5 to 10 hours per month down to a structured 10-minute weekly review. And it guarantees that no transaction ends up in "miscellaneous" because you could not be bothered.
Automating starts with deciding what to automate
The technology is the easy part. The Qonto API is documented. n8n is a mature product. AI models cost pennies. The deduplication code fits in 15 lines. What makes the difference between a project that stays in draft and a workflow running reliably for 14 months is the upfront decision: which transactions to run fully automatic, which to monitor weekly, which to keep under human control.
Duval & Associés did not automate their entire accounting on day one. They started with the 80 client receipts — the easiest, most predictable transactions. Then SaaS subscriptions. Then recurring suppliers. Each month, they shifted more volume from Level 3 to Level 2, from Level 2 to Level 1. After six months, the workflow was running on autopilot for 115 of the 150 monthly transactions, and the director had recovered the equivalent of two weeks of work per year.
If you want to set up this kind of automation without spending your evenings figuring out why the page parameter does not work on the first call, I can help design the workflow, define your category taxonomy and install the necessary guardrails. This is exactly the kind of project I build — from scratch or as an integration with your existing stack.
Also available: Read in French