Syncing transactions automatically into your accounting software or data warehouse eliminates manual reconciliation and gives your finance team an always-current view of cash flow. In this guide, we will retrieve all bank accounts, fetch transactions updated since the last sync across each account, and download any associated attachments, all without manual exports.
Example: An ERP integration pulls completed and pending transactions from Qonto every 15 minutes and automatically reconciles them against open purchase orders.
Prerequisites:
- At least one active bank account in Qonto.
- Scopes:
organization.read, attachment.read.
Consider using webhooks instead of polling. The transactions webhook notifies your server in real-time whenever a transaction is created or updated, no need to poll our API on a schedule. This reduces latency and API usage, and ensures your system reacts immediately to changes.
Retrieve your bank accounts
Fetch the list of bank accounts to get their IDs. You can either retrieve the organization object (which includes accounts) or list accounts directly.Endpoint: List business accountsOAuth scope required: organization.read
import requests
BASE_URL = "https://thirdparty.qonto.com/v2"
headers = {"Authorization": "Bearer {your_access_token}"}
response = requests.get(f"{BASE_URL}/bank_accounts", headers=headers, params={"per_page": 100})
response.raise_for_status()
bank_accounts = response.json()["bank_accounts"]
print(f"Found {len(bank_accounts)} bank account(s)")
const BASE_URL = "https://thirdparty.qonto.com/v2";
const headers = { Authorization: "Bearer {your_access_token}" };
const response = await fetch(`${BASE_URL}/bank_accounts?per_page=100`, { headers });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { bank_accounts: bankAccounts } = await response.json();
console.log(`Found ${bankAccounts.length} bank account(s)`);
Fetch transactions for each account
For each bank account, list transactions updated since your last sync. Include includes[]=attachments to retrieve attachment metadata in the same request and avoid extra API calls.Endpoint: List transactionsOAuth scope required: organization.read
By default, only completed transactions are returned. If you need pending or declined transactions, you must explicitly include them using the status[] parameter.
from datetime import datetime, timezone
last_sync_at = "2026-01-01T00:00:00Z" # Load from your database
current_sync_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
all_transactions = []
for account in bank_accounts:
page = 1
while True:
response = requests.get(
f"{BASE_URL}/transactions",
headers=headers,
params={
"bank_account_id": account["id"],
"updated_at_from": last_sync_at,
"updated_at_to": current_sync_at,
"status[]": ["completed", "pending", "declined"],
"includes[]": "attachments",
"per_page": 100,
"page": page,
},
)
response.raise_for_status()
data = response.json()
all_transactions.extend(data["transactions"])
if data["meta"]["next_page"] is None:
break
page = data["meta"]["next_page"]
print(f"Fetched {len(all_transactions)} transaction(s) across all accounts")
const lastSyncAt = "2026-01-01T00:00:00Z"; // Load from your database
const currentSyncAt = new Date().toISOString();
const allTransactions = [];
for (const account of bankAccounts) {
let page = 1;
while (true) {
const params = new URLSearchParams({
bank_account_id: account.id,
updated_at_from: lastSyncAt,
updated_at_to: currentSyncAt,
per_page: 100,
page,
});
params.append("status[]", "completed");
params.append("status[]", "pending");
params.append("status[]", "declined");
params.append("includes[]", "attachments");
const response = await fetch(`${BASE_URL}/transactions?${params}`, { headers });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
allTransactions.push(...data.transactions);
if (data.meta.next_page === null) break;
page = data.meta.next_page;
}
}
console.log(`Fetched ${allTransactions.length} transaction(s) across all accounts`);
Download transaction attachments
When includes[]=attachments is used, each transaction includes an attachments array with pre-signed download URLs. Download them immediately, the URLs expire after 30 minutes.Endpoint: Retrieve an attachmentOAuth scope required: attachment.read
Do not store the attachment URLs for later use. If you need to re-download a file, call GET /v2/attachments/{id} to get a fresh URL. import os
for transaction in all_transactions:
for attachment in transaction.get("attachments", []):
file_response = requests.get(attachment["url"])
file_response.raise_for_status()
os.makedirs("./transaction_attachments", exist_ok=True)
with open(f"./transaction_attachments/{attachment['file_name']}", "wb") as f:
f.write(file_response.content)
print(f"Downloaded: {attachment['file_name']}")
import { writeFile, mkdir } from "node:fs/promises";
for (const transaction of allTransactions) {
for (const attachment of transaction.attachments ?? []) {
const fileResponse = await fetch(attachment.url);
await mkdir("./transaction_attachments", { recursive: true });
const buffer = Buffer.from(await fileResponse.arrayBuffer());
await writeFile(`./transaction_attachments/${attachment.file_name}`, buffer);
console.log(`Downloaded: ${attachment.file_name}`);
}
}
Store the sync timestamp
Persist the sync timestamp only after all transactions across all accounts have been successfully processed.You can further filter transactions by side (credit or debit), operation_type[] (card, transfer, income, etc.), or settled_at_from / settled_at_to for settlement-date-based filtering.
save_to_db("last_transaction_sync_at", current_sync_at) # Use your own tools here
print(f"Sync complete. Next run will start from: {current_sync_at}")
await saveToDB("last_transaction_sync_at", currentSyncAt); // Use your own tools here
console.log(`Sync complete. Next run will start from: ${currentSyncAt}`);
Example output:
[Step 1] GET /bank_accounts
Found 6 bank account(s)
[Step 2] GET /transactions (paginated, all accounts)
Fetched 2 transaction(s) across 6 accounts
[Step 3] Download transaction attachments
Downloaded: Invoice-1.pdf
Downloaded: Invoice-2.pdf
[Step 4] Store sync timestamp
Sync complete. Next run will start from: 2026-03-19T21:11:02.942Z
(In production: persist currentSyncAt to your database here)
We now have an up-to-date local view of all transactions across all bank accounts, with their attachments downloaded.