Aller au contenu

Factures

L'objet facture, les montants calculés et les huit appels qui permettent de le gérer.

Une facture est un document que vous émettez et que votre client règle sur une page hébergée par Wajub. Cette page sert de référence pour l'objet facture et ses appels ; Facturation et abonnements présente le cycle.

La facturation est un produit live dont l'accès dépend de votre plan. Une clé sandbox renvoie 403 This feature is only available in live mode.. Un plan sans cette fonctionnalité renvoie 403 Invoicing is not available on your plan.. Une fois le plafond total d'un plan atteint, la réponse est 403 You have reached the maximum number of invoices for your plan.. Seuls les documents de type invoice comptent dans ce plafond.

Les huit appels

Voici toutes les opérations proposées par l'API pour les factures. /send est l'appel qui envoie une facture par e-mail, son PDF est un lien public (plus bas), et aucun endpoint ne débite un client.

AppelFonction
GET /invoicesLister, filtrer et trier vos factures
POST /invoicesCréer une facture, toujours avec draft
GET /invoices/{id}Récupérer une facture
PUT /invoices/{id}Remplacer une facture tant qu'elle est un brouillon
DELETE /invoices/{id}Supprimer une facture tant qu'elle est un brouillon
POST /invoices/{id}/sendLa numéroter, l'envoyer par e-mail avec son PDF et ouvrir sa page publique
POST /invoices/{id}/mark-paidEnregistrer un paiement encaissé ailleurs
POST /invoices/{id}/cancelFermer une facture

{id} correspond à l'id renvoyé par l'API, soit la chaîne inv_.

L'objet facture

Tous les appels renvoient cet objet. items est présent chaque fois que la facture est chargée avec ses lignes, ce qui est le cas pour les huit appels.

Réponse · une facture avec une ligne taxée
{
"id": "inv_LRQqYvlhrgUOE225KMKU",
"invoice_number": "INV-2026-0001",
"document_type": "invoice",
"status": "sent",
"customer": "cus_7Kq2mX9vL4tRb3nP8sZcWd1e",
"customer_name": "Amina Traoré",
"customer_email": "amina@example.com",
"customer_company_name": "Traoré & Fils",
"customer_phone": "+237670000000",
"customer_address": "BP 1204, Douala",
"invoice_date": "2026-09-12",
"due_date": "2026-10-12",
"currency": "XAF",
"language": "fr",
"subtotal": 300000,
"discount_amount": 0,
"has_global_discount": false,
"global_discount_type": null,
"global_discount_value": null,
"tax_amount": 57750,
"total": 357750,
"amount_paid": 0,
"amount_due": 357750,
"payment_type": "full",
"is_recurring": false,
"recurring_interval": null,
"next_invoice_date": null,
"recurring_parent": null,
"notes": null,
"terms": null,
"footer": null,
"accept": {
},
"sent_at": "2026-09-12T09:14:02+00:00",
"paid_at": null,
"cancelled_at": null,
"items": [
{
"id": "00000000-0000-0000-0000-000000000001",
"name": "Consulting service",
"description": null,
"quantity": 2,
"unit": null,
"unit_price": 150000,
"tax_rate": 19.25,
"tax_amount": 57750,
"discount_type": null,
"discount_value": 0,
"discount_amount": 0,
"subtotal": 300000,
"total": 357750,
"sort_order": 0
}
],
"created_at": "2026-09-12T09:10:44+00:00",
"updated_at": "2026-09-12T09:14:02+00:00"
}

invoice_number vaut null sur un brouillon et est attribué au premier envoi de la facture. Il vient de la numérotation de votre compte, la même séquence que celle du Dashboard, que vous envoyiez la facture ici ou là-bas : le préfixe (INV- si vous ne l'avez pas changé), l'année, puis un compteur à quatre chiffres, tels que réglés dans les paramètres de facturation du Dashboard. Le compteur repart à 0001 chaque année, sauf si vous l'avez désactivé, et un numéro est unique au sein de votre compte. status peut valoir draft, sent, viewed, partial, paid, overdue, cancelled ou refunded. Facturation et abonnements présente les transitions entre ces statuts et les webhooks émis.

customer désigne le client à qui la facture est adressée, par l'identifiant cus_ que renvoie l'API Clients, et vaut null quand vous l'avez adressée à un nom et un e-mail seulement. Rapprochez vos données sur customer.

language est la langue de l'e-mail et du PDF de la facture, fr ou en, enregistrée à la création de la facture, ici ou dans le Dashboard. accept est la restriction des moyens proposés au payeur sur la page de la facture, {} quand il n'y en a pas.

Deux champs sont calculés plutôt que stockés. amount_due vaut toujours total moins amount_paid, avec un minimum de zéro. Il ne devient donc jamais négatif en cas de trop-perçu. amount_paid ne change qu'avec mark-paid, un paiement effectué sur la page hébergée ou un avoir émis depuis le Dashboard, qui le diminue de son montant. Un paiement sur la page compte les trois : les paiements en ligne de la facture, plus ce que mark-paid a enregistré, moins ses avoirs. Une facture qui porte un avoir de 1 000 ne passe à paid qu'une fois 1 000 de plus que son total reçus.

Créer une facture

Les quatre champs obligatoires sont customer_name, items, invoice_date et currency. Tous les autres sont facultatifs. La facture est toujours créée avec draft, quelle que soit votre requête.

POSThttps://api.wajub.com/invoices
customer_namestringobligatoire
Le destinataire de la facture. Ce champ reste obligatoire même avec customer_id.
customer_idstringfacultatif
L'un de vos clients, par son identifiant cus_. Son nom, son e-mail, son entreprise, son téléphone et son adresse sont copiés sur la facture et remplacent les champs customer_* envoyés. Un client qui n'est pas le vôtre renvoie 422.
customer_emailstringfacultatif
L'adresse à laquelle /send envoie la facture et son PDF, et à laquelle partent ses [relances](/billing/invoice/reminders).
customer_company_namestringfacultatif
Imprimé sur le document à côté du nom.
customer_phonestringfacultatif
Imprimé sur le document.
customer_addressstringfacultatif
Imprimée sur le document.
itemsarrayobligatoire
Au moins une ligne. Consultez l'objet ligne ci-dessous.
invoice_datedateobligatoire
La date d'émission imprimée sur le document.
due_datedatefacultatif
La date de paiement attendue. Elle doit être égale ou postérieure à invoice_date.
payment_terms_daysintegerfacultatif
Définit due_date à ce nombre de jours après aujourd'hui, uniquement si due_date est absent. Le calcul part de la date du jour, pas de invoice_date.
currencystringobligatoire
Une devise que Wajub prend en charge : XAF, XOF, NGN et les autres de la [liste](/resources/coverage). Tout autre code renvoie 422.
languageenumfacultatif
fr ou en : la langue de l'e-mail envoyé par /send, des relances et du PDF. Par défaut, la langue préférée du client que désigne customer_id, sinon la langue de votre compte ([**Paiement — Langue**](https://dashboard.wajub.com/settings/regional/language)), sinon en. Toute autre valeur renvoie 422.
acceptobjectfacultatif
Restreint les moyens proposés au payeur sur la page de la facture, comme sur [POST /payments](/payments/payment-methods#choosing-what-a-payment-accepts).
is_recurringbooleanfacultatifdéfaut : false
Marque la facture comme modèle. Consultez [Factures récurrentes](/billing/invoice/recurring).
recurring_intervalenumfacultatif
daily, weekly, monthly, quarterly ou yearly.
payment_typeenumfacultatifdéfaut : full
full, split ou milestone. Les paiements en plusieurs fois sont décrits ci-dessous.
payment_schedulesarrayfacultatif
Les échéances lorsque payment_type ne vaut pas full.
notesstringfacultatif
Texte libre imprimé sur le document, limité à 5 000 caractères.
termsstringfacultatif
Conditions générales, limitées à 5 000 caractères.
footerstringfacultatif
Texte de pied de page, limité à 500 caractères.
template_idstring (uuid)facultatif
Un modèle de facture créé dans le Dashboard. Il détermine uniquement la mise en page imprimée.
document_typeenumfacultatifdéfaut : invoice
invoice, quote ou estimate. Un devis et une estimation restent des brouillons : voir [Devis et estimations](#quotes-and-estimates).
global_discount_typeenumfacultatif
percentage ou fixed : une remise sur toute la facture, envoyée avec global_discount_value. Voir [Une remise sur toute la facture](#an-invoice-wide-discount).
global_discount_valuenumberfacultatif
Le pourcentage, 100 au plus, ou le montant lorsque le type vaut fixed. Envoyé sans global_discount_type, ou l'inverse, il renvoie 422.
has_global_discountbooleanfacultatif
true exige les deux champs ci-dessus. false retire la remise sur toute la facture, quoi qu'ils indiquent.

Chaque entrée de items constitue un objet distinct.

namestringobligatoire
Le libellé de la ligne.
descriptionstringfacultatif
Une seconde ligne sous le libellé.
quantitynumberobligatoire
La quantité.
unit_priceintegerobligatoire
Le prix unitaire, en unités entières de la devise.
unitstringfacultatif
Un libellé comme hour ou day. Il sert uniquement à l’affichage.
tax_ratenumberfacultatif
Un pourcentage de 0 à 100, ajouté à la ligne.
discount_typeenumfacultatif
percentage ou fixed.
discount_valuenumberfacultatif
Le pourcentage, 100 au plus, ou le montant lorsque le type vaut fixed, au plus quantity × unit_price de la ligne : une remise ne fait jamais passer une ligne sous zéro. Au-delà, 422 sur items.N.discount_value.
curl https://api.wajub.com/invoices \
  -H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Traoré & Fils",
    "customer_email": "amina@example.com",
    "invoice_date": "2026-09-12",
    "payment_terms_days": 30,
    "currency": "XAF",
    "items": [
      { "name": "Consulting service", "quantity": 2, "unit_price": 150000, "tax_rate": 19.25 },
      { "name": "Onboarding", "quantity": 1, "unit_price": 100000, "discount_type": "percentage", "discount_value": 10 }
    ]
  }'

La création est le seul appel de facture qui respecte Idempotency-Key. Rejouer une création avec la même clé renvoie la première facture au lieu d'en émettre une seconde. Rejouer un appel send ou mark-paid l'exécute une nouvelle fois.

Les montants sont en unité principale

unit_price, subtotal, discount_amount, tax_amount, total et amount_paid sont tous exprimés dans l'unité principale de la devise : 150000 représente cent cinquante mille francs, et non mille cinq cents.

Les décimales sont acceptées. "unit_price": 1500.50 est enregistré tel quel, et un taux qui ne produit pas un résultat entier garde ses décimales : 19,25 % de 1 000 vaut 192,5. Wajub conserve deux décimales sur chaque montant.

Calcul d'une ligne

Chaque ligne effectue ses propres calculs à chaque enregistrement, dans l'ordre suivant. Vous envoyez les quatre premières valeurs. Les quatre autres sont calculées et renvoyées.

subtotal        = quantity × unit_price
discount_amount = discount_type = percentage  →  subtotal × discount_value / 100
                  discount_type = fixed       →  discount_value
                  otherwise                   →  0
taxable         = subtotal − discount_amount
tax_amount      = taxable × tax_rate / 100
total           = taxable + tax_amount

La taxe est exclusive. Elle est ajoutée à la ligne et n'en est jamais extraite. Elle est aussi calculée après la remise. Une remise de 10 % sur une ligne taxée réduit donc également la taxe.

La facture additionne ensuite les lignes, chacune nette de sa remise.

subtotal        = Σ (subtotal de la ligne − discount_amount de la ligne)
discount_amount = la remise sur toute la facture, 0 sans elle
tax_amount      = Σ tax_amount des lignes
total           = Σ total des lignes − discount_amount
amount_due      = max(0, total − amount_paid)

Une remise sur une ligne est déduite du subtotal, du total et du amount_due de la facture, et sa taxe avec elle. Le discount_amount de la facture est la seule remise sur toute la facture : celles des lignes sont dans items[].discount_amount.

Une remise sur toute la facture

global_discount_type et global_discount_value déduisent une remise de toute la facture, après les remises des lignes. Un pourcentage se calcule sur le subtotal, les lignes hors taxe ; un montant fixed est déduit tel quel, au plus ce subtotal. La taxe reste celle des lignes, et le total est la somme des totaux des lignes moins la remise, jamais sous zéro.

Deux lignes et 10 % sur toute la facture
2 × 50 000, 10 % de remise, TVA 19,25 %    total de la ligne  107 325
1 × 20 000, 5 000 de remise               total de la ligne   15 000

subtotal         105 000   (90 000 + 15 000)
discount_amount   10 500   (10 % de 105 000)
tax_amount        17 325
total            111 825   (107 325 + 15 000 − 10 500)

La facture renvoie la remise choisie dans has_global_discount, global_discount_type et global_discount_value, et ce qu'elle représente dans discount_amount. La page de paiement encaisse le total remisé, et le reçu comme la confirmation de paiement indiquent la remise après les lignes.

Devis et estimations

Créez un devis ou une estimation avec document_type: quote ou estimate. Il se lit, se corrige, s'annule et se supprime par son id comme une facture, et GET /invoices?document_type=quote liste les devis. Il reste un draft : l'API ne l'envoie pas, ne le numérote pas et ne l'encaisse pas. POST /invoices/{id}/send renvoie donc 400 Only an invoice can be sent through the API. et mark-paid renvoie 400 Only an invoice can be marked as paid. Le document_type est fixé à la création et conservé : PUT ne le change pas.

Paiement en plusieurs fois

Définissez payment_type sur split ou milestone, puis transmettez le calendrier. La page hébergée débite alors une échéance à la fois au lieu du solde total.

amountnumberobligatoire
Le montant débité pour cette échéance.
labelstringfacultatif
Affiché sur la page de paiement. La valeur par défaut est Installment 1 ou Milestone 1.
due_datedatefacultatif
Affichée sur la page de paiement.

Le client ouvre la facture et voit la première échéance non payée. Son amount est débité dans la limite de l'amount_due restant. Après son règlement, Wajub marque cette échéance comme payée, ajoute le montant à amount_paid et fait passer la facture à partial. La visite suivante propose l'échéance suivante. La facture passe à paid lorsque amount_paid atteint le total. Si toutes les échéances sont réglées mais qu'un solde reste dû, la page propose le paiement du reste en une fois.

Lister les factures

GET /invoices renvoie vos factures live, de la plus récente à la plus ancienne, par groupes de vingt-cinq.

GEThttps://api.wajub.com/invoices
searchstringfacultatif
Recherche en texte intégral sur le numéro, le nom et l’e-mail du client.
statusenumfacultatif
Un statut, exactement comme il apparaît sur l’objet : draft, sent, viewed, partial, paid, overdue, cancelled ou refunded.
customer_idstringfacultatif
Un client, par son identifiant cus_.
date_fromdatefacultatif
Limite inférieure de invoice_date.
date_todatefacultatif
Limite supérieure de invoice_date, égale ou postérieure à date_from.
due_date_fromdatefacultatif
Limite inférieure de due_date.
due_date_todatefacultatif
Limite supérieure de due_date, égale ou postérieure à due_date_from.
amount_minnumberfacultatif
Limite inférieure de total, en unité principale, comme total se lit : 10000 vaut dix mille francs. Chaque facture est comparée dans sa propre devise.
amount_maxnumberfacultatif
Limite supérieure de total, en unité principale.
payment_statusenumfacultatif
unpaid, partially_paid ou fully_paid, déterminé en comparant amount_paid à total.
sort_byenumfacultatifdéfaut : created_at
invoice_date, due_date, total, amount_paid, status ou created_at. La liste le suit en premier, puis du plus récent au plus ancien entre factures à égalité.
sort_direnumfacultatifdéfaut : desc
asc ou desc.
per_pageintegerfacultatifdéfaut : 25
Entre 1 et 100.
document_typeenumfacultatifdéfaut : invoice
invoice, quote ou estimate : un type de document par liste. Sans lui, la liste ne contient que des factures.

Un status, un payment_status, un document_type, un sort_by ou un sort_dir hors de ces valeurs renvoie une 422 sur ce champ, comme un per_page hors limites. De même pour une borne de date qui n'est pas une date, ou une borne supérieure antérieure à la borne inférieure.

curl -G https://api.wajub.com/invoices \
  -H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
  -d status=overdue \
  -d customer_id=cus_7Kq2mX9vL4tRb3nP8sZcWd1e

La liste utilise une pagination par curseur avec le tri par défaut. Si vous demandez un sort_by ou un sort_dir personnalisé, elle revient aux numéros de page.

Corriger une facture

PUT /invoices/{id} remplace le document au lieu de le modifier partiellement. customer_name, items, invoice_date et currency sont obligatoires à chaque appel. Renvoyez donc toute la facture avec votre modification, pas uniquement le changement. Les lignes envoyées remplacent entièrement les précédentes : chaque mise à jour remplace les lignes et leurs id. Tous les items[].id changent donc.

Vous pouvez modifier recurring_interval pendant une mise à jour. L'alias recurring_frequency accepté à la création ne l'est pas. Utilisez donc toujours recurring_interval. La remise sur toute la facture est conservée tant que vous ne l'envoyez pas : has_global_discount: false la retire. language reste tel quel tant que vous ne l'envoyez pas, même quand customer_id change. Un PUT envoie un seul invoice.updated, avec les lignes et les totaux enregistrés, et aucun quand il ne change rien. Une facture supprimée ne vous est plus jamais renvoyée, et son numéro de facture reste utilisé.

Enregistrer un paiement encaissé ailleurs

mark-paid sert aux fonds reçus hors de Wajub, en espèces ou par virement bancaire. Il modifie le solde de la facture, mais ne crée aucune transaction et ne règle rien.

POSThttps://api.wajub.com/invoices/{id}/mark-paid
amountnumberfacultatif
Le montant reçu, supérieur à zéro. Omettez-le pour enregistrer la totalité de total.
payment_datedatefacultatif
Le jour où l'argent est arrivé. Devient paid_at quand ce paiement règle la facture, y compris un paiement unique du montant total. Aujourd'hui si omis.
transaction_idstringfacultatif
Votre référence pour l'argent reçu, un numéro de virement par exemple. Conservée sur le paiement que la facture enregistre, que le Dashboard affiche dans son historique ; l'API ne la renvoie pas.
payment_methodstringfacultatif
Conservé de la même façon, pour vos dossiers.
notesstringfacultatif
Conservées de la même façon, pour vos dossiers.

Un amount inférieur au total est ajouté à amount_paid et fait passer le statut à partial. Rappelez l'endpoint pour l'échéance suivante. Les montants s'accumulent et la facture passe à paid avec un paid_at dès qu'ils atteignent le total.

curl https://api.wajub.com/invoices/inv_LRQqYvlhrgUOE225KMKU/mark-paid \
  -H "Authorization: sk.xxxxxxxxxxxxxxxx…" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 178875, "payment_date": "2026-09-20" }'

La facture doit être ouverte : envoyée, consultée, en retard ou partiellement payée. Un brouillon renvoie 400, puisqu'il n'a été ni envoyé ni numéroté : envoyez-le d'abord. Une facture déjà paid renvoie aussi 400 et garde son paid_at ; de même pour une facture cancelled ou refunded, avec 400 This invoice cannot be marked as paid, et pour un devis ou une estimation, avec 400 Only an invoice can be marked as paid. Un amount nul ou négatif renvoie 422.

Le PDF

Une fois envoyée, une facture a un PDF que toute personne qui détient son id peut télécharger, sans clé : la copie que votre client archive. Un brouillon renvoie 404, comme sa page.

La facture en PDF
https://api.wajub.com/i/inv_LRQqYvlhrgUOE225KMKU/pdf

Il est rédigé dans la language de la facture. Ajoutez ?locale=fr ou ?locale=en pour l'obtenir dans l'autre langue. C'est le lien que propose la page de la facture : partagez-le comme vous partagez la page.

Annuler une facture

POST /invoices/{id}/cancel définit le statut sur cancelled, renseigne cancelled_at et ferme la page publique. Une facture déjà paid, cancelled ou refunded renvoie 400 This invoice cannot be cancelled.

Une facture partiellement payée ne peut plus être modifiée ni supprimée. L'annuler la clôt sans rembourser les fonds encaissés. Rembourser en totalité ce qui a été payé en ligne, avec les remboursements sur les paiements sous-jacents, la fait passer à refunded, sauf si une part a été enregistrée avec mark-paid.

Que pensez-vous de ce contenu ?