Automatically pulling supplier invoices into your system removes the need for manual data entry and gives your finance team real-time visibility into what is owed and when. In this guide, we will fetch all supplier invoices updated since the last sync, paginate through results, and download their attachments; using the same polling pattern as client invoices, with different endpoints and filters.
Example: A spend management tool syncs supplier invoices from Qonto nightly to match them against purchase orders and flag any discrepancies before payment.
Prerequisites:
- At least one supplier invoice exists in Qonto.
- Scopes:
supplier_invoice.read, attachment.read
Initialize the sync timestamp
Same approach as client invoices: load the last successful sync timestamp from your database and capture the current time.from datetime import datetime, timezone
BASE_URL = "https://thirdparty.qonto.com/v2"
headers = {"Authorization": "Bearer {your_access_token}"}
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")
const BASE_URL = "https://thirdparty.qonto.com/v2";
const headers = { Authorization: "Bearer {your_access_token}" };
const lastSyncAt = "2026-01-01T00:00:00Z"; // Load from your database
const currentSyncAt = new Date().toISOString();
Fetch all supplier invoices updated in the time window
List supplier invoices updated between your last sync and now, sorted chronologically and paginated.Endpoint: List supplier invoicesOAuth scope required: supplier_invoice.read.
Additional filters are available: filter[status] (e.g., to_review, to_pay, paid), filter[due_date], and filter[created_at_from] / filter[created_at_to].
def fetch_all_supplier_invoices(last_sync_at, current_sync_at):
invoices = []
page = 1
while True:
response = requests.get(
f"{BASE_URL}/supplier_invoices",
headers=headers,
params={
"filter[updated_at_from]": last_sync_at,
"filter[updated_at_to]": current_sync_at,
"sort_by": "updated_at:asc", # Process chronologically
"per_page": 100,
"page": page,
},
)
response.raise_for_status()
data = response.json()
invoices.extend(data["supplier_invoices"])
if data["meta"]["next_page"] is None:
break
page = data["meta"]["next_page"]
return invoices
invoices = fetch_all_supplier_invoices(last_sync_at, current_sync_at)
print(f"Fetched {len(invoices)} supplier invoices")
async function fetchAllSupplierInvoices(lastSyncAt, currentSyncAt) {
const invoices = [];
let page = 1;
while (true) {
const params = new URLSearchParams({
"filter[updated_at_from]": lastSyncAt,
"filter[updated_at_to]": currentSyncAt,
sort_by: "updated_at:asc", // Process chronologically
per_page: 100,
page,
});
const response = await fetch(`${BASE_URL}/supplier_invoices?${params}`, { headers });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
invoices.push(...data.supplier_invoices);
if (data.meta.next_page === null) break;
page = data.meta.next_page;
}
return invoices;
}
const invoices = await fetchAllSupplierInvoices(lastSyncAt, currentSyncAt);
console.log(`Fetched ${invoices.length} supplier invoices`);
Retrieve and download attachments
For each invoice with an attachment_id, fetch the attachment and download the file. The attachment URL expires in 30 minutes — download immediately.Endpoint: Retrieve an attachmentOAuth scope required: attachment.read.
import os
for invoice in invoices:
if not invoice.get("attachment_id"):
continue
attachment_response = requests.get(
f"{BASE_URL}/attachments/{invoice['attachment_id']}",
headers=headers,
)
attachment_response.raise_for_status()
attachment = attachment_response.json()["attachment"]
file_response = requests.get(attachment["url"])
file_response.raise_for_status()
os.makedirs("./supplier_invoices", exist_ok=True)
with open(f"./supplier_invoices/{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 invoice of invoices) {
if (!invoice.attachment_id) continue;
const attachmentResponse = await fetch(
`${BASE_URL}/attachments/${invoice.attachment_id}`,
{ headers }
);
if (!attachmentResponse.ok) throw new Error(`HTTP ${attachmentResponse.status}`);
const { attachment } = await attachmentResponse.json();
const fileResponse = await fetch(attachment.url);
await mkdir("./supplier_invoices", { recursive: true });
const buffer = Buffer.from(await fileResponse.arrayBuffer());
await writeFile(`./supplier_invoices/${attachment.file_name}`, buffer);
console.log(`Downloaded: ${attachment.file_name}`);
}
Store the sync timestamp
Persist the new sync timestamp only after full success.save_to_db("last_supplier_invoice_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_supplier_invoice_sync_at", currentSyncAt); // Use your own tools here
console.log(`Sync complete. Next run will start from: ${currentSyncAt}`);
Example output:
[Step 1] Initialize sync timestamps
lastSyncAt: 2026-03-18T00:00:00Z
currentSyncAt: 2026-03-19T16:50:12.227Z
[Step 2] GET /supplier_invoices (paginated)
Fetched 2 supplier invoices
[Step 3] Download attachments
Downloaded: supplier_invoice_1.pdf
Downloaded: supplier_invoice_2.pdf
[Step 4] Store sync timestamp
Sync complete. Next run will start from: 2026-03-19T16:50:12.22
We now have an up-to-date local copy of all supplier invoices and their attachments for the given time window.