Use this file to discover all available pages before exploring further.
Generating invoices programmatically speeds up your billing cycle and ensures every invoice meets electronic invoicing standards without manual intervention. In this guide, we will find or create a client, draft and finalize an invoice, then poll until the Factur-X PDF (a standard PDF with embedded XML required for e-invoicing compliance) is ready to download.
Example: A SaaS platform automatically generates a Qonto invoice at the end of each billing period and sends the Factur-X PDF to the customer, with no manual step required.
Prerequisites:
E-invoicing must be enabled on your Qonto account.
This step is optional if you already know the IBAN of the account you want to use.
Python
Node.js
response = requests.get(f"{BASE_URL}/organization", headers=headers)response.raise_for_status()bank_accounts = response.json()["organization"]["bank_accounts"]# Use the first account, or filter by slug/currency as neededreceiving_iban = bank_accounts[0]["iban"]print(f"Receiving IBAN: {receiving_iban}")
const orgResponse = await fetch(`${BASE_URL}/organization`, { headers });if (!orgResponse.ok) throw new Error(`HTTP ${orgResponse.status}`);const { organization } = await orgResponse.json();// Use the first account, or filter by slug/currency as neededconst receivingIban = organization.bank_accounts[0].iban;console.log(`Receiving IBAN: ${receivingIban}`);
3
Create the invoice draft
Create the invoice in draft state. It will not yet be sent or assigned a number.Endpoint: Create a client invoice
Finalizing the invoice assigns it an official number, locks it for editing, and triggers the asynchronous generation of the Factur-X PDF.Endpoint: Finalize a client invoice
OAuth scope required:client_invoice.write
Python
Node.js
response = requests.post( f"{BASE_URL}/client_invoices/{invoice_id}/finalize", headers=headers,)response.raise_for_status()finalized_invoice = response.json()["client_invoice"]attachment_id = finalized_invoice.get("attachment_id") # May be None initiallyfinalized_at = finalized_invoice["finalized_at"]print(f"Invoice finalized at {finalized_at} — attachment: {attachment_id}")
The Factur-X PDF is generated asynchronously after finalization. The attachment_id may not be set immediately on the invoice — re-fetch the invoice until it appears, then poll GET /attachments/{id} until it returns 200. That is your signal to download.
Do not download the PDF until the attachment endpoint returns 200.
import timeMAX_ATTEMPTS = 15POLL_INTERVAL_SECONDS = 2attachment = Nonefor attempt in range(MAX_ATTEMPTS): # attachment_id may not be set immediately — re-fetch the invoice if needed if not attachment_id: invoice_response = requests.get( f"{BASE_URL}/client_invoices/{invoice_id}", headers=headers, ) invoice_response.raise_for_status() attachment_id = invoice_response.json()["client_invoice"].get("attachment_id") if not attachment_id: print(f"Attempt {attempt + 1}/{MAX_ATTEMPTS} — attachment_id not set yet, waiting...") time.sleep(POLL_INTERVAL_SECONDS) continue attachment_response = requests.get( f"{BASE_URL}/attachments/{attachment_id}", headers=headers, ) attachment_response.raise_for_status() attachment = attachment_response.json()["attachment"] print("Factur-X PDF is ready!") breakelse: raise TimeoutError("Factur-X PDF was not ready within the timeout period.")
const MAX_ATTEMPTS = 15;const POLL_INTERVAL_MS = 2000;let attachment;for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { // attachment_id may not be set immediately — re-fetch the invoice if needed if (!attachmentId) { const invoiceResponse = await fetch( `${BASE_URL}/client_invoices/${invoiceId}`, { headers } ); if (!invoiceResponse.ok) throw new Error(`HTTP ${invoiceResponse.status}`); const { client_invoice } = await invoiceResponse.json(); attachmentId = client_invoice.attachment_id ?? null; if (!attachmentId) { console.log(`Attempt ${attempt + 1}/${MAX_ATTEMPTS} — attachment_id not set yet, waiting...`); await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); continue; } } const attachmentResponse = await fetch( `${BASE_URL}/attachments/${attachmentId}`, { headers } ); if (!attachmentResponse.ok) throw new Error(`HTTP ${attachmentResponse.status}`); attachment = (await attachmentResponse.json()).attachment; console.log("Factur-X PDF is ready!"); break;}if (!attachment) { throw new Error("Factur-X PDF was not ready within the timeout period.");}
6
Download the Factur-X PDF
Download the file from the attachment URL. The URL is valid for 30 minutes, download immediately.
To verify the Factur-X XML is correctly embedded, open the PDF in Adobe Acrobat Reader and check for a paperclip icon, it should contain a factur-x.xml attachment.
See example PDF
Example output:
[Step 1] Find or create client Found existing client: b84a2798-xxxx-xxxx-xxxx-xxxxxxxxxxxx[Step 2] GET /organization Receiving IBAN: FR761695xxxxxxxxxxxxxxxxxxx[Step 3] POST /client_invoices (draft) Invoice draft created: 019d07ca-xxxx-xxxx-xxxx-xxxxxxxxxxxx[Step 4] POST /client_invoices/{id}/finalize Invoice finalized at 2026-03-19T20:30:01Z — attachment_id: null[Step 5] Poll GET /attachments/{id} until Factur-X is ready Attempt 1/15 — attachment_id not set yet, waiting... Attempt 2/15 - Factur-X PDF is ready![Step 6] Download Factur-X PDF Downloaded Factur-X PDF: ./client_invoices/2603-F-2026-0000291-Qonto-SA.pdf
We now have the finalized Factur-X PDF downloaded locally. It contains the embedded factur-x.xml file required for electronic invoicing compliance.