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, remettez à un client, puis attendez de voir payé. Wajub stocke les lignes, calcule les totaux, héberge la page de paiement et enregistre les montants réglés. Wajub ne relance personne et ne débite rien. Le produit se résume à un document avec une URL publique et un solde actualisé. Facturation et abonnements présente ce cycle. Cette page sert de référence pour l'objet lui-même.
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. Il n'existe aucun endpoint pour les PDF, les e-mails ou le débit d'un client.
| Appel | Fonction |
|---|---|
GET /invoices | Lister, filtrer et trier vos factures |
POST /invoices | Créer une facture, toujours avec draft |
GET /invoices/{id} | Récupérer une facture |
PUT /invoices/{id} | Remplacer une facture tant qu'elle reste modifiable |
DELETE /invoices/{id} | Supprimer logiquement une facture tant qu'elle reste modifiable |
POST /invoices/{id}/send | La faire passer à sent et ouvrir sa page publique |
POST /invoices/{id}/mark-paid | Enregistrer un paiement encaissé ailleurs |
POST /invoices/{id}/cancel | Fermer une facture |
{id} correspond à l'id renvoyé par l'API, soit la chaîne inv_. L'UUID interne est également
accepté, mais il ne vous est jamais présenté. Utilisez donc l'identifiant 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.
invoice_number est généré pour chaque équipe et redémarre tous les mois : INV-, suivi de l'année
et du mois, puis d'un compteur à quatre chiffres. 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.
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 ou un paiement effectué sur la page hébergée.
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.
https://api.wajub.com/invoicescustomer_namestringobligatoirecustomer_id.customer_idstring (uuid)facultatifcustomer_* envoyés.customer_emailstringfacultatifcustomer_company_namestringfacultatifcustomer_phonestringfacultatifcustomer_addressstringfacultatifitemsarrayobligatoireinvoice_datedateobligatoiredue_datedatefacultatifinvoice_date.payment_terms_daysintegerfacultatifdue_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.currencystringobligatoireXAF, XOF, NGN et les autres devises prises en charge.is_recurringbooleanfacultatifdéfaut : falserecurring_intervalenumfacultatifdaily, weekly, monthly, quarterly ou yearly.payment_typeenumfacultatifdéfaut : fullfull, split ou milestone. Les paiements en plusieurs fois sont décrits ci-dessous.payment_schedulesarrayfacultatifpayment_type ne vaut pas full.notesstringfacultatiftermsstringfacultatiffooterstringfacultatiftemplate_idstring (uuid)facultatifdocument_typeenumfacultatifdéfaut : invoiceinvoice, quote ou estimate. Lisez l’avertissement ci-dessous avant de le modifier.Chaque entrée de items constitue un objet distinct.
namestringobligatoiredescriptionstringfacultatifquantitynumberobligatoireunit_priceintegerobligatoireunitstringfacultatifhour ou day. Il sert uniquement à l’affichage.tax_ratenumberfacultatifdiscount_typeenumfacultatifpercentage ou fixed.discount_valuenumberfacultatiffixed.curl https://api.wajub.com/invoices -H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" -H "Content-Type: application/json" -d '{
"customer_name": "Traoré & Fils",
"customer_email": "amina@example.cm",
"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 des nombres entiers
Tous les champs monétaires d'une facture et de ses lignes sont des entiers. unit_price,
subtotal, discount_amount, tax_amount, total et amount_paid contiennent des unités entières
de la devise. Aucune unité secondaire n'est utilisée. 150000 représente cent cinquante mille
francs, et non mille cinq cents.
Un montant décimal échoue après la validation, pas pendant celle-ci
unit_price, quantity, tax_rate et discount_value sont validés comme numeric. La valeur
"unit_price": 1500.50 passe donc la validation. L'écriture échoue ensuite, car la colonne ne peut
pas la contenir. Vous recevez une erreur 500 au lieu d'une erreur 422 qui indiquerait le champ
incorrect. La même erreur survient lorsqu'un taux ne produit pas un résultat entier : 19,25 % de
1 000 vaut 192,5. Construisez chaque ligne de façon à obtenir un prix, une remise et une taxe en
unités entières.
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_amountLa 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.
subtotal = Σ (quantity × unit_price)
tax_amount = Σ line tax_amount
total = subtotal + tax_amount
amount_due = max(0, total − amount_paid)Une remise sur une ligne réduit la taxe, mais pas le total de la facture
Observez les deux sommes ci-dessus. Le sous-total de la facture multiplie de nouveau la quantité
par le prix et ne soustrait jamais discount_amount. La taxe ajoutée correspond pourtant à celle
calculée par chaque ligne après sa remise. Dans l'exemple précédent, la deuxième ligne renvoie
total: 90000, mais la facture compte toujours 100 000 pour cette ligne. Tant que ce problème
n'est pas corrigé, n'utilisez pas les remises dans l'API. Donnez directement à la ligne le prix à
facturer.
Aucune remise globale sur une facture
Les remises s'appliquent par ligne. L'appel de création accepte également has_global_discount,
global_discount_type et global_discount_value. Ces trois champs ne font rien. Ils sont validés,
puis supprimés avant l'écriture, car aucune colonne ne les stocke. Ils ne produisent aucune erreur.
Une facture envoyée avec une remise globale de 15 % est donc enregistrée au prix total tout en
donnant l'impression d'avoir fonctionné.
Devis et estimations
document_type accepte quote et estimate, puis crée le document.
Un devis créé avec l'API ne peut pas être relu
Tous les autres appels de facture filtrent sur document_type = invoice. Un devis ou une
estimation est donc absent de GET /invoices. Les appels GET, PUT, DELETE, /send,
/mark-paid et /cancel effectués sur son id renvoient tous 404 Invoice not found. Le document
existe, ne peut rien facturer et reste visible uniquement dans le Dashboard. Ne modifiez pas
document_type et créez vos devis dans le Dashboard.
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.
amountintegerobligatoirelabelstringfacultatifInstallment 1 ou Milestone 1.due_datedatefacultatifLe 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 écrit status: "paid", paid_at et
transaction_uid sur cette échéance, 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.
Vous pouvez définir un calendrier, mais pas le relire
L'objet facture renvoie payment_type, mais jamais payment_schedules. Les échéances existantes,
leur paiement et la transaction associée à chacune sont visibles uniquement dans le Dashboard.
Conservez votre propre copie du calendrier envoyé si vous devez le suivre de votre côté.
Lister les factures
GET /invoices renvoie vos factures live, de la plus récente à la plus ancienne, par groupes de
vingt-cinq.
https://api.wajub.com/invoicessearchstringfacultatifstatusenumfacultatifcustomer_idstring (uuid)facultatifdate_fromdatefacultatifinvoice_date.date_todatefacultatifinvoice_date.due_date_fromdatefacultatifdue_date.due_date_todatefacultatifdue_date.amount_minintegerfacultatiftotal.amount_maxintegerfacultatiftotal.payment_statusenumfacultatifunpaid, partially_paid ou fully_paid, déterminé en comparant amount_paid à total.sort_byenumfacultatifdéfaut : created_atinvoice_date, due_date, total, amount_paid, status ou created_at.sort_direnumfacultatifdéfaut : descasc ou desc.per_pageintegerfacultatifdéfaut : 25curl -G https://api.wajub.com/invoices -H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" -d status=overdue -d customer_id=9d1f0c7a-4b2e-4f61-9d3a-7c8e5b21a940La 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, car un curseur nécessite un ordre
stable.
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. Les anciennes sont supprimées et recréées. Tous les items[].id
changent donc.
Une facture devient non modifiable dès qu'elle reçoit des fonds
PUT et DELETE renvoient 400 This invoice cannot be edited (paid, cancelled, or has payments).
dès que le statut vaut paid, cancelled ou refunded, ou dès que amount_paid dépasse zéro.
Une facture partiellement payée est déjà figée. Annulez-la et émettez-en une nouvelle.
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 suppression est
logique : la ligne reste présente, le numéro de facture reste utilisé et rien ne vous la renvoie.
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.
https://api.wajub.com/invoices/{id}/mark-paidamountintegerfacultatiftotal.payment_datedatefacultatifpaid_at si ce paiement règle le solde.transaction_idstringfacultatifpayment_methodstringfacultatifnotesstringfacultatifUn 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.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" -H "Content-Type: application/json" -d '{ "amount": 178875, "payment_date": "2026-09-20" }'Une facture cancelled ou refunded refuse l'appel avec 400 This invoice cannot be marked as paid.
Annuler une facture
POST /invoices/{id}/cancel définit le statut sur cancelled, renseigne cancelled_at et ferme la
page publique. L'appel accepte un champ reason, mais ne le stocke pas. Une facture déjà paid,
cancelled ou refunded renvoie 400 This invoice cannot be cancelled.
L'annulation est la seule sortie possible pour une facture partiellement payée, car elle ne peut plus être modifiée ou supprimée. Elle ne rembourse pas les fonds encaissés. Utilisez les remboursements sur le paiement sous-jacent.
Pages associées