Factures vers votre comptabilité
Comptabilisez automatiquement les factures de Klantly dans votre logiciel comptable, avec le PDF, et renvoyez les paiements reçus en banque.
Vous créez et envoyez vos factures dans Klantly ; votre comptabilité se fait ailleurs. Avec l'API, vous comptabilisez chaque facture envoyée dans votre logiciel comptable, vous conservez le PDF avec l'écriture et vous enregistrez sur la facture dans Klantly les paiements reçus en banque.
Ce dont vous avez besoin
Une clé API avec ces scopes :
| Scope | Pour quoi faire |
|---|---|
invoices.read |
Récupérer les factures et leur PDF. |
webhooks.manage |
Facultatif : créer un webhook pour les événements de facture. |
invoices.write |
Facultatif : renvoyer les paiements reçus en banque, ou transformer un devis accepté en facture. |
invoices.send |
Facultatif : obtenir le lien de paiement pour que le client paie en ligne. |
La fonctionnalité Factures doit être active pour votre entreprise.
Étape 1 : savoir quand comptabiliser
Ne comptabilisez une facture qu'une fois envoyée. Un brouillon (draft) peut encore changer ou disparaître ; une facture envoyée est définitive.
Écoutez ces événements avec un webhook :
| Événement | Ce que vous faites |
|---|---|
invoice.sent |
Comptabiliser la facture comme facture de vente. Arrive aussi lors d'un nouvel envoi (un rappel) : comptabilisez par id, pas deux fois. |
invoice.paid |
La facture est entièrement payée. Marquez l'écriture comme soldée. |
invoice.cancelled |
La facture a été annulée. Passez un avoir dans votre comptabilité. |
Vous préférez interroger vous-même, par exemple toutes les heures ? Demandez alors les factures modifiées depuis votre passage précédent :
curl --globoff "https://app.klantly.com/api/v1/invoices?filter[updated_since]=2026-10-06T00:00:00Z&sort=updated_at&limit=100" \
-H "Authorization: Bearer $KLANTLY_API_KEY"Poursuivez avec next_cursor jusqu'à ce qu'il vaille null, voir Pagination. Retenez l'heure de début de votre passage, moins une minute de marge, et utilisez-la la fois suivante comme filter[updated_since]. Ignorez les brouillons et vérifiez status pour chaque facture.
Étape 2 : comptabiliser la facture
Tout ce qu'il faut pour l'écriture se trouve dans la facture :
| Champ | Contenu |
|---|---|
number |
Le numéro de facture, par exemple FAC-2026-00042. Utilisez-le comme référence de l'écriture. |
invoice_date, due_date |
Date de facture et date d'échéance (AAAA-MM-JJ). |
customer |
Nom, raison sociale, adresse, numéro de TVA et numéro d'entreprise tels qu'ils figurent sur la facture. |
items |
Les lignes, avec line_total (hors TVA), tax_rate et is_taxable. |
discount_amount |
Une remise sur toute la facture, en montant. |
subtotal, tax_amount, total |
Les totaux. On a toujours : subtotal − discount_amount + tax_amount = total. |
is_term_invoice, term_percentage |
Une facture d'acompte : une partie d'un devis. |
Les montants sont du texte avec deux décimales, par exemple "1305.79". Calculez avec des nombres décimaux, pas à virgule flottante, sinon vous aurez des écarts d'arrondi.
Comptabilisez par taux de TVA la somme des line_total des lignes à ce taux. Si la facture a un discount_amount, répartissez-le au prorata des taux ou comptabilisez-le sur une ligne à part. Vérifiez ensuite que votre écriture donne total.
Remarque
Une ligne avec unit_price_incl a été saisie en prix TTC. line_total_incl est alors le montant que le client paie pour cette ligne, au centime près. Pour l'écriture, vous utilisez toujours line_total et tax_rate.
Étape 3 : conserver le PDF avec l'écriture
Récupérez la facture telle que le client l'a reçue :
curl "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/pdf" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-o FAC-2026-00042.pdfLa réponse est le fichier lui-même. La première fois, Klantly génère le PDF, ce qui peut prendre quelques secondes ; ensuite il vient du cache. Cette requête compte dans la limite des actions lourdes : récupérez le PDF une fois et conservez-le.
Étape 4 : renvoyer les paiements
Quand un paiement arrive en banque, signalez-le à Klantly. Votre équipe voit alors que la facture est payée et Klantly n'envoie plus de rappels de paiement :
curl -X POST "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/payments" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bank-20261006-000318" \
-d '{
"amount": "1943.00",
"payment_method": "bank_transfer",
"reference": "BE68539007547034 / FAC-2026-00042"
}'- Mettez le numéro de transaction de la banque dans
Idempotency-Key: un paiement n'est alors jamais enregistré deux fois, même si vous répétez la requête. - Un paiement partiel est possible. La facture est alors
partialjusqu'à réception du montant complet ; ensuite elle passe àpaidet vous recevezinvoice.paid. - Le montant ne peut jamais dépasser
amount_due. S'il le dépasse, vous obtenez422suramount.
Si vous n'enregistrez les paiements que dans votre comptabilité, ignorez cette étape.
Laisser le client payer en ligne
Si votre entreprise a connecté Mollie et dispose de la fonctionnalité Paiements en ligne, le client paie une facture envoyée sur une page de paiement. Récupérez ce lien pour le partager vous-même, par exemple dans un rappel depuis votre comptabilité ou via WhatsApp :
curl "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/payment-link" \
-H "Authorization: Bearer $KLANTLY_API_KEY"La réponse contient url et amount_due. Si la facture a un échéancier, le client choisit la prochaine échéance sur cette page. Un paiement fait sur cette page est enregistré automatiquement sur la facture : vous recevez invoice.paid quand tout est réglé, et vous n'avez pas à le renvoyer avec l'étape 4.
Attention
Toute personne qui a le lien peut consulter et payer la facture. Ne le partagez qu'avec le client. C'est pourquoi il nécessite le scope invoices.send.
Si la facture est encore un brouillon, déjà payée ou annulée, vous obtenez 409 avec invalid_state_transition ; si Mollie n'est pas encore connecté, 409 avec conflict.
Du devis à la facture
Quand le client a accepté un devis, vous en faites un brouillon de facture en une seule fois. Client, lignes, remise et textes sont repris :
curl -X POST "https://app.klantly.com/api/v1/quotes/3b8e5f20-6a1c-4d9e-8b7a-5c4d3e2f1a09/invoice" \
-H "Authorization: Bearer $KLANTLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: devis-OF-26457-facture" \
-d '{"payment_term_days": 14}'Un devis a au plus une facture. Si vous redemandez, vous obtenez 409 avec le code conflict et l'id de la facture existante dans invoice_id. Envoyez-la ensuite avec POST /invoices/{id}/send.
Tout ensemble
use GuzzleHttp\Client;
/**
* Comptabilise les factures modifiées depuis $since et renvoie l'heure à utiliser pour le passage suivant.
* $book reçoit la facture et une fonction qui récupère le PDF : ne l'appelez que si la facture n'est pas
* encore comptabilisée. $markPaid et $credit mettent votre comptabilité à jour.
*/
function syncInvoices(Client $klantly, string $since, callable $book, callable $markPaid, callable $credit): string
{
$startedAt = gmdate('Y-m-d\TH:i:s\Z', time() - 60); // Une minute de marge pour les écarts d'horloge.
$cursor = null;
do {
$page = json_decode((string) $klantly->get('invoices', [
'query' => array_filter([
'filter' => ['updated_since' => $since],
'sort' => 'updated_at',
'limit' => 100,
'cursor' => $cursor,
]),
])->getBody(), true);
foreach ($page['data'] as $invoice) {
match ($invoice['status']) {
'draft' => null, // Pas encore à comptabiliser.
'cancelled' => $credit($invoice),
default => $book($invoice, fn () => downloadPdf($klantly, $invoice)), // Dédoublonnez sur $invoice['id'].
};
if ($invoice['status'] === 'paid') {
$markPaid($invoice);
}
}
$cursor = $page['meta']['next_cursor'];
} while ($cursor !== null);
return $startedAt;
}
function downloadPdf(Client $klantly, array $invoice): string
{
$path = sys_get_temp_dir() . '/' . $invoice['number'] . '.pdf';
$klantly->get("invoices/{$invoice['id']}/pdf", ['sink' => $path]);
return $path;
}import { writeFile } from 'node:fs/promises';
const BASE_URL = 'https://app.klantly.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.KLANTLY_API_KEY}` };
async function klantly(path) {
const response = await fetch(`${BASE_URL}/${path}`, { headers });
const json = await response.json();
if (!response.ok) throw new Error(json.detail ?? json.title);
return json;
}
async function downloadPdf(invoice) {
const response = await fetch(`${BASE_URL}/invoices/${invoice.id}/pdf`, { headers });
if (!response.ok) throw new Error(`PDF de ${invoice.number} non récupéré (${response.status})`);
const path = `/tmp/${invoice.number}.pdf`;
await writeFile(path, Buffer.from(await response.arrayBuffer()));
return path;
}
// Comptabilise les factures modifiées depuis `since` et renvoie l'heure à utiliser pour le passage suivant.
export async function syncInvoices(since, { book, markPaid, credit }) {
const startedAt = new Date(Date.now() - 60_000).toISOString(); // Une minute de marge pour les écarts d'horloge.
let cursor = null;
do {
const params = new URLSearchParams({ 'filter[updated_since]': since, sort: 'updated_at', limit: '100' });
if (cursor) params.set('cursor', cursor);
const page = await klantly(`invoices?${params}`);
for (const invoice of page.data) {
if (invoice.status === 'draft') continue; // Pas encore à comptabiliser.
if (invoice.status === 'cancelled') {
await credit(invoice);
continue;
}
// book reçoit une fonction qui récupère le PDF : ne l'appelez que si la facture n'est pas encore comptabilisée.
await book(invoice, () => downloadPdf(invoice)); // Dédoublonnez sur invoice.id.
if (invoice.status === 'paid') await markPaid(invoice);
}
cursor = page.meta.next_cursor;
} while (cursor);
return startedAt;
}Gérer les erreurs
404avecnot_found: la facture n'existe pas (ou plus), par exemple un brouillon supprimé.409avecinvalid_state_transition: vous enregistrez un paiement sur une facture annulée, ou vous transformez un devis qui n'a pas été accepté.422avecvalidation_failed: par exemple un paiement supérieur àamount_due.errorsindique par champ ce qui ne va pas.429avecrate_limited: attendez le nombre de secondes indiqué dansRetry-Afteret réessayez. Voir Limites de débit.
Dernière mise à jour le 17 septembre 2026