Klantly Developers

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
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
curl "https://app.klantly.com/api/v1/invoices/7a1d9e42-5c3b-4f6a-9d8e-2c4b6a8d0e13/pdf" \
  -H "Authorization: Bearer $KLANTLY_API_KEY" \
  -o FAC-2026-00042.pdf

La 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
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 partial jusqu'à réception du montant complet ; ensuite elle passe à paid et vous recevez invoice.paid.
  • Le montant ne peut jamais dépasser amount_due. S'il le dépasse, vous obtenez 422 sur amount.

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
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
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

PHP
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;
}
Node.js
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

  • 404 avec not_found : la facture n'existe pas (ou plus), par exemple un brouillon supprimé.
  • 409 avec invalid_state_transition : vous enregistrez un paiement sur une facture annulée, ou vous transformez un devis qui n'a pas été accepté.
  • 422 avec validation_failed : par exemple un paiement supérieur à amount_due. errors indique par champ ce qui ne va pas.
  • 429 avec rate_limited : attendez le nombre de secondes indiqué dans Retry-After et réessayez. Voir Limites de débit.

Dernière mise à jour le 17 septembre 2026