Skip to main content
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.
1

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 accounts
OAuth 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)")
2

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 transactions
OAuth 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")
3

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 attachment
OAuth 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']}")
4

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}")
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.