Plan BusinessVersion 1.0.0

API FactuPilot

Lisez et créez vos clients, factures et devis depuis vos propres outils.

URL de basehttps://factupilot.fr/api/v1

Cette API REST permet à vos outils (site web, CRM, ERP, logiciel métier, scripts) de lire et de créer vos clients, vos factures et vos devis dans FactuPilot, d'émettre vos factures, et de les faire transmettre à votre plateforme agréée. Des webhooks vous préviennent ensuite des événements de chaque facture. Elle est réservée au plan Business (49 EUR par mois).

Démarrage rapide

  1. Passez votre entreprise au plan Business depuis l'onglet « Abonnement » de votre tableau de bord.
  2. Ouvrez Paramètres, section Clés API, créez une clé et cochez les portées dont vous avez besoin. La clé complète (fp_live_ suivi de 32 caractères) n'est affichée qu'une seule fois : copiez-la aussitôt.
  3. Appelez l'API en HTTPS, avec la clé dans l'en-tête Authorization.
curl "https://factupilot.fr/api/v1/clients?limit=5" \
  -H "Authorization: Bearer fp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Créer un client puis lui ouvrir une facture brouillon :

curl -X POST "https://factupilot.fr/api/v1/clients" \
  -H "Authorization: Bearer fp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Boulangerie Dupont", "email": "contact@boulangerie-dupont.fr"}'

curl -X POST "https://factupilot.fr/api/v1/invoices" \
  -H "Authorization: Bearer fp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"clientId": "<id renvoyé ci-dessus>", "items": [{"description": "Création du site", "quantity": 1, "unitPriceHt": 650, "vatRate": 20}]}'

Les corps de requête sont en JSON, les réponses aussi (sauf le PDF d'une facture).

Authentification

Toute requête porte l'en-tête Authorization: Bearer fp_live_.... La clé est vérifiée à chaque appel, y compris l'accès au plan Business de votre entreprise : si l'entreprise quitte le plan Business, ses clés cessent immédiatement de fonctionner (réponse 403) et reprennent dès le retour au plan.

Ne placez jamais une clé dans une page web, une application mobile ou un dépôt public : appelez l'API depuis votre serveur. Une clé compromise se supprime depuis Paramètres > Clés API.

Chaque clé porte des portées choisies à sa création. Chaque opération de ce document indique celle qu'elle exige (champ x-scope).

PortéeOpérations
clients:readGET /clients
clients:writePOST /clients
invoices:readGET /invoices, GET /invoices/{id}/pdf
invoices:writePOST /invoices, POST /invoices/{id}/emettre
quotes:readGET /quotes
quotes:writePOST /quotes, POST /quotes/create

Les routes réservées aux applications connectées au hub (clés fp_app_) ne sont pas décrites ici.

Erreurs

Toute erreur renvoie un corps JSON {"error": "message"} et un code HTTP :

CodeSignification
400Requête invalide : corps qui n'est pas du JSON, champ obligatoire absent, date illisible, ligne invalide, paramètre de liste hors limites. Le message nomme la valeur en cause et dit quoi corriger.
401Clé absente, inconnue, expirée ou mal formée (Authorization: Bearer ...).
403Soit la clé n'a pas la portée exigée (Permission refusée · clients:read requise), soit l'entreprise n'a plus l'accès API (plan Business). La clé est alors reconnue, d'où un 403 et non un 401.
404Ressource introuvable dans votre entreprise (client, facture).
409Conflit d'état (POST /invoices/{id}/emettre) : la facture n'est plus un brouillon, ou c'est un brouillon saisi dans FactuPilot, qui s'émet depuis FactuPilot.
429Trop de requêtes. L'en-tête Retry-After indique le délai d'attente en secondes.
500Erreur interne. Le message est générique (Erreur interne du serveur) ; réessayez, puis contactez le support si elle persiste.

Ordre des contrôles : authentification (401), accès au plan (403), limite de débit (429), portée (403), validation de la requête (400), puis existence de la ressource (404) et état (409).

Limites de débit

  • Lecture (listes) : 120 requêtes par minute.
  • Écriture (création de client, de facture, de devis, émission d'une facture) et PDF : 30 requêtes par minute.

Le décompte se fait par fenêtre de 60 secondes et est commun à toutes les clés utilisateur de l'entreprise ; chaque route applique son propre seuil. Au-delà, l'API répond 429 avec Retry-After. Ces limites sont appliquées par le serveur qui traite l'appel : traitez-les comme un plafond à ne pas approcher plutôt que comme un quota garanti.

Pagination

Les listes acceptent page (à partir de 1) et limit (20 par défaut, 100 au maximum) et renvoient un bloc pagination : page, limit, total et pages. Les listes sont triées du plus récent au plus ancien.

Montants, dates et TVA

  • Montants en euros, nombres décimaux : 123.45 pour 123,45 EUR. Jamais en centimes, ni en entrée (unitPriceHt) ni en sortie (totalHt, totalVat, totalTtc).
  • Totaux calculés par FactuPilot : vous envoyez des lignes, jamais de totaux. Le prix unitaire est arrondi au centime à l'enregistrement.
  • Dates au format ISO 8601, en UTC (2026-10-16T09:12:44.318Z). En entrée, une date seule (2026-11-15) vaut minuit UTC.
  • Taux de TVA en pourcentage (20, 10, 5.5, 2.1, 0). Une ligne sans vatRate est à 0 % : précisez toujours le taux.
  • Ligne à 0 % : le motif est obligatoire à l'émission. Pour une entreprise assujettie à la TVA, chaque ligne à 0 % doit porter un motifTvaZero, choisi dans le catalogue officiel dont les identifiants sont listés dans le schéma MotifTvaZero. Le motif est imprimé sur le document et transmis dans le Factur-X. L'API accepte un brouillon sans motif, mais FactuPilot refuse de l'émettre tant qu'une ligne à 0 % n'en porte pas : renseignez-le dès la création. Une entreprise en franchise en base de TVA n'a pas de motif à fournir. Certains motifs (export, livraison intracommunautaire, autoliquidation) exigent en plus un numéro de TVA intracommunautaire, ou un SIREN, sur la fiche du client.

Factures et devis créés par l'API

Un devis créé par l'API naît en brouillon (DRAFT) avec son numéro légal déjà attribué (par exemple DEV-2026-0018), pris dans la même séquence que les documents créés depuis le tableau de bord. Il n'est pas envoyé au client : l'envoi se fait depuis FactuPilot.

Une facture naît elle aussi en brouillon avec son numéro légal (par exemple FA-2026-0043), sauf si vous demandez son émission : voir la section suivante.

Créer un document consomme un numéro de la séquence. Factures : envoyez un en-tête Idempotency-Key (1 à 100 caractères visibles, par exemple un UUID) ; rejouer la requête avec la même clé rend la facture déjà créée (200, en-tête Idempotent-Replayed: true), sans doublon ni nouveau numéro. Devis et clients : leurs créations ne sont pas idempotentes ; si une requête échoue de façon incertaine (délai dépassé, 500), vérifiez avec la liste correspondante avant de la rejouer, pour ne pas créer de doublon.

Émission et transmission

Émettre. Un logiciel métier qui facture sans intervention humaine n'a pas besoin de brouillon. Deux façons d'émettre une facture, avec les mêmes exigences qu'à l'émission depuis FactuPilot :

  • À la création : POST /invoices avec "emettre": true. La facture naît émise (SENT), numérotée, en un seul appel.
  • Plus tard : POST /invoices/{id}/emettre sur un brouillon créé par l'API. Seuls ces brouillons s'émettent ainsi : un brouillon saisi dans FactuPilot répond 409 et s'émet depuis FactuPilot. Une facture déjà émise ou annulée répond 409, une facture inconnue ou d'une autre entreprise 404.
curl -X POST "https://factupilot.fr/api/v1/invoices" \
  -H "Authorization: Bearer fp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: commande-2026-0042" \
  -d '{"clientId": "<id du client>", "emettre": true, "items": [{"description": "Transport Perpignan - Toulouse", "quantity": 1, "unitPriceHt": 420, "vatRate": 20}]}'

Une facture émise est intangible : elle ne se modifie plus, une correction passe par un avoir, depuis FactuPilot. Pour une entreprise assujettie à la TVA, une ligne à 0 % sans motifTvaZero est refusée (400) : avec "emettre": true aucune facture n'est créée et aucun numéro n'est consommé, avec POST /invoices/{id}/emettre le brouillon reste en brouillon.

Transmettre. FactuPilot numérote la facture, produit son PDF Factur-X et la remet à la plateforme agréée de l'entreprise (Super PDP, Iopole...). Pour les factures émises par l'API, cette remise est automatique si l'entreprise a activé le réglage « transmission automatique des factures de l'API » dans FactuPilot ; sinon la facture reste à transmettre depuis FactuPilot. Un brouillon n'est jamais transmis.

La transmission est attendue avant la réponse : l'appel peut donc durer plus longtemps qu'une simple création. Si votre client HTTP abandonne avant la réponse, rejouez la requête avec la même Idempotency-Key : vous obtenez la facture déjà émise et l'état enregistré de sa transmission, sans double émission ni double transmission. Son résultat figure dans l'objet data.transmission de la réponse (201 de POST /invoices, 200 de POST /invoices/{id}/emettre) :

ChampContenu
statuttransmise : remise à la plateforme. refusee : une règle l'interdit, rien n'est parti (plan, plateforme en dépôt manuel, adresse électronique de l'acheteur manquante). echec : incident, à relancer depuis FactuPilot. desactivee : transmission automatique non activée pour l'entreprise (ou plan sans l'API), ou coupée sur ce serveur. inconnue : réponse d'un rejeu dont la transmission n'est pas encore enregistrée ; rien n'est retransmis.
plateformesuperpdp, iopole ou manuel (dépôt manuel), null si elle n'a pas pu être lue.
statutPlateformeStatut brut renvoyé par la plateforme à la remise, null si rien n'a été remis.
messageExplication lisible, en français.

Un refus ou un échec de transmission ne fait jamais échouer l'appel : la facture est créée et émise, la réponse reste 201 (ou 200). Testez donc data.transmission.statut, pas seulement le code HTTP. Le suivi de la facture auprès de la plateforme (acceptée, rejetée...) vous parvient ensuite par le webhook facture.statut_plateforme.

Webhooks

Plutôt que d'interroger l'API pour savoir ce qu'il est advenu d'une facture, laissez FactuPilot prévenir votre serveur. Un webhook est une adresse HTTPS de votre serveur, à laquelle FactuPilot envoie une requête POST JSON à chaque événement auquel vous l'avez abonné. Les webhooks se créent depuis l'onglet API des Paramètres (cinq au plus par entreprise) : vous y choisissez l'adresse et les événements, et FactuPilot affiche le secret de signature, whsec_ suivi de 32 caractères, une seule fois. L'adresse doit être publique, en https et sur le port standard 443 (pas de réseau local, d'adresse IP privée, ni d'autre port). Le bouton « Envoyer un test » envoie un événement webhook.test.

Comme l'API, les webhooks sont réservés au plan Business : une entreprise qui le quitte ne reçoit plus d'événements. La clé racine webhooks de ce document OpenAPI décrit chaque requête reçue.

ÉvénementQuand
facture.creeeUne facture vient d'être créée (par l'API, le tableau de bord, un devis accepté, un acompte, un avoir...).
facture.emiseUne facture vient d'être émise (brouillon passé en SENT, ou facture née émise : elle déclenche alors facture.creee puis facture.emise).
facture.payeeUne facture vient d'être entièrement payée, y compris par un paiement en ligne. Un paiement partiel ne déclenche rien.
facture.statut_plateformeLe suivi de la facture par la plateforme agréée a changé (téléversée, acceptée, rejetée, erreur de transmission...). Un rejeu du même statut ne déclenche rien.
webhook.testEnvoyé par le bouton « Envoyer un test », même si le webhook est désactivé. On ne s'y abonne pas.

Requête reçue

En-têtes :

En-têteValeur
Content-Typeapplication/json
User-AgentFactuPilot-Webhooks/1
X-FactuPilot-EvenementLe type de l'événement, par exemple facture.emise.
X-FactuPilot-LivraisonL'identifiant de l'événement (champ id du corps). Identique à chaque nouvelle tentative : c'est votre clé d'idempotence.
X-FactuPilot-Signaturet=<horodatage en secondes>,v1=<signature hexadécimale>

Corps (les noms de champs sont ceux des webhooks, en français) :

{
  "id": "evt_9f2c4a7e1b3d58606a1c0e2f",
  "type": "facture.emise",
  "creeLe": "2026-10-16T09:12:44.318Z",
  "donnees": {
    "facture": {
      "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
      "numero": "FA-2026-0043",
      "statut": "SENT",
      "typeDocument": "STANDARD",
      "totalTtc": 1032,
      "client": { "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae", "nom": "Boulangerie Dupont" },
      "plateforme": null
    }
  }
}

totalTtc est en euros. plateforme vaut null tant que la facture n'a jamais été présentée à une plateforme agréée, puis { "nom", "statut", "code", "statutTexte" } (statut est l'étape de suivi : televersee, en_transit, acceptee, rejetee, erreur...).

Vérifier la signature

Chaque requête est signée avec le secret du webhook : vérifiez la signature avant de traiter l'événement, pour être sûr qu'il vient de FactuPilot et n'a pas été modifié.

  1. Lisez X-FactuPilot-Signature : t est l'horodatage de l'envoi (secondes), v1 la signature.
  2. Refusez la requête si t s'écarte de plus de 5 minutes de votre horloge (protection contre le rejeu). L'horodatage est renouvelé à chaque tentative.
  3. Calculez le HMAC-SHA256, en hexadécimal, de la chaîne "<t>.<corps>" : l'horodatage, un point, puis le corps brut tel que reçu, octet pour octet. La clé est le secret entier, préfixe whsec_ compris.
  4. Comparez avec v1 à temps constant (pas avec ===).

Le corps doit être celui reçu, avant tout JSON.parse : re-sérialiser le JSON change les octets et la signature ne correspond plus.

const { createHmac, timingSafeEqual } = require("node:crypto");

const TOLERANCE_SECONDES = 300;

// corpsBrut : le corps tel que reçu (Buffer ou chaîne), avant tout JSON.parse.
// enTete    : la valeur de X-FactuPilot-Signature.
// secret    : le secret complet du webhook, préfixe whsec_ compris.
function signatureValide(corpsBrut, enTete, secret) {
  const parties = {};
  for (const morceau of String(enTete || "").split(",")) {
    const i = morceau.indexOf("=");
    if (i > 0) parties[morceau.slice(0, i)] = morceau.slice(i + 1);
  }
  const t = Number(parties.t);
  if (!Number.isInteger(t) || !parties.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDES) return false;

  const attendu = createHmac("sha256", secret).update(t + ".").update(corpsBrut).digest("hex");
  const a = Buffer.from(attendu, "hex");
  const b = Buffer.from(parties.v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

Avec Express, lisez le corps brut pour la route du webhook :

app.post("/webhooks/factupilot", express.raw({ type: "application/json" }), (req, res) => {
  if (!signatureValide(req.body, req.get("X-FactuPilot-Signature"), process.env.FACTUPILOT_WEBHOOK_SECRET)) {
    return res.status(400).send("Signature invalide");
  }
  const evenement = JSON.parse(req.body.toString("utf8"));
  // Ignorez un evenement.id déjà traité, puis traitez et répondez vite.
  res.sendStatus(200);
});

Répondre et nouvelles tentatives

Répondez par un code 2xx en moins de 10 secondes : le corps de votre réponse est ignoré. Traitez l'événement ensuite, de façon asynchrone si c'est long. Tout autre résultat (code 3xx, 4xx ou 5xx, délai dépassé, connexion impossible) est un échec. Une redirection n'est suivie que vers la même origine ; une redirection vers un autre domaine est un échec.

Après un échec, FactuPilot réessaie jusqu'à 6 tentatives au total, avec le même id d'événement :

TentativeDélai après l'échec précédent
1Immédiate
21 minute
35 minutes
430 minutes
52 heures
612 heures

Après la sixième tentative, la livraison est abandonnée et l'incident est signalé à FactuPilot. Elle reste consultable, avec son code de réponse et son erreur, dans l'historique du webhook ; quand votre serveur est injoignable, l'erreur affichée est « Connexion impossible », sans détail réseau. Pour rattraper des événements manqués, relisez les factures avec GET /invoices.

Quelques règles de robustesse : dédupliquez sur id (une même livraison peut arriver plus d'une fois), ne comptez pas sur l'ordre des événements (une nouvelle tentative de facture.emise peut arriver après facture.payee : fiez-vous au statut de la charge ou relisez la facture avec GET /invoices?ids=...), et régénérez le secret s'il a fuité (l'ancien cesse de fonctionner aussitôt).

Remarques sur les cas limites

  • Les clients archivés, les factures archivées et les anciennes versions de devis révisés ne figurent pas dans les listes.
  • Un paramètre ou un champ illisible (corps qui n'est pas du JSON, date, status hors de la liste, limit inférieur à 1, description de ligne vide) donne une erreur 400 dont le message nomme la valeur en cause. Une erreur 500 est un incident de FactuPilot : réessayez, puis contactez le support.

Référence des opérations

Les exemples curl lisent votre clé dans la variable d'environnement FACTUPILOT_API_KEY. Chaque opération indique la portée que la clé doit porter.

Clients

Fiches clients de l'entreprise.

Lister les clients

GET/clients
Portée requise
clients:read
Limite de débit
120 requêtes par minute

Renvoie les clients non archivés de l'entreprise, du plus récent au plus ancien (date de création).

Le paramètre search filtre sur le nom, l'e-mail et le SIRET (sous-chaîne, sans tenir compte de la casse).

Au-delà de 2000 clients, la liste est tronquée : utilisez search pour cibler.

Paramètres
Paramètres de Lister les clients
ParamètreTypeDescription
page
requête
integer

Numéro de page, à partir de 1. Une valeur inférieure à 1 est ramenée à 1 ; une valeur non numérique est refusée (erreur 400).

  • Par défaut : 1
  • min. 1
  • Exemple : 1
limit
requête
integer

Nombre d'éléments par page, de 1 à 100 (20 par défaut). Une valeur supérieure à 100 est ramenée à 100 ; une valeur inférieure à 1 ou non numérique est refusée (erreur 400).

  • Par défaut : 20
  • min. 1 · max. 100
  • Exemple : 20
search
requête
string

Texte recherché dans le nom, l'e-mail et le SIRET du client.

  • Exemple : dupont
Exemple de requête
curl "https://factupilot.fr/api/v1/clients?page=1&limit=20&search=dupont" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY"
Réponses
  • 200

    Page de clients.

    application/json
    Exemple de réponse
    {
      "data": [
        {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Boulangerie Dupont",
          "email": "contact@boulangerie-dupont.fr",
          "phone": "04 68 12 34 56",
          "address": "12 rue de la République",
          "city": "Perpignan",
          "zipCode": "66000",
          "siret": "81234567800019",
          "siren": "812345678",
          "clientType": "COMPANY",
          "codeServiceExecutant": null,
          "numeroEngagementParDefaut": null,
          "createdAt": "2026-09-14T08:32:11.402Z",
          "updatedAt": "2026-09-14T08:32:11.402Z"
        }
      ],
      "pagination": {
        "page": 1,
        "limit": 20,
        "total": 1,
        "pages": 1
      }
    }
    Champs de la réponse
    Champs de la réponse 200
    ChampTypeDescription
    data
    requis
    array<object>

    Fiche client.

    data[].id
    requis
    string

    Identifiant du client.

    data[].name
    requis
    string

    Nom ou raison sociale.

    data[].email
    string | null

    Adresse e-mail, enregistrée en minuscules.

    data[].phone
    string | null
    data[].address
    string | null
    data[].city
    string | null
    data[].zipCode
    string | null
    data[].siret
    string | null

    SIRET, chiffres uniquement.

    data[].siren
    string | null

    SIREN, dérivé du SIRET (9 premiers chiffres) par FactuPilot.

    data[].clientType
    string

    Type de client : entreprise (COMPANY), particulier (INDIVIDUAL) ou entité publique (ADMINISTRATION).

    Valeurs permises :COMPANYINDIVIDUALADMINISTRATION
    data[].codeServiceExecutant
    string | null

    Code du service exécutant (Chorus Pro), repris sur les factures du client. null hors ADMINISTRATION.

    data[].numeroEngagementParDefaut
    string | null

    Numéro d'engagement proposé par défaut sur les factures du client. null hors ADMINISTRATION.

    data[].createdAt
    requis
    string (date-time)

    Date de création (ISO 8601, UTC).

    data[].updatedAt
    string (date-time)

    Présent dans la liste seulement. Reprend la date de création : FactuPilot ne conserve pas de date de dernière modification pour les clients.

    pagination
    requis
    object

    Position dans la liste. pages vaut 0 quand la liste est vide.

    pagination.page
    requis
    integer

    Page courante (à partir de 1).

    • min. 1
    pagination.limit
    requis
    integer

    Nombre maximal d'éléments par page.

    • min. 1 · max. 100
    pagination.total
    requis
    integer

    Nombre total d'éléments correspondant aux filtres.

    • min. 0
    pagination.pages
    requis
    integer

    Nombre total de pages (total divisé par limit, arrondi au supérieur).

    • min. 0
  • 400

    Paramètre illisible : limit inférieur à 1 ou non numérique, page non numérique.

    application/json
    Exemples de réponse (2)

    Limite invalide

    {
      "error": "Le paramètre 'limit' doit être un entier compris entre 1 et 100."
    }

    Page non numérique

    {
      "error": "Le paramètre 'page' doit être un entier supérieur ou égal à 1."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée clients:read, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · clients:read requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Créer un client

POST/clients
Portée requise
clients:write
Limite de débit
30 requêtes par minute

Crée une fiche client. L'appel n'est pas idempotent : rejouer la requête crée un second client, sans contrôle de doublon.

Le SIREN est déduit du SIRET fourni.

Client public. Pour une mairie, une préfecture ou toute entité publique, envoyez "clientType": "ADMINISTRATION" avec son siret, son codeServiceExecutant et, si vous le connaissez, un numeroEngagementParDefaut. Ces deux références sont alors reprises sur chaque facture créée pour ce client (POST /invoices peut les remplacer facture par facture) et transmises dans le Factur-X. Elles sont refusées (erreur 400) pour un client d'un autre type.

Corps de la requête

Type de contenu : application/json · corps obligatoire

Champs du corps de Créer un client
ChampTypeDescription
name
requis
string

Nom ou raison sociale (obligatoire).

  • 1 caractère min.
email
string

Adresse e-mail.

phone
string
address
string
city
string
zipCode
string
siret
string

SIRET. Seuls les chiffres sont conservés ; le SIREN est déduit des 9 premiers. Le champ siren n'est pas accepté en entrée.

notes
string

Notes internes (non renvoyées par l'API).

clientType
string

Type de client : COMPANY (entreprise, par défaut), INDIVIDUAL (particulier, sans SIRET) ou ADMINISTRATION (mairie, préfecture, entité publique). Une autre valeur est refusée (erreur 400).

Valeurs permises :COMPANYINDIVIDUALADMINISTRATION
  • Par défaut : COMPANY
codeServiceExecutant
string

Code du service exécutant (Chorus Pro), repris sur chaque facture du client. Réservé à clientType: "ADMINISTRATION" : fourni pour un autre type, il est refusé (erreur 400).

  • 100 caractères max.
numeroEngagementParDefaut
string

Numéro d'engagement proposé par défaut sur les factures du client (modifiable facture par facture). Réservé à clientType: "ADMINISTRATION" : fourni pour un autre type, il est refusé (erreur 400).

  • 100 caractères max.
Exemple de requête
curl -X POST "https://factupilot.fr/api/v1/clients" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Boulangerie Dupont",
  "email": "contact@boulangerie-dupont.fr",
  "phone": "04 68 12 34 56",
  "address": "12 rue de la République",
  "city": "Perpignan",
  "zipCode": "66000",
  "siret": "812 345 678 00019",
  "notes": "Rencontré au salon des commerçants"
}'
Réponses
  • 201

    Client créé.

    application/json
    Exemple de réponse
    {
      "data": {
        "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
        "name": "Boulangerie Dupont",
        "email": "contact@boulangerie-dupont.fr",
        "phone": "04 68 12 34 56",
        "address": "12 rue de la République",
        "city": "Perpignan",
        "zipCode": "66000",
        "siret": "81234567800019",
        "siren": "812345678",
        "clientType": "COMPANY",
        "codeServiceExecutant": null,
        "numeroEngagementParDefaut": null,
        "createdAt": "2026-10-16T09:02:47.918Z"
      }
    }
    Champs de la réponse
    Champs de la réponse 201
    ChampTypeDescription
    data
    requis
    object

    Fiche client.

    data.id
    requis
    string

    Identifiant du client.

    data.name
    requis
    string

    Nom ou raison sociale.

    data.email
    string | null

    Adresse e-mail, enregistrée en minuscules.

    data.phone
    string | null
    data.address
    string | null
    data.city
    string | null
    data.zipCode
    string | null
    data.siret
    string | null

    SIRET, chiffres uniquement.

    data.siren
    string | null

    SIREN, dérivé du SIRET (9 premiers chiffres) par FactuPilot.

    data.clientType
    string

    Type de client : entreprise (COMPANY), particulier (INDIVIDUAL) ou entité publique (ADMINISTRATION).

    Valeurs permises :COMPANYINDIVIDUALADMINISTRATION
    data.codeServiceExecutant
    string | null

    Code du service exécutant (Chorus Pro), repris sur les factures du client. null hors ADMINISTRATION.

    data.numeroEngagementParDefaut
    string | null

    Numéro d'engagement proposé par défaut sur les factures du client. null hors ADMINISTRATION.

    data.createdAt
    requis
    string (date-time)

    Date de création (ISO 8601, UTC).

    data.updatedAt
    string (date-time)

    Présent dans la liste seulement. Reprend la date de création : FactuPilot ne conserve pas de date de dernière modification pour les clients.

  • 400

    Corps illisible, champ name absent ou n'est pas une chaîne, autre champ qui n'est pas du texte, clientType inconnu, ou codeServiceExecutant / numeroEngagementParDefaut fournis pour un client qui n'est pas une ADMINISTRATION (ou de plus de 100 caractères).

    application/json
    Exemples de réponse (5)

    name absent

    {
      "error": "Le champ 'name' est requis"
    }

    Corps illisible

    {
      "error": "Le corps de la requête n'est pas un JSON valide : envoyez un objet JSON avec l'en-tête Content-Type: application/json."
    }

    Champ qui n'est pas du texte

    {
      "error": "Le champ 'email' doit être une chaîne de caractères."
    }

    clientType inconnu

    {
      "error": "Le champ 'clientType' doit valoir COMPANY, INDIVIDUAL, ADMINISTRATION."
    }

    Mention du secteur public sur un autre type de client

    {
      "error": "Le champ 'codeServiceExecutant' ne s'applique qu'à un client de type ADMINISTRATION (mairie, préfecture...)."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée clients:write, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · clients:write requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Factures

Factures : liste, création (brouillon ou émise), émission, PDF.

Lister les factures

GET/invoices
Portée requise
invoices:read
Limite de débit
120 requêtes par minute

Renvoie les factures non archivées de l'entreprise, de la plus récente à la plus ancienne (date d'émission). Les brouillons sont inclus.

Filtres : status, startDate et endDate (sur la date d'émission), clientId, et ids pour relire des factures précises (suivi des paiements).

Quand ids est fourni, seules ces factures sont lues : les filtres status et clientId ne sont alors pas appliqués. Les identifiants qui n'appartiennent pas à votre entreprise sont ignorés. Pagination : jusqu'à 2000 factures sont lues avant découpe en pages, filtrez par date ou statut pour parcourir un long historique.

Paramètres
Paramètres de Lister les factures
ParamètreTypeDescription
page
requête
integer

Numéro de page, à partir de 1. Une valeur inférieure à 1 est ramenée à 1 ; une valeur non numérique est refusée (erreur 400).

  • Par défaut : 1
  • min. 1
  • Exemple : 1
limit
requête
integer

Nombre d'éléments par page, de 1 à 100 (20 par défaut). Une valeur supérieure à 100 est ramenée à 100 ; une valeur inférieure à 1 ou non numérique est refusée (erreur 400).

  • Par défaut : 20
  • min. 1 · max. 100
  • Exemple : 20
status
requête
string

Ne garde que les factures de ce statut. Une valeur hors de la liste est refusée (erreur 400).

Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
  • Exemple : PAID
startDate
requête
string

Ne garde que les documents émis à partir de cette date (incluse), au format ISO 8601. Une date seule (2026-01-01) vaut minuit UTC. Une date illisible est refusée (erreur 400).

  • Exemple : 2026-01-01
endDate
requête
string

Ne garde que les documents émis jusqu'à cette date (incluse), au format ISO 8601. Une date seule (2026-01-31) vaut minuit UTC : pour inclure toute la journée, passez 2026-01-31T23:59:59Z. Une date illisible est refusée (erreur 400).

  • Exemple : 2026-01-31T23:59:59Z
ids
requête
string

Identifiants de factures séparés par des virgules (100 au maximum, les suivants sont ignorés). Prime sur status et clientId.

  • Exemple : jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe,jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhf
clientId
requête
string

Ne garde que les documents de ce client. Un identifiant inconnu, malformé ou appartenant à une autre entreprise donne une liste vide, jamais une erreur.

  • Exemple : k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae
Exemple de requête
curl "https://factupilot.fr/api/v1/invoices?page=1&limit=20&status=PAID" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY"
Réponses
  • 200

    Page de factures.

    application/json
    Exemple de réponse
    {
      "data": [
        {
          "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
          "number": "FA-2026-0042",
          "status": "PAID",
          "invoiceType": "STANDARD",
          "date": "2026-10-02T07:15:30.000Z",
          "dueDate": "2026-11-01T00:00:00.000Z",
          "subject": "Création du site vitrine",
          "totalHt": 860,
          "totalVat": 172,
          "totalTtc": 1032,
          "paidAt": "2026-10-09T13:41:05.000Z",
          "paidAmount": 1032,
          "paymentMethod": "Virement",
          "publicToken": "8f3a1c6e2b7d4954a0c1e6f3b8d27a95",
          "client": {
            "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
            "name": "Boulangerie Dupont",
            "email": "contact@boulangerie-dupont.fr"
          },
          "createdAt": "2026-10-02T07:15:30.512Z",
          "updatedAt": "2026-10-02T07:15:30.512Z"
        }
      ],
      "pagination": {
        "page": 1,
        "limit": 20,
        "total": 1,
        "pages": 1
      }
    }
    Champs de la réponse
    Champs de la réponse 200
    ChampTypeDescription
    data
    requis
    array<object>

    Facture, telle que renvoyée par la liste. Montants en euros.

    data[].id
    requis
    string

    Identifiant de la facture.

    data[].number
    requis
    string

    Numéro légal, par exemple FA-2026-0042.

    data[].status
    requis
    string

    DRAFT : brouillon. SENT : émise. PAID : payée. OVERDUE : en retard. CANCELLED : annulée. REFUNDED : remboursée. PARTIALLY_REFUNDED : partiellement remboursée.

    Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
    data[].invoiceType
    requis
    string

    Type de document. Un avoir (AVOIR) porte un montant négatif.

    Valeurs permises :STANDARDACOMPTESOLDEAVOIRPARTIELLE
    data[].date
    requis
    string (date-time)

    Date d'émission.

    data[].dueDate
    requis
    string (date-time)

    Date d'échéance.

    data[].subject
    string | null

    Objet de la facture.

    data[].totalHt
    requis
    number

    Total HT en euros.

    data[].totalVat
    requis
    number

    Total de TVA en euros.

    data[].totalTtc
    requis
    number

    Total TTC en euros.

    data[].paidAt
    string (date-time) | null

    Date de paiement, null si non payée.

    data[].paidAmount
    number | null

    Montant encaissé en euros. null tant que rien n'est encaissé (un encaissement à 0 est aussi rendu null).

    data[].paymentMethod
    string | null

    Mode de paiement constaté, texte libre.

    data[].publicToken
    string | null

    Jeton du portail client : le lien public durable de la facture (consultation, PDF, paiement) est https://factupilot.fr/facture/{publicToken}. null tant qu'aucun lien n'a été émis.

    data[].numeroEngagement
    string | null

    Numéro d'engagement de la commande publique (BT-13 du Factur-X). null si aucun.

    data[].codeServiceExecutant
    string | null

    Code du service exécutant (Chorus Pro, BT-10). null si aucun.

    data[].numeroMarche
    string | null

    Numéro de marché (BT-12 du Factur-X). null si aucun.

    data[].client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    data[].client.id
    requis
    string

    Identifiant du client.

    data[].client.name
    requis
    string
    data[].client.email
    requis
    string | null
    data[].createdAt
    requis
    string (date-time)
    data[].updatedAt
    string (date-time)

    Reprend createdAt : FactuPilot ne conserve pas de date de dernière modification exposée par l'API.

    pagination
    requis
    object

    Position dans la liste. pages vaut 0 quand la liste est vide.

    pagination.page
    requis
    integer

    Page courante (à partir de 1).

    • min. 1
    pagination.limit
    requis
    integer

    Nombre maximal d'éléments par page.

    • min. 1 · max. 100
    pagination.total
    requis
    integer

    Nombre total d'éléments correspondant aux filtres.

    • min. 0
    pagination.pages
    requis
    integer

    Nombre total de pages (total divisé par limit, arrondi au supérieur).

    • min. 0
  • 400

    Paramètre illisible : limit inférieur à 1 ou non numérique, page non numérique, status hors de la liste, startDate ou endDate illisible.

    application/json
    Exemples de réponse (4)

    Limite invalide

    {
      "error": "Le paramètre 'limit' doit être un entier compris entre 1 et 100."
    }

    Page non numérique

    {
      "error": "Le paramètre 'page' doit être un entier supérieur ou égal à 1."
    }

    Statut hors liste

    {
      "error": "Le paramètre 'status' doit valoir l'une des valeurs suivantes : DRAFT, SENT, PAID, OVERDUE, CANCELLED, REFUNDED, PARTIALLY_REFUNDED."
    }

    Date illisible

    {
      "error": "Le paramètre 'startDate' doit être une date valide au format ISO 8601, par exemple 2026-11-15 ou 2026-11-15T09:30:00Z."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée invoices:read, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · invoices:read requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Créer une facture

POST/invoices
Portée requise
invoices:write
Limite de débit
30 requêtes par minute

Crée une facture pour un client de l'entreprise. Par défaut elle naît en brouillon (DRAFT) et son numéro légal est déjà attribué, dans la séquence de l'entreprise, comme pour une facture créée depuis le tableau de bord. Avec "emettre": true, elle naît directement émise (SENT) : c'est le cas d'un logiciel métier qui facture sans intervention humaine. Un brouillon s'émet plus tard avec POST /invoices/{id}/emettre.

Une facture émise est intangible : elle ne se modifie plus, une correction passe par un avoir (exemple d'appel : section « Émission et transmission » de la description générale). Les mêmes exigences qu'à l'émission depuis FactuPilot s'appliquent donc dès la création : voir TVA à 0 % ci-dessous.

Transmission. Quand la facture est émise par cet appel et que l'entreprise a activé la transmission automatique, FactuPilot la remet à sa plateforme agréée AVANT de répondre, et le résultat figure dans data.transmission. Un refus ou un échec de transmission ne fait jamais échouer l'appel : la réponse reste 201, la facture est créée et émise. Un brouillon n'est jamais transmis.

Les totaux sont calculés par FactuPilot à partir des lignes, jamais fournis.

Commande publique. Pour un client ADMINISTRATION, la facture reprend son codeServiceExecutant et son numeroEngagementParDefaut. Vous pouvez envoyer numeroEngagement, codeServiceExecutant et numeroMarche (100 caractères au plus chacun) : ils l'emportent, champ par champ, sur ceux du client, et valent aussi pour un client privé (un numéro de commande, un marché). Ils sont inscrits dans le Factur-X et renvoyés dans la réponse (null si aucun). Une facture émise ne se modifie plus : fournissez-les dès la création.

Idempotence. Envoyez un en-tête Idempotency-Key (1 à 100 caractères visibles, sans espace : un UUID, ou la référence de la facture dans votre logiciel) pour pouvoir rejouer la requête sans risque, par exemple après un délai dépassé. Un second appel avec la même clé, pour la même entreprise, ne crée rien et ne consomme aucun numéro : il répond 200 (et non 201) avec la facture du premier appel telle qu'elle est maintenant, et l'en-tête Idempotent-Replayed: true. La clé désigne la facture, pas le corps : la même clé avec un autre corps rend quand même la facture d'origine. Une requête refusée (400, 404) n'enregistre pas la clé. Le rejeu d'une facture émise ne la retransmet jamais : data.transmission y donne l'état enregistré de sa transmission. Sans l'en-tête, rejouer la requête crée une seconde facture et consomme un nouveau numéro.

TVA à 0 %. Une ligne sans vatRate est à 0 %. Pour une entreprise assujettie à la TVA, chaque ligne à 0 % doit porter un motifTvaZero avant l'émission de la facture. Avec emettre à true, une ligne à 0 % sans motif est refusée (400, aucune facture créée, aucun numéro consommé). Un brouillon sans motif est accepté, mais ne pourra pas être émis tant que le motif manque : renseignez-le dès la création. Les entreprises en franchise en base n'ont pas de motif à fournir. Certains motifs exigent en plus un numéro de TVA intracommunautaire (ou un SIREN) sur la fiche du client.

Paramètres
Paramètres de Créer une facture
ParamètreTypeDescription
Idempotency-Key
en-tête
string

Clé d'idempotence choisie par votre logiciel : 1 à 100 caractères visibles, sans espace (un UUID, ou la référence de la facture chez vous). Rejouer la requête avec la même clé ne crée ni n'émet rien de plus : voir la description de l'opération.

  • 1 caractère min. · 100 caractères max.
  • Exemple : commande-2026-0042
Corps de la requête

Type de contenu : application/json · corps obligatoire

Champs du corps de Créer une facture
ChampTypeDescription
clientId
requis
string

Identifiant d'un client de votre entreprise (voir GET /clients).

items
requis
array<object>

Au moins une ligne. Ligne d'une facture ou d'un devis. Les totaux ne sont jamais fournis : FactuPilot les calcule. Attention : une ligne sans vatRate est à 0 % de TVA.

  • 1 élément min.
items[].description
requis
string

Libellé de la ligne (obligatoire). Une description vide ou absente est refusée (erreur 400).

  • 1 caractère min.
items[].quantity
number | string

Quantité, strictement positive, décimale permise. Une chaîne numérique est tolérée. Absente, elle vaut 1.

  • Par défaut : 1
  • supérieur à 0
items[].unitPriceHt
number | string

Prix unitaire HT en EUROS (décimal, par exemple 12.5), jamais en centimes. Arrondi au centime à l'enregistrement. Absent, il vaut 0.

  • Par défaut : 0
  • min. 0
items[].vatRate
number | string

Taux de TVA en pourcentage (20, 10, 5.5, 2.1 ou 0). Absent, il vaut 0. Une ligne à 0 % doit porter un motifTvaZero avant l'émission.

  • Par défaut : 0
  • min. 0 · max. 100
items[].motifTvaZero
string

Identifiant du motif d'une ligne à 0 % de TVA, choisi dans le catalogue officiel de FactuPilot (104 motifs : exonérations, export, livraisons intracommunautaires, autoliquidation, santé, enseignement, etc.). Le motif est imprimé sur le document et transmis dans le Factur-X. Seuls les identifiants de la liste sont acceptés, sinon l'API répond 400. Il n'est conservé que sur une ligne à 0 %.

104 valeurs permises
deboursvente-a-un-exportateur-sous-attestation-d-achat-envente-de-biens-places-en-entrepot-ou-regime-suspensifcommission-d-un-intermediaire-sur-une-operation-hors-ueservices-lies-a-des-biens-en-entrepot-sous-douaneprestation-de-services-a-un-professionnel-de-l-uevente-avec-installation-chez-un-client-pro-de-l-ueexport-hors-uevente-detaxee-a-un-voyageur-hors-uelivraison-intracommunautaireprestation-de-services-a-un-professionnel-hors-ueprestation-immaterielle-a-un-particulier-hors-ueautoliquidation-code-francais-article-283-2-du-cgisous-traitance-de-travaux-du-batiment-btpvente-de-dechets-ferraille-et-matieres-de-recuperationvente-d-or-brut-ou-semi-ouvre-a-un-professionnelcession-de-quotas-carbone-ou-garanties-d-origineservices-de-communications-electroniques-entrelivraison-d-electricite-ou-de-gaz-a-un-professionnelformation-professionnelle-continue-organisme-avecenseignement-scolaire-superieur-ou-a-distancecours-particuliers-par-un-enseignant-independantenseignement-et-formation-professionnelle-code-europeencours-particuliers-donnes-par-un-enseignantsoins-medicaux-et-paramedicauxprotheses-dentaires-dentiste-prothesisteanalyses-de-biologie-medicalefrais-d-hospitalisation-en-etablissement-de-sante-privesoins-en-ehpad-pris-en-charge-au-forfaitorganes-sang-et-lait-humainstransport-de-malades-en-ambulance-ou-vslhospitalisation-et-soins-en-etablissement-de-santesoins-medicaux-et-paramedicaux-code-europeenorganes-sang-et-lait-humains-code-europeenprotheses-dentaires-et-prothesistes-dentairestransport-de-malades-ou-blesses-en-ambulancecreche-ou-micro-creche-autorisee-moins-de-3-ansmandataire-judiciaire-a-la-protection-des-majeursservices-d-un-syndicat-ou-parti-a-ses-membres-cotisationassociation-services-sportifs-culturels-ou-sociaux-auxoeuvre-sociale-ou-philanthropique-sans-but-lucratifmanifestation-de-bienfaisance-ou-de-soutien-6-par-anassociation-intermediaire-d-insertionaide-a-domicile-par-une-association-agreeelieu-de-vie-et-d-accueil-action-socialearticles-fabriques-par-des-travailleurs-handicapesaide-sociale-et-services-sociaux-code-europeenprotection-de-l-enfance-et-garde-de-jeunes-enfantsmise-a-disposition-de-personnel-religieux-ouservices-d-un-organisme-politique-syndical-ou-religieuxservices-sportifs-d-une-association-a-ses-membresservices-culturels-d-un-organisme-public-ou-reconnumanifestations-de-soutien-au-profit-d-une-associationvente-d-un-terrain-non-constructiblevente-d-un-immeuble-acheve-depuis-plus-de-5-anslocation-de-terres-et-batiments-agricoleslocation-par-bail-conferant-un-droit-reellocation-nue-de-locaux-ou-de-terrain-non-amenageconcession-de-droits-sur-terres-ou-locaux-nuslocation-meublee-d-habitation-sans-services-hoteliersagence-de-voyages-part-realisee-hors-uerevente-d-un-bien-d-occasion-regime-de-la-margerevente-d-une-oeuvre-d-art-regime-de-la-margerevente-d-objets-de-collection-ou-d-antiquite-margevente-d-or-d-investissement-lingots-piecesoperations-a-terme-sur-marchandises-marche-reglementecredit-et-courtage-en-credit-iobsppaiements-virements-change-de-devisescession-de-titres-actions-ou-parts-socialesgestion-de-fonds-d-investissement-opcvm-opcassurance-et-courtage-en-assuranceassurance-finance-immobilier-code-europeen-globalservices-lies-a-une-importation-transport-transitservices-lies-a-une-exportation-de-biensnavires-et-avions-du-trafic-international-code-globalavitaillement-des-navires-de-haute-meravitaillement-des-navires-de-guerrevente-reparation-location-de-navires-de-haute-merservices-pour-les-besoins-directs-des-naviresavitaillement-des-avions-de-lignes-internationalesvente-reparation-location-d-avions-de-lignesservices-pour-les-besoins-directs-des-avionsentraide-entre-agriculteursvente-de-sa-peche-par-un-pecheur-professionnelexonerations-propres-a-la-guadeloupe-martinique-etvente-de-biens-expedies-vers-l-outre-mer-domdiplomates-organismes-internationaux-armees-alliees-codevente-a-une-ambassade-ou-un-consulat-autre-pays-uevente-aux-institutions-et-agences-de-l-union-europeennevente-a-un-organisme-international-autre-pays-uevente-aux-forces-armees-alliees-stationnees-en-francevente-aux-forces-armees-otan-d-un-autre-pays-uevente-aux-forces-britanniques-stationnees-a-chyprerevente-d-un-bien-d-equipement-sans-tva-recupereetravaux-sur-monuments-aux-morts-et-sepultures-de-guerreservice-universel-postal-la-posteaide-apres-une-catastrophe-dans-l-union-arretegroupement-de-moyens-refacturation-des-frais-communsvente-de-timbres-poste-ou-fiscaux-a-leur-valeur-facialeorganisation-de-jeux-de-casino-ou-de-cercleproduit-des-loteries-paris-et-jeux-en-ligneexoneration-d-interet-general-code-europeen-globalservice-postal-universel-code-europeengroupement-de-moyens-refacturant-les-frais-communs
subject
string

Objet de la facture.

notes
string

Notes imprimées sur la facture.

dueDate
string

Date d'échéance, ISO 8601 (2026-11-15 ou 2026-11-15T00:00:00Z). Par défaut, 30 jours après la création. Une date illisible est refusée (erreur 400).

numeroEngagement
string

Numéro d'engagement de la commande publique (BT-13 du Factur-X). Absent : repris du client ADMINISTRATION (numeroEngagementParDefaut). Fourni, il l'emporte. Au-delà de 100 caractères : erreur 400.

  • 100 caractères max.
codeServiceExecutant
string

Code du service exécutant (Chorus Pro, BT-10). Absent : repris du client ADMINISTRATION. Fourni, il l'emporte. Au-delà de 100 caractères : erreur 400.

  • 100 caractères max.
numeroMarche
string

Numéro de marché (BT-12 du Factur-X). Jamais repris du client : à fournir pour chaque facture. Au-delà de 100 caractères : erreur 400.

  • 100 caractères max.
emettre
boolean

true : la facture est créée directement ÉMISE (SENT), avec les mêmes exigences qu'à l'émission depuis FactuPilot (motif obligatoire pour une ligne à 0 % chez une entreprise assujettie à la TVA), puis transmise à la plateforme agréée si l'entreprise a activé la transmission automatique. false ou absent : brouillon (DRAFT).

  • Par défaut : false

Autre exemple de corps : Ligne à 0 % de TVA avec son motif

{
  "clientId": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
  "subject": "Prestation de conseil pour un client européen",
  "items": [
    {
      "description": "Conseil en stratégie digitale, forfait mensuel",
      "quantity": 1,
      "unitPriceHt": 1800,
      "vatRate": 0,
      "motifTvaZero": "prestation-de-services-a-un-professionnel-de-l-ue"
    }
  ]
}
Exemple de requête

Corps : Facture avec TVA à 20 %

curl -X POST "https://factupilot.fr/api/v1/invoices" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "clientId": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
  "subject": "Création du site vitrine",
  "notes": "Paiement à 30 jours par virement.",
  "dueDate": "2026-11-15",
  "items": [
    {
      "description": "Conception graphique de la maquette",
      "quantity": 1,
      "unitPriceHt": 650,
      "vatRate": 20
    },
    {
      "description": "Intégration et mise en ligne",
      "quantity": 3.5,
      "unitPriceHt": 60,
      "vatRate": 20
    }
  ]
}'
Réponses
  • 200

    Rejeu reconnu : une facture a déjà été créée avec cette Idempotency-Key. Rien n'est créé, numéroté ni transmis ; la réponse donne cette facture telle qu'elle est maintenant (avec l'état enregistré de sa transmission si elle n'est plus un brouillon).

    application/json
    • En-tête Idempotent-Replayed : true : la requête a été reconnue comme un rejeu (même Idempotency-Key), rien n'a été créé ni transmis.
    Exemple de réponse
    {
      "data": {
        "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
        "number": "FA-2026-0044",
        "status": "SENT",
        "date": "2026-10-16T14:05:09.771Z",
        "dueDate": "2026-11-15T14:05:09.771Z",
        "subject": "Livraison du 16 octobre",
        "totalHt": 420,
        "totalVat": 84,
        "totalTtc": 504,
        "client": {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Boulangerie Dupont",
          "email": "contact@boulangerie-dupont.fr"
        },
        "items": [
          {
            "id": "j97m3x5c8v2q4n6w1d0h7rtb9y4z2kae",
            "description": "Transport Perpignan - Toulouse",
            "quantity": 1,
            "unitPriceHt": 420,
            "vatRate": 20,
            "totalHt": 420
          }
        ],
        "createdAt": "2026-10-16T14:05:09.903Z",
        "transmission": {
          "statut": "transmise",
          "plateforme": "superpdp",
          "statutPlateforme": "api:uploaded",
          "message": "Facture transmise à Super PDP."
        }
      }
    }
    Champs de la réponse
    Champs de la réponse 200
    ChampTypeDescription
    data
    requis
    object

    Facture venant d'être créée ou émise, relue depuis la base : totaux et lignes sont ceux calculés par FactuPilot. Les références de la commande publique (numeroEngagement, codeServiceExecutant, numeroMarche) figurent dans la réponse de POST /invoices (création et rejeu) et dans celle de POST /invoices/{id}/emettre. L'objet transmission n'est présent que lorsque l'appel a ÉMIS la facture, ou dans la réponse d'un rejeu (Idempotency-Key) d'une facture qui n'est plus un brouillon.

    data.id
    requis
    string
    data.number
    requis
    string

    Numéro légal attribué à la création.

    data.status
    requis
    string

    DRAFT à la création d'un brouillon, SENT quand l'appel a émis la facture. La réponse d'un rejeu (Idempotency-Key) donne le statut ACTUEL de la facture, qui a pu changer depuis (payée, en retard...).

    Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
    data.transmission
    object

    Issue de la transmission automatique à la plateforme agréée de l'entreprise, présente quand l'appel a ÉMIS la facture. Dans la réponse d'un rejeu (même Idempotency-Key) d'une facture émise, c'est l'état ENREGISTRÉ de sa transmission : un rejeu ne retransmet jamais. Un refus ou un échec de transmission ne fait JAMAIS échouer l'appel : la facture est créée et émise dans tous les cas.

    data.transmission.statut
    requis
    string

    transmise : la facture a été remise à la plateforme. refusee : une règle l'interdit, rien n'est parti (plan, plateforme en dépôt manuel, adresse électronique de l'acheteur manquante). echec : incident pendant la transmission, à relancer depuis FactuPilot. desactivee : la transmission automatique n'est pas activée pour l'entreprise (ou son plan n'inclut plus l'API), ou elle est coupée sur ce serveur. inconnue : seulement dans la réponse d'un rejeu, quand aucune transmission n'est encore enregistrée (la première requête est peut-être encore en cours) ; rien n'est retransmis.

    Valeurs permises :transmiserefuseeechecdesactiveeinconnue
    data.transmission.plateforme
    requis
    string | null

    Plateforme agréée de l'entreprise : superpdp, iopole ou manuel (dépôt manuel). null si elle n'a pas pu être lue.

    data.transmission.statutPlateforme
    requis
    string | null

    Statut brut renvoyé par la plateforme à la remise (par exemple api:uploaded). null si rien n'a été remis.

    data.transmission.message
    requis
    string

    Explication lisible, en français.

    data.date
    requis
    string (date-time)
    data.dueDate
    requis
    string (date-time)
    data.subject
    string | null
    data.totalHt
    requis
    number
    data.totalVat
    requis
    number
    data.totalTtc
    requis
    number
    data.numeroEngagement
    string | null

    Numéro d'engagement de la commande publique (BT-13 du Factur-X). null si aucun.

    data.codeServiceExecutant
    string | null

    Code du service exécutant (Chorus Pro, BT-10). null si aucun.

    data.numeroMarche
    string | null

    Numéro de marché (BT-12 du Factur-X). null si aucun.

    data.client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    data.client.id
    requis
    string

    Identifiant du client.

    data.client.name
    requis
    string
    data.client.email
    requis
    string | null
    data.items
    requis
    array<object>

    Ligne de facture telle qu'enregistrée. Montants en euros.

    data.items[].id
    requis
    string
    data.items[].description
    requis
    string
    data.items[].quantity
    requis
    number
    data.items[].unitPriceHt
    requis
    number

    Prix unitaire HT en euros.

    data.items[].vatRate
    requis
    number

    Taux de TVA en pourcentage.

    data.items[].totalHt
    requis
    number

    Total HT de la ligne en euros.

    data.createdAt
    requis
    string (date-time)
  • 201

    Facture créée, avec son numéro légal : en brouillon, ou émise si emettre valait true (avec alors l'objet transmission).

    application/json
    Exemples de réponse (2)

    Brouillon

    {
      "data": {
        "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
        "number": "FA-2026-0043",
        "status": "DRAFT",
        "date": "2026-10-16T09:12:44.318Z",
        "dueDate": "2026-11-15T00:00:00.000Z",
        "subject": "Création du site vitrine",
        "totalHt": 860,
        "totalVat": 172,
        "totalTtc": 1032,
        "numeroEngagement": null,
        "codeServiceExecutant": null,
        "numeroMarche": null,
        "client": {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Boulangerie Dupont",
          "email": "contact@boulangerie-dupont.fr"
        },
        "items": [
          {
            "id": "j97m3x5c8v2q4n6w1d0h7rtb9y4z2kae",
            "description": "Conception graphique de la maquette",
            "quantity": 1,
            "unitPriceHt": 650,
            "vatRate": 20,
            "totalHt": 650
          },
          {
            "id": "j9708q2v5m3x7c1n4w6d9h8rtb2y5zje",
            "description": "Intégration et mise en ligne",
            "quantity": 3.5,
            "unitPriceHt": 60,
            "vatRate": 20,
            "totalHt": 210
          }
        ],
        "createdAt": "2026-10-16T09:12:44.527Z"
      }
    }

    Émise et transmise

    {
      "data": {
        "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
        "number": "FA-2026-0044",
        "status": "SENT",
        "date": "2026-10-16T14:05:09.771Z",
        "dueDate": "2026-11-15T14:05:09.771Z",
        "subject": "Livraison du 16 octobre",
        "totalHt": 420,
        "totalVat": 84,
        "totalTtc": 504,
        "client": {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Boulangerie Dupont",
          "email": "contact@boulangerie-dupont.fr"
        },
        "items": [
          {
            "id": "j97m3x5c8v2q4n6w1d0h7rtb9y4z2kae",
            "description": "Transport Perpignan - Toulouse",
            "quantity": 1,
            "unitPriceHt": 420,
            "vatRate": 20,
            "totalHt": 420
          }
        ],
        "createdAt": "2026-10-16T14:05:09.903Z",
        "transmission": {
          "statut": "transmise",
          "plateforme": "superpdp",
          "statutPlateforme": "api:uploaded",
          "message": "Facture transmise à Super PDP."
        }
      }
    }
    Champs de la réponse
    Champs de la réponse 201
    ChampTypeDescription
    data
    requis
    object

    Facture venant d'être créée ou émise, relue depuis la base : totaux et lignes sont ceux calculés par FactuPilot. Les références de la commande publique (numeroEngagement, codeServiceExecutant, numeroMarche) figurent dans la réponse de POST /invoices (création et rejeu) et dans celle de POST /invoices/{id}/emettre. L'objet transmission n'est présent que lorsque l'appel a ÉMIS la facture, ou dans la réponse d'un rejeu (Idempotency-Key) d'une facture qui n'est plus un brouillon.

    data.id
    requis
    string
    data.number
    requis
    string

    Numéro légal attribué à la création.

    data.status
    requis
    string

    DRAFT à la création d'un brouillon, SENT quand l'appel a émis la facture. La réponse d'un rejeu (Idempotency-Key) donne le statut ACTUEL de la facture, qui a pu changer depuis (payée, en retard...).

    Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
    data.transmission
    object

    Issue de la transmission automatique à la plateforme agréée de l'entreprise, présente quand l'appel a ÉMIS la facture. Dans la réponse d'un rejeu (même Idempotency-Key) d'une facture émise, c'est l'état ENREGISTRÉ de sa transmission : un rejeu ne retransmet jamais. Un refus ou un échec de transmission ne fait JAMAIS échouer l'appel : la facture est créée et émise dans tous les cas.

    data.transmission.statut
    requis
    string

    transmise : la facture a été remise à la plateforme. refusee : une règle l'interdit, rien n'est parti (plan, plateforme en dépôt manuel, adresse électronique de l'acheteur manquante). echec : incident pendant la transmission, à relancer depuis FactuPilot. desactivee : la transmission automatique n'est pas activée pour l'entreprise (ou son plan n'inclut plus l'API), ou elle est coupée sur ce serveur. inconnue : seulement dans la réponse d'un rejeu, quand aucune transmission n'est encore enregistrée (la première requête est peut-être encore en cours) ; rien n'est retransmis.

    Valeurs permises :transmiserefuseeechecdesactiveeinconnue
    data.transmission.plateforme
    requis
    string | null

    Plateforme agréée de l'entreprise : superpdp, iopole ou manuel (dépôt manuel). null si elle n'a pas pu être lue.

    data.transmission.statutPlateforme
    requis
    string | null

    Statut brut renvoyé par la plateforme à la remise (par exemple api:uploaded). null si rien n'a été remis.

    data.transmission.message
    requis
    string

    Explication lisible, en français.

    data.date
    requis
    string (date-time)
    data.dueDate
    requis
    string (date-time)
    data.subject
    string | null
    data.totalHt
    requis
    number
    data.totalVat
    requis
    number
    data.totalTtc
    requis
    number
    data.numeroEngagement
    string | null

    Numéro d'engagement de la commande publique (BT-13 du Factur-X). null si aucun.

    data.codeServiceExecutant
    string | null

    Code du service exécutant (Chorus Pro, BT-10). null si aucun.

    data.numeroMarche
    string | null

    Numéro de marché (BT-12 du Factur-X). null si aucun.

    data.client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    data.client.id
    requis
    string

    Identifiant du client.

    data.client.name
    requis
    string
    data.client.email
    requis
    string | null
    data.items
    requis
    array<object>

    Ligne de facture telle qu'enregistrée. Montants en euros.

    data.items[].id
    requis
    string
    data.items[].description
    requis
    string
    data.items[].quantity
    requis
    number
    data.items[].unitPriceHt
    requis
    number

    Prix unitaire HT en euros.

    data.items[].vatRate
    requis
    number

    Taux de TVA en pourcentage.

    data.items[].totalHt
    requis
    number

    Total HT de la ligne en euros.

    data.createdAt
    requis
    string (date-time)
  • 400

    Requête invalide : en-tête Idempotency-Key mal formé, corps illisible, clientId absent, aucune ligne dans items, ligne invalide (description vide, quantité, prix, taux de TVA, motif inconnu), date dueDate illisible, champ du mauvais type, référence de la commande publique de plus de 100 caractères, ou, avec emettre à true, ligne à 0 % sans motif chez une entreprise assujettie.

    application/json
    Exemples de réponse (11)

    Corps illisible

    {
      "error": "Le corps de la requête n'est pas un JSON valide : envoyez un objet JSON avec l'en-tête Content-Type: application/json."
    }

    clientId absent

    {
      "error": "Le champ 'clientId' est requis"
    }

    Aucune ligne

    {
      "error": "Au moins un article (items) est requis"
    }

    Description vide

    {
      "error": "Ligne 1 : description requise (texte non vide)"
    }

    Quantité invalide

    {
      "error": "Ligne 2 : quantité invalide (doit être > 0)"
    }

    Motif inconnu

    {
      "error": "Ligne 1 : motifTvaZero inconnu (identifiant du catalogue des motifs du 0 %)"
    }

    Date d'échéance illisible

    {
      "error": "Le champ 'dueDate' doit être une date valide au format ISO 8601, par exemple 2026-11-15 ou 2026-11-15T09:30:00Z."
    }

    emettre n'est pas un booléen

    {
      "error": "Le champ 'emettre' doit être un booléen (true ou false)."
    }

    Référence de plus de 100 caractères

    {
      "error": "Le champ 'numeroEngagement' ne doit pas dépasser 100 caractères."
    }

    Idempotency-Key mal formé

    {
      "error": "L'en-tête 'Idempotency-Key' doit compter de 1 à 100 caractères visibles (lettres, chiffres, tirets...), sans espace : par exemple un UUID."
    }

    Ligne à 0 % sans motif, avec emettre: true

    {
      "error": "Cette entreprise est réglée comme assujettie à la TVA dans FactuPilot : envoyez items[].motifTvaZero (identifiant du catalogue des motifs du 0 % de TVA) sur chaque ligne à 0 %, ou créez la facture sans 'emettre' pour la compléter depuis FactuPilot. Si l'entreprise est en franchise en base de TVA (TVA non applicable, article 293 B du CGI), c'est son régime de TVA qu'il faut corriger dans FactuPilot (Paramètres, Régime de TVA) : aucun motif n'est alors demandé. Aucune facture n'a été créée."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée invoices:write, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · invoices:write requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 404

    Le client n'existe pas dans votre entreprise.

    application/json
    Exemple de réponse
    {
      "error": "Client introuvable"
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Émettre un brouillon

POST/invoices/{id}/emettre
Portée requise
invoices:write
Limite de débit
30 requêtes par minute

Émet une facture en brouillon (DRAFT) créée par l'API (POST /invoices) : elle passe en SENT, avec les mêmes exigences qu'à l'émission depuis le bouton « Émettre » de FactuPilot. L'appel n'attend aucun corps.

Brouillons concernés. Seuls les brouillons créés par l'API s'émettent ici. Un brouillon saisi dans FactuPilot (tableau de bord, échéancier de prélèvements), peut-être en cours de modification par quelqu'un, répond 409 : il s'émet depuis FactuPilot. Les brouillons créés par l'API avant la mise en service de cette opération ne portent pas cette marque : ils s'émettent eux aussi depuis FactuPilot.

Règles. Pour une entreprise assujettie à la TVA, chaque ligne à 0 % doit porter un motifTvaZero. Si un motif manque, l'appel répond 400 et la facture reste en brouillon : un brouillon créé par l'API ne peut pas être complété par l'API, complétez-le dans FactuPilot puis émettez-le, ou recréez-le avec les motifs. Une facture déjà émise, payée, en retard ou annulée répond 409 : une facture émise est intangible, une correction passe par un avoir.

Transmission. Comme à la création avec "emettre": true, si l'entreprise a activé la transmission automatique, la facture est remise à sa plateforme agréée AVANT la réponse, et le résultat figure dans data.transmission. Un refus ou un échec de transmission ne fait jamais échouer l'émission : la réponse reste 200.

Rejeu. Sans en-tête, une seconde émission de la même facture répond 409, et deux appels simultanés n'en émettent qu'un : il n'y a jamais de seconde transmission. Avec un en-tête Idempotency-Key, un appel sur une facture déjà émise (par exemple le rejeu d'un appel dont la réponse s'est perdue) répond 200 avec la facture et l'état ENREGISTRÉ de sa transmission, et l'en-tête Idempotent-Replayed: true, sans rien retransmettre. Pour cette opération, la clé n'est pas mémorisée : le rejeu se reconnaît à l'état de la facture. Une facture annulée répond toujours 409.

Paramètres
Paramètres de Émettre un brouillon
ParamètreTypeDescription
id
cheminrequis
string

Identifiant du brouillon (champ id renvoyé à sa création).

  • Exemple : jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe
Idempotency-Key
en-tête
string

Clé d'idempotence choisie par votre logiciel : 1 à 100 caractères visibles, sans espace (un UUID, ou la référence de la facture chez vous). Rejouer la requête avec la même clé ne crée ni n'émet rien de plus : voir la description de l'opération.

  • 1 caractère min. · 100 caractères max.
  • Exemple : commande-2026-0042
Corps de la requête

Type de contenu : application/json · corps facultatif

Aucun champ n'est attendu : un corps éventuel est ignoré. Envoyez {} si votre client HTTP exige un corps.

Exemple de requête
curl -X POST "https://factupilot.fr/api/v1/invoices/jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe/emettre" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Réponses
  • 200

    Facture émise, avec son état de transmission. Avec Idempotency-Key, aussi la réponse d'un rejeu sur une facture déjà émise (en-tête Idempotent-Replayed: true, état de transmission enregistré, rien n'est retransmis).

    application/json
    • En-tête Idempotent-Replayed : true : la requête a été reconnue comme un rejeu (même Idempotency-Key), rien n'a été créé ni transmis.
    Exemple de réponse
    {
      "data": {
        "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
        "number": "FA-2026-0044",
        "status": "SENT",
        "date": "2026-10-16T14:05:09.771Z",
        "dueDate": "2026-11-15T14:05:09.771Z",
        "subject": "Livraison du 16 octobre",
        "totalHt": 420,
        "totalVat": 84,
        "totalTtc": 504,
        "client": {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Boulangerie Dupont",
          "email": "contact@boulangerie-dupont.fr"
        },
        "items": [
          {
            "id": "j97m3x5c8v2q4n6w1d0h7rtb9y4z2kae",
            "description": "Transport Perpignan - Toulouse",
            "quantity": 1,
            "unitPriceHt": 420,
            "vatRate": 20,
            "totalHt": 420
          }
        ],
        "createdAt": "2026-10-16T14:05:09.903Z",
        "transmission": {
          "statut": "transmise",
          "plateforme": "superpdp",
          "statutPlateforme": "api:uploaded",
          "message": "Facture transmise à Super PDP."
        }
      }
    }
    Champs de la réponse
    Champs de la réponse 200
    ChampTypeDescription
    data
    requis
    object

    Facture venant d'être créée ou émise, relue depuis la base : totaux et lignes sont ceux calculés par FactuPilot. Les références de la commande publique (numeroEngagement, codeServiceExecutant, numeroMarche) figurent dans la réponse de POST /invoices (création et rejeu) et dans celle de POST /invoices/{id}/emettre. L'objet transmission n'est présent que lorsque l'appel a ÉMIS la facture, ou dans la réponse d'un rejeu (Idempotency-Key) d'une facture qui n'est plus un brouillon.

    data.id
    requis
    string
    data.number
    requis
    string

    Numéro légal attribué à la création.

    data.status
    requis
    string

    DRAFT à la création d'un brouillon, SENT quand l'appel a émis la facture. La réponse d'un rejeu (Idempotency-Key) donne le statut ACTUEL de la facture, qui a pu changer depuis (payée, en retard...).

    Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
    data.transmission
    object

    Issue de la transmission automatique à la plateforme agréée de l'entreprise, présente quand l'appel a ÉMIS la facture. Dans la réponse d'un rejeu (même Idempotency-Key) d'une facture émise, c'est l'état ENREGISTRÉ de sa transmission : un rejeu ne retransmet jamais. Un refus ou un échec de transmission ne fait JAMAIS échouer l'appel : la facture est créée et émise dans tous les cas.

    data.transmission.statut
    requis
    string

    transmise : la facture a été remise à la plateforme. refusee : une règle l'interdit, rien n'est parti (plan, plateforme en dépôt manuel, adresse électronique de l'acheteur manquante). echec : incident pendant la transmission, à relancer depuis FactuPilot. desactivee : la transmission automatique n'est pas activée pour l'entreprise (ou son plan n'inclut plus l'API), ou elle est coupée sur ce serveur. inconnue : seulement dans la réponse d'un rejeu, quand aucune transmission n'est encore enregistrée (la première requête est peut-être encore en cours) ; rien n'est retransmis.

    Valeurs permises :transmiserefuseeechecdesactiveeinconnue
    data.transmission.plateforme
    requis
    string | null

    Plateforme agréée de l'entreprise : superpdp, iopole ou manuel (dépôt manuel). null si elle n'a pas pu être lue.

    data.transmission.statutPlateforme
    requis
    string | null

    Statut brut renvoyé par la plateforme à la remise (par exemple api:uploaded). null si rien n'a été remis.

    data.transmission.message
    requis
    string

    Explication lisible, en français.

    data.date
    requis
    string (date-time)
    data.dueDate
    requis
    string (date-time)
    data.subject
    string | null
    data.totalHt
    requis
    number
    data.totalVat
    requis
    number
    data.totalTtc
    requis
    number
    data.numeroEngagement
    string | null

    Numéro d'engagement de la commande publique (BT-13 du Factur-X). null si aucun.

    data.codeServiceExecutant
    string | null

    Code du service exécutant (Chorus Pro, BT-10). null si aucun.

    data.numeroMarche
    string | null

    Numéro de marché (BT-12 du Factur-X). null si aucun.

    data.client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    data.client.id
    requis
    string

    Identifiant du client.

    data.client.name
    requis
    string
    data.client.email
    requis
    string | null
    data.items
    requis
    array<object>

    Ligne de facture telle qu'enregistrée. Montants en euros.

    data.items[].id
    requis
    string
    data.items[].description
    requis
    string
    data.items[].quantity
    requis
    number
    data.items[].unitPriceHt
    requis
    number

    Prix unitaire HT en euros.

    data.items[].vatRate
    requis
    number

    Taux de TVA en pourcentage.

    data.items[].totalHt
    requis
    number

    Total HT de la ligne en euros.

    data.createdAt
    requis
    string (date-time)
  • 400

    La facture ne peut pas être émise telle quelle : ligne à 0 % sans motif chez une entreprise assujettie à la TVA, ou numéro de TVA manquant. Elle reste en brouillon. Aussi : en-tête Idempotency-Key mal formé.

    application/json
    Exemples de réponse (2)

    Ligne à 0 % sans motif

    {
      "error": "Cette entreprise est réglée comme assujettie à la TVA dans FactuPilot : la facture contient une ligne à 0 % sans motif. Complétez le brouillon dans FactuPilot, ou recréez-le avec items[].motifTvaZero (identifiant du catalogue des motifs du 0 % de TVA). Si l'entreprise est en franchise en base de TVA (TVA non applicable, article 293 B du CGI), c'est son régime de TVA qu'il faut corriger dans FactuPilot (Paramètres, Régime de TVA) : aucun motif n'est alors demandé. La facture reste en brouillon."
    }

    Idempotency-Key mal formé

    {
      "error": "L'en-tête 'Idempotency-Key' doit compter de 1 à 100 caractères visibles (lettres, chiffres, tirets...), sans espace : par exemple un UUID."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée invoices:write, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · invoices:write requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 404

    Facture inconnue, identifiant malformé, ou facture d'une autre entreprise.

    application/json
    Exemple de réponse
    {
      "error": "Facture introuvable"
    }
  • 409

    L'état de la facture refuse l'émission : déjà émise (payée, en retard...) sans Idempotency-Key, annulée, ou brouillon qui n'a pas été créé par l'API (il s'émet depuis FactuPilot).

    application/json
    Exemples de réponse (3)

    Déjà émise

    {
      "error": "Cette facture est déjà émise : elle ne peut plus l'être."
    }

    Annulée

    {
      "error": "Cette facture est annulée : elle ne peut pas être émise."
    }

    Brouillon saisi dans FactuPilot

    {
      "error": "Ce brouillon n'a pas été créé par l'API : il s'émet depuis FactuPilot, pas par une clé d'API."
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Télécharger le PDF d'une facture

GET/invoices/{id}/pdf
Portée requise
invoices:read
Limite de débit
30 requêtes par minute

Génère et renvoie le PDF de la facture, y compris d'un brouillon. Le rendu est coûteux : la route est limitée à 30 appels par minute.

Le fichier est produit à chaque appel et n'est pas mis en cache (Cache-Control: no-store). Son nom est le numéro de la facture (FA-2026-0042.pdf).

Remarque : sur cette route, une clé sans la portée invoices:read reçoit un 401 (message « Non autorise ») et non un 403.

Paramètres
Paramètres de Télécharger le PDF d'une facture
ParamètreTypeDescription
id
cheminrequis
string

Identifiant de la facture (champ id renvoyé par GET /invoices).

  • Exemple : jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe
Exemple de requête
curl "https://factupilot.fr/api/v1/invoices/jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe/pdf" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY" \
  --output reponse.pdf
Réponses
  • 200

    Le PDF de la facture.

    application/pdf

    Le corps de la réponse est un fichier binaire.

    • En-tête Content-Disposition : Nom du fichier : le numéro de la facture. (exemple : inline; filename="FA-2026-0042.pdf")
    • En-tête Cache-Control (exemple : no-store, no-cache, must-revalidate)
  • 401

    Clé absente, invalide, expirée, ou sans la portée invoices:read (ce dernier cas rend ici 401 et non 403).

    application/json
    Exemples de réponse (2)

    Clé absente ou invalide

    {
      "error": "Non autorise"
    }

    Portée invoices:read manquante

    {
      "error": "Non autorise"
    }
  • 403

    L'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemple de réponse
    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 404

    Facture inconnue, identifiant malformé, ou facture d'une autre entreprise.

    application/json
    Exemple de réponse
    {
      "error": "Facture introuvable"
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Devis

Devis : liste et création en brouillon.

Lister les devis

GET/quotes
Portée requise
quotes:read
Limite de débit
120 requêtes par minute

Renvoie les devis non archivés de l'entreprise, du plus récent au plus ancien (date d'émission). Les brouillons sont inclus ; les anciennes versions d'un devis révisé sont exclues.

Filtres : status, startDate et endDate (sur la date d'émission) et clientId. Jusqu'à 2000 devis sont lus avant découpe en pages : filtrez par date ou statut pour parcourir un long historique.

Paramètres
Paramètres de Lister les devis
ParamètreTypeDescription
page
requête
integer

Numéro de page, à partir de 1. Une valeur inférieure à 1 est ramenée à 1 ; une valeur non numérique est refusée (erreur 400).

  • Par défaut : 1
  • min. 1
  • Exemple : 1
limit
requête
integer

Nombre d'éléments par page, de 1 à 100 (20 par défaut). Une valeur supérieure à 100 est ramenée à 100 ; une valeur inférieure à 1 ou non numérique est refusée (erreur 400).

  • Par défaut : 20
  • min. 1 · max. 100
  • Exemple : 20
status
requête
string

Ne garde que les devis de ce statut. Une valeur hors de la liste est refusée (erreur 400).

Valeurs permises :DRAFTSENTACCEPTEDREFUSEDEXPIRED
  • Exemple : ACCEPTED
startDate
requête
string

Ne garde que les documents émis à partir de cette date (incluse), au format ISO 8601. Une date seule (2026-01-01) vaut minuit UTC. Une date illisible est refusée (erreur 400).

  • Exemple : 2026-01-01
endDate
requête
string

Ne garde que les documents émis jusqu'à cette date (incluse), au format ISO 8601. Une date seule (2026-01-31) vaut minuit UTC : pour inclure toute la journée, passez 2026-01-31T23:59:59Z. Une date illisible est refusée (erreur 400).

  • Exemple : 2026-01-31T23:59:59Z
clientId
requête
string

Ne garde que les documents de ce client. Un identifiant inconnu, malformé ou appartenant à une autre entreprise donne une liste vide, jamais une erreur.

  • Exemple : k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae
Exemple de requête
curl "https://factupilot.fr/api/v1/quotes?page=1&limit=20&status=ACCEPTED" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY"
Réponses
  • 200

    Page de devis.

    application/json
    Exemple de réponse
    {
      "data": [
        {
          "id": "jh72v9c4x6m1q8n3w5d0r7tb4y2z9kae",
          "number": "DEV-2026-0017",
          "status": "ACCEPTED",
          "date": "2026-10-05T10:20:00.000Z",
          "validUntil": "2026-11-04T10:20:00.000Z",
          "subject": "Refonte du site vitrine",
          "totalHt": 1500,
          "totalVat": 300,
          "totalTtc": 1800,
          "sentAt": "2026-10-05T10:25:41.000Z",
          "acceptedAt": "2026-10-07T16:02:19.000Z",
          "refusedAt": null,
          "signatureToken": "b41c9d07e5a2486f93a0d1c7e82f5b36",
          "client": {
            "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
            "name": "Boulangerie Dupont",
            "email": "contact@boulangerie-dupont.fr"
          },
          "createdAt": "2026-10-05T10:20:00.311Z",
          "updatedAt": "2026-10-05T10:20:00.311Z"
        }
      ],
      "pagination": {
        "page": 1,
        "limit": 20,
        "total": 1,
        "pages": 1
      }
    }
    Champs de la réponse
    Champs de la réponse 200
    ChampTypeDescription
    data
    requis
    array<object>

    Devis, tel que renvoyé par la liste. Montants en euros. Les devis révisés (anciennes versions) ne sont pas listés.

    data[].id
    requis
    string

    Identifiant du devis.

    data[].number
    requis
    string

    Numéro légal, par exemple DEV-2026-0017.

    data[].status
    requis
    string

    DRAFT : brouillon. SENT : envoyé. ACCEPTED : accepté. REFUSED : refusé. EXPIRED : expiré.

    Valeurs permises :DRAFTSENTACCEPTEDREFUSEDEXPIRED
    data[].date
    requis
    string (date-time)

    Date d'émission.

    data[].validUntil
    requis
    string (date-time)

    Date limite de validité.

    data[].subject
    string | null
    data[].totalHt
    requis
    number

    Total HT en euros.

    data[].totalVat
    requis
    number

    Total de TVA en euros.

    data[].totalTtc
    requis
    number

    Total TTC en euros.

    data[].sentAt
    string (date-time) | null
    data[].acceptedAt
    string (date-time) | null
    data[].refusedAt
    string (date-time) | null
    data[].signatureToken
    string | null

    Jeton de la page publique de signature : le lien à transmettre au client final est https://factupilot.fr/sign/{signatureToken}. null tant que le devis n'a pas de lien.

    data[].client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    data[].client.id
    requis
    string

    Identifiant du client.

    data[].client.name
    requis
    string
    data[].client.email
    requis
    string | null
    data[].createdAt
    requis
    string (date-time)
    data[].updatedAt
    string (date-time)

    Reprend createdAt : FactuPilot ne conserve pas de date de dernière modification exposée par l'API.

    pagination
    requis
    object

    Position dans la liste. pages vaut 0 quand la liste est vide.

    pagination.page
    requis
    integer

    Page courante (à partir de 1).

    • min. 1
    pagination.limit
    requis
    integer

    Nombre maximal d'éléments par page.

    • min. 1 · max. 100
    pagination.total
    requis
    integer

    Nombre total d'éléments correspondant aux filtres.

    • min. 0
    pagination.pages
    requis
    integer

    Nombre total de pages (total divisé par limit, arrondi au supérieur).

    • min. 0
  • 400

    Paramètre illisible : limit inférieur à 1 ou non numérique, page non numérique, status hors de la liste, startDate ou endDate illisible.

    application/json
    Exemples de réponse (4)

    Limite invalide

    {
      "error": "Le paramètre 'limit' doit être un entier compris entre 1 et 100."
    }

    Page non numérique

    {
      "error": "Le paramètre 'page' doit être un entier supérieur ou égal à 1."
    }

    Statut hors liste

    {
      "error": "Le paramètre 'status' doit valoir l'une des valeurs suivantes : DRAFT, SENT, ACCEPTED, REFUSED, EXPIRED."
    }

    Date illisible

    {
      "error": "Le paramètre 'startDate' doit être une date valide au format ISO 8601, par exemple 2026-11-15 ou 2026-11-15T09:30:00Z."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée quotes:read, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · quotes:read requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Créer un devis (brouillon)

POST/quotes
Portée requise
quotes:write
Limite de débit
30 requêtes par minute

Crée un devis pour un client de l'entreprise. Il naît en brouillon (DRAFT) avec son numéro légal déjà attribué. L'API ne l'envoie pas au client : l'envoi se fait depuis FactuPilot.

Les totaux sont calculés par FactuPilot. L'appel n'est pas idempotent : rejouer la requête crée un second devis.

Mêmes règles de lignes et de TVA à 0 % que pour POST /invoices : une ligne sans vatRate est à 0 %, et le motifTvaZero d'une ligne à 0 % est exigé à l'envoi du devis pour une entreprise assujettie.

Pour un formulaire de contact qui ne connaît que le nom du client, voir POST /quotes/create.

Corps de la requête

Type de contenu : application/json · corps obligatoire

Champs du corps de Créer un devis (brouillon)
ChampTypeDescription
clientId
requis
string

Identifiant d'un client de votre entreprise (voir GET /clients).

items
requis
array<object>

Au moins une ligne. Ligne d'une facture ou d'un devis. Les totaux ne sont jamais fournis : FactuPilot les calcule. Attention : une ligne sans vatRate est à 0 % de TVA.

  • 1 élément min.
items[].description
requis
string

Libellé de la ligne (obligatoire). Une description vide ou absente est refusée (erreur 400).

  • 1 caractère min.
items[].quantity
number | string

Quantité, strictement positive, décimale permise. Une chaîne numérique est tolérée. Absente, elle vaut 1.

  • Par défaut : 1
  • supérieur à 0
items[].unitPriceHt
number | string

Prix unitaire HT en EUROS (décimal, par exemple 12.5), jamais en centimes. Arrondi au centime à l'enregistrement. Absent, il vaut 0.

  • Par défaut : 0
  • min. 0
items[].vatRate
number | string

Taux de TVA en pourcentage (20, 10, 5.5, 2.1 ou 0). Absent, il vaut 0. Une ligne à 0 % doit porter un motifTvaZero avant l'émission.

  • Par défaut : 0
  • min. 0 · max. 100
items[].motifTvaZero
string

Identifiant du motif d'une ligne à 0 % de TVA, choisi dans le catalogue officiel de FactuPilot (104 motifs : exonérations, export, livraisons intracommunautaires, autoliquidation, santé, enseignement, etc.). Le motif est imprimé sur le document et transmis dans le Factur-X. Seuls les identifiants de la liste sont acceptés, sinon l'API répond 400. Il n'est conservé que sur une ligne à 0 %.

104 valeurs permises
deboursvente-a-un-exportateur-sous-attestation-d-achat-envente-de-biens-places-en-entrepot-ou-regime-suspensifcommission-d-un-intermediaire-sur-une-operation-hors-ueservices-lies-a-des-biens-en-entrepot-sous-douaneprestation-de-services-a-un-professionnel-de-l-uevente-avec-installation-chez-un-client-pro-de-l-ueexport-hors-uevente-detaxee-a-un-voyageur-hors-uelivraison-intracommunautaireprestation-de-services-a-un-professionnel-hors-ueprestation-immaterielle-a-un-particulier-hors-ueautoliquidation-code-francais-article-283-2-du-cgisous-traitance-de-travaux-du-batiment-btpvente-de-dechets-ferraille-et-matieres-de-recuperationvente-d-or-brut-ou-semi-ouvre-a-un-professionnelcession-de-quotas-carbone-ou-garanties-d-origineservices-de-communications-electroniques-entrelivraison-d-electricite-ou-de-gaz-a-un-professionnelformation-professionnelle-continue-organisme-avecenseignement-scolaire-superieur-ou-a-distancecours-particuliers-par-un-enseignant-independantenseignement-et-formation-professionnelle-code-europeencours-particuliers-donnes-par-un-enseignantsoins-medicaux-et-paramedicauxprotheses-dentaires-dentiste-prothesisteanalyses-de-biologie-medicalefrais-d-hospitalisation-en-etablissement-de-sante-privesoins-en-ehpad-pris-en-charge-au-forfaitorganes-sang-et-lait-humainstransport-de-malades-en-ambulance-ou-vslhospitalisation-et-soins-en-etablissement-de-santesoins-medicaux-et-paramedicaux-code-europeenorganes-sang-et-lait-humains-code-europeenprotheses-dentaires-et-prothesistes-dentairestransport-de-malades-ou-blesses-en-ambulancecreche-ou-micro-creche-autorisee-moins-de-3-ansmandataire-judiciaire-a-la-protection-des-majeursservices-d-un-syndicat-ou-parti-a-ses-membres-cotisationassociation-services-sportifs-culturels-ou-sociaux-auxoeuvre-sociale-ou-philanthropique-sans-but-lucratifmanifestation-de-bienfaisance-ou-de-soutien-6-par-anassociation-intermediaire-d-insertionaide-a-domicile-par-une-association-agreeelieu-de-vie-et-d-accueil-action-socialearticles-fabriques-par-des-travailleurs-handicapesaide-sociale-et-services-sociaux-code-europeenprotection-de-l-enfance-et-garde-de-jeunes-enfantsmise-a-disposition-de-personnel-religieux-ouservices-d-un-organisme-politique-syndical-ou-religieuxservices-sportifs-d-une-association-a-ses-membresservices-culturels-d-un-organisme-public-ou-reconnumanifestations-de-soutien-au-profit-d-une-associationvente-d-un-terrain-non-constructiblevente-d-un-immeuble-acheve-depuis-plus-de-5-anslocation-de-terres-et-batiments-agricoleslocation-par-bail-conferant-un-droit-reellocation-nue-de-locaux-ou-de-terrain-non-amenageconcession-de-droits-sur-terres-ou-locaux-nuslocation-meublee-d-habitation-sans-services-hoteliersagence-de-voyages-part-realisee-hors-uerevente-d-un-bien-d-occasion-regime-de-la-margerevente-d-une-oeuvre-d-art-regime-de-la-margerevente-d-objets-de-collection-ou-d-antiquite-margevente-d-or-d-investissement-lingots-piecesoperations-a-terme-sur-marchandises-marche-reglementecredit-et-courtage-en-credit-iobsppaiements-virements-change-de-devisescession-de-titres-actions-ou-parts-socialesgestion-de-fonds-d-investissement-opcvm-opcassurance-et-courtage-en-assuranceassurance-finance-immobilier-code-europeen-globalservices-lies-a-une-importation-transport-transitservices-lies-a-une-exportation-de-biensnavires-et-avions-du-trafic-international-code-globalavitaillement-des-navires-de-haute-meravitaillement-des-navires-de-guerrevente-reparation-location-de-navires-de-haute-merservices-pour-les-besoins-directs-des-naviresavitaillement-des-avions-de-lignes-internationalesvente-reparation-location-d-avions-de-lignesservices-pour-les-besoins-directs-des-avionsentraide-entre-agriculteursvente-de-sa-peche-par-un-pecheur-professionnelexonerations-propres-a-la-guadeloupe-martinique-etvente-de-biens-expedies-vers-l-outre-mer-domdiplomates-organismes-internationaux-armees-alliees-codevente-a-une-ambassade-ou-un-consulat-autre-pays-uevente-aux-institutions-et-agences-de-l-union-europeennevente-a-un-organisme-international-autre-pays-uevente-aux-forces-armees-alliees-stationnees-en-francevente-aux-forces-armees-otan-d-un-autre-pays-uevente-aux-forces-britanniques-stationnees-a-chyprerevente-d-un-bien-d-equipement-sans-tva-recupereetravaux-sur-monuments-aux-morts-et-sepultures-de-guerreservice-universel-postal-la-posteaide-apres-une-catastrophe-dans-l-union-arretegroupement-de-moyens-refacturation-des-frais-communsvente-de-timbres-poste-ou-fiscaux-a-leur-valeur-facialeorganisation-de-jeux-de-casino-ou-de-cercleproduit-des-loteries-paris-et-jeux-en-ligneexoneration-d-interet-general-code-europeen-globalservice-postal-universel-code-europeengroupement-de-moyens-refacturant-les-frais-communs
subject
string

Objet du devis.

notes
string

Notes imprimées sur le devis.

validUntil
string

Date limite de validité, ISO 8601. Par défaut, 30 jours après la création. Une date illisible est refusée (erreur 400).

Exemple de requête
curl -X POST "https://factupilot.fr/api/v1/quotes" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "clientId": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
  "subject": "Refonte du site vitrine",
  "notes": "Devis valable 30 jours.",
  "validUntil": "2026-11-30",
  "items": [
    {
      "description": "Refonte graphique et intégration",
      "quantity": 1,
      "unitPriceHt": 1200,
      "vatRate": 20
    },
    {
      "description": "Formation à l'\''administration du site",
      "quantity": 2,
      "unitPriceHt": 150,
      "vatRate": 20
    }
  ]
}'
Réponses
  • 201

    Devis créé en brouillon, avec son numéro légal.

    application/json
    Exemple de réponse
    {
      "data": {
        "id": "jh72v9c4x6m1q8n3w5d0r7tb4y2z9kae",
        "number": "DEV-2026-0018",
        "status": "DRAFT",
        "date": "2026-10-16T09:20:12.604Z",
        "validUntil": "2026-11-30T00:00:00.000Z",
        "subject": "Refonte du site vitrine",
        "totalHt": 1500,
        "totalVat": 300,
        "totalTtc": 1800,
        "client": {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Boulangerie Dupont",
          "email": "contact@boulangerie-dupont.fr"
        },
        "items": [
          {
            "id": null,
            "description": "Refonte graphique et intégration",
            "quantity": 1,
            "unitPriceHt": 1200,
            "vatRate": 20,
            "totalHt": 1200
          },
          {
            "id": null,
            "description": "Formation à l'administration du site",
            "quantity": 2,
            "unitPriceHt": 150,
            "vatRate": 20,
            "totalHt": 300
          }
        ],
        "createdAt": "2026-10-16T09:20:12.811Z"
      }
    }
    Champs de la réponse
    Champs de la réponse 201
    ChampTypeDescription
    data
    requis
    object

    Devis venant d'être créé. Les totaux sont ceux calculés par FactuPilot.

    data.id
    requis
    string
    data.number
    requis
    string

    Numéro légal attribué à la création.

    data.status
    requis
    string

    Toujours DRAFT à la création.

    Valeurs permises :DRAFT
    data.date
    requis
    string (date-time)
    data.validUntil
    requis
    string (date-time)
    data.subject
    string | null
    data.totalHt
    requis
    number
    data.totalVat
    requis
    number
    data.totalTtc
    requis
    number
    data.client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    data.client.id
    requis
    string

    Identifiant du client.

    data.client.name
    requis
    string
    data.client.email
    requis
    string | null
    data.items
    requis
    array<object>

    Ligne de devis renvoyée à la création. Elle est reconstruite à partir de votre demande (arrondi au centime par ligne) : id vaut donc toujours null.

    data.items[].id
    requis
    null
    data.items[].description
    requis
    string
    data.items[].quantity
    requis
    number
    data.items[].unitPriceHt
    requis
    number

    Prix unitaire HT en euros.

    data.items[].vatRate
    requis
    number

    Taux de TVA en pourcentage.

    data.items[].totalHt
    requis
    number

    Total HT de la ligne en euros.

    data.createdAt
    requis
    string (date-time)
  • 400

    Requête invalide : corps illisible, clientId absent, aucune ligne dans items, ligne invalide (description vide, quantité, prix, taux de TVA, motif inconnu), date validUntil illisible ou champ du mauvais type.

    application/json
    Exemples de réponse (6)

    Corps illisible

    {
      "error": "Le corps de la requête n'est pas un JSON valide : envoyez un objet JSON avec l'en-tête Content-Type: application/json."
    }

    clientId absent

    {
      "error": "Le champ 'clientId' est requis"
    }

    Aucune ligne

    {
      "error": "Au moins un article (items) est requis"
    }

    Description vide

    {
      "error": "Ligne 1 : description requise (texte non vide)"
    }

    Prix invalide

    {
      "error": "Ligne 1 : prix unitaire invalide (doit être >= 0)"
    }

    Date de validité illisible

    {
      "error": "Le champ 'validUntil' doit être une date valide au format ISO 8601, par exemple 2026-11-15 ou 2026-11-15T09:30:00Z."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée quotes:write, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · quotes:write requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 404

    Le client n'existe pas dans votre entreprise.

    application/json
    Exemple de réponse
    {
      "error": "Client introuvable"
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Créer un devis depuis un formulaire de contact

POST/quotes/create
Portée requise
quotes:write
Limite de débit
30 requêtes par minute

Route pensée pour un formulaire de contact : vous envoyez les coordonnées du visiteur, FactuPilot retrouve le client (même nom et, s'il est fourni, même e-mail) ou le crée, puis ouvre un devis brouillon avec son numéro légal. Le devis peut être créé sans ligne : le marchand le complète ensuite depuis son tableau de bord.

Différences avec POST /quotes : le client est donné en toutes lettres et non par identifiant, les lignes sont facultatives, une unité (unit) peut être précisée, et la réponse est plus courte (success et quote).

Tolérance. Cette route accepte les valeurs « vides » d'un formulaire : un champ absent, null, 0, false ou une chaîne vide prend sa valeur par défaut (quantité 1, prix 0, TVA 0, validité 30 jours, coordonnée absente), un nombre peut être envoyé en texte, et un motifTvaZero inconnu est ignoré. Ce qui ne peut pas être compris est refusé en erreur 400 avant toute écriture, sans créer ni client ni devis : corps illisible, client.name absent, champ texte d'un autre type, ligne qui n'est pas un objet ou sans description, quantité, prix ou taux de TVA négatifs ou illisibles, durée de validité illisible.

L'appel n'est pas idempotent : chaque appel crée un devis.

Corps de la requête

Type de contenu : application/json · corps obligatoire

Champs du corps de Créer un devis depuis un formulaire de contact
ChampTypeDescription
client
requis
object

Client à retrouver ou créer. Un client existant est repris s'il porte exactement ce nom (et cet e-mail, s'il est fourni, sans tenir compte de la casse).

client.name
requis
string
  • 1 caractère min.
client.email
string
client.phone
string
client.address
string
client.city
string
client.zipCode
string
subject
string

Objet du devis. Par défaut : « Demande depuis le site web ».

notes
string
items
array<object>

Lignes du devis. Facultatif : sans ligne, le devis est créé vide, à compléter ensuite dans FactuPilot. Ligne d'un devis créé par POST /quotes/create, pensée pour un formulaire de contact et TOLÉRANTE : une valeur vide (absente, null, 0, chaîne vide) prend la valeur par défaut, un nombre peut être envoyé en texte, un motifTvaZero inconnu est ignoré, et une unité peut être précisée. Sont refusés (erreur 400) : une ligne qui n'est pas un objet, une description vide, une quantité, un prix ou un taux de TVA négatifs ou illisibles, un taux au-dessus de 100.

items[].description
requis
string

Libellé de la ligne (obligatoire, non vide).

  • 1 caractère min.
items[].quantity
number | string | null

Quantité. Absente, null ou 0, elle vaut 1. Une chaîne numérique est tolérée. Négative ou illisible : erreur 400.

  • Par défaut : 1
items[].unitPriceHt
number | string | null

Prix unitaire HT en euros, arrondi au centime. Absent ou null, il vaut 0. Une chaîne numérique est tolérée. Négatif ou illisible : erreur 400.

  • Par défaut : 0
items[].vatRate
number | string | null

Taux de TVA en pourcentage, de 0 à 100. Absent ou null, il vaut 0. Une chaîne numérique est tolérée.

  • Par défaut : 0
items[].motifTvaZero
string | null

Motif d'une ligne à 0 % (identifiant du schéma MotifTvaZero). Gardé s'il est au catalogue ; inconnu, null ou d'un autre type, il est ignoré sans erreur.

104 valeurs permises
deboursvente-a-un-exportateur-sous-attestation-d-achat-envente-de-biens-places-en-entrepot-ou-regime-suspensifcommission-d-un-intermediaire-sur-une-operation-hors-ueservices-lies-a-des-biens-en-entrepot-sous-douaneprestation-de-services-a-un-professionnel-de-l-uevente-avec-installation-chez-un-client-pro-de-l-ueexport-hors-uevente-detaxee-a-un-voyageur-hors-uelivraison-intracommunautaireprestation-de-services-a-un-professionnel-hors-ueprestation-immaterielle-a-un-particulier-hors-ueautoliquidation-code-francais-article-283-2-du-cgisous-traitance-de-travaux-du-batiment-btpvente-de-dechets-ferraille-et-matieres-de-recuperationvente-d-or-brut-ou-semi-ouvre-a-un-professionnelcession-de-quotas-carbone-ou-garanties-d-origineservices-de-communications-electroniques-entrelivraison-d-electricite-ou-de-gaz-a-un-professionnelformation-professionnelle-continue-organisme-avecenseignement-scolaire-superieur-ou-a-distancecours-particuliers-par-un-enseignant-independantenseignement-et-formation-professionnelle-code-europeencours-particuliers-donnes-par-un-enseignantsoins-medicaux-et-paramedicauxprotheses-dentaires-dentiste-prothesisteanalyses-de-biologie-medicalefrais-d-hospitalisation-en-etablissement-de-sante-privesoins-en-ehpad-pris-en-charge-au-forfaitorganes-sang-et-lait-humainstransport-de-malades-en-ambulance-ou-vslhospitalisation-et-soins-en-etablissement-de-santesoins-medicaux-et-paramedicaux-code-europeenorganes-sang-et-lait-humains-code-europeenprotheses-dentaires-et-prothesistes-dentairestransport-de-malades-ou-blesses-en-ambulancecreche-ou-micro-creche-autorisee-moins-de-3-ansmandataire-judiciaire-a-la-protection-des-majeursservices-d-un-syndicat-ou-parti-a-ses-membres-cotisationassociation-services-sportifs-culturels-ou-sociaux-auxoeuvre-sociale-ou-philanthropique-sans-but-lucratifmanifestation-de-bienfaisance-ou-de-soutien-6-par-anassociation-intermediaire-d-insertionaide-a-domicile-par-une-association-agreeelieu-de-vie-et-d-accueil-action-socialearticles-fabriques-par-des-travailleurs-handicapesaide-sociale-et-services-sociaux-code-europeenprotection-de-l-enfance-et-garde-de-jeunes-enfantsmise-a-disposition-de-personnel-religieux-ouservices-d-un-organisme-politique-syndical-ou-religieuxservices-sportifs-d-une-association-a-ses-membresservices-culturels-d-un-organisme-public-ou-reconnumanifestations-de-soutien-au-profit-d-une-associationvente-d-un-terrain-non-constructiblevente-d-un-immeuble-acheve-depuis-plus-de-5-anslocation-de-terres-et-batiments-agricoleslocation-par-bail-conferant-un-droit-reellocation-nue-de-locaux-ou-de-terrain-non-amenageconcession-de-droits-sur-terres-ou-locaux-nuslocation-meublee-d-habitation-sans-services-hoteliersagence-de-voyages-part-realisee-hors-uerevente-d-un-bien-d-occasion-regime-de-la-margerevente-d-une-oeuvre-d-art-regime-de-la-margerevente-d-objets-de-collection-ou-d-antiquite-margevente-d-or-d-investissement-lingots-piecesoperations-a-terme-sur-marchandises-marche-reglementecredit-et-courtage-en-credit-iobsppaiements-virements-change-de-devisescession-de-titres-actions-ou-parts-socialesgestion-de-fonds-d-investissement-opcvm-opcassurance-et-courtage-en-assuranceassurance-finance-immobilier-code-europeen-globalservices-lies-a-une-importation-transport-transitservices-lies-a-une-exportation-de-biensnavires-et-avions-du-trafic-international-code-globalavitaillement-des-navires-de-haute-meravitaillement-des-navires-de-guerrevente-reparation-location-de-navires-de-haute-merservices-pour-les-besoins-directs-des-naviresavitaillement-des-avions-de-lignes-internationalesvente-reparation-location-d-avions-de-lignesservices-pour-les-besoins-directs-des-avionsentraide-entre-agriculteursvente-de-sa-peche-par-un-pecheur-professionnelexonerations-propres-a-la-guadeloupe-martinique-etvente-de-biens-expedies-vers-l-outre-mer-domdiplomates-organismes-internationaux-armees-alliees-codevente-a-une-ambassade-ou-un-consulat-autre-pays-uevente-aux-institutions-et-agences-de-l-union-europeennevente-a-un-organisme-international-autre-pays-uevente-aux-forces-armees-alliees-stationnees-en-francevente-aux-forces-armees-otan-d-un-autre-pays-uevente-aux-forces-britanniques-stationnees-a-chyprerevente-d-un-bien-d-equipement-sans-tva-recupereetravaux-sur-monuments-aux-morts-et-sepultures-de-guerreservice-universel-postal-la-posteaide-apres-une-catastrophe-dans-l-union-arretegroupement-de-moyens-refacturation-des-frais-communsvente-de-timbres-poste-ou-fiscaux-a-leur-valeur-facialeorganisation-de-jeux-de-casino-ou-de-cercleproduit-des-loteries-paris-et-jeux-en-ligneexoneration-d-interet-general-code-europeen-globalservice-postal-universel-code-europeengroupement-de-moyens-refacturant-les-frais-communs
items[].unit
string

Unité : HOUR heure, DAY jour, UNIT unité, FIXED forfait, SQM m², LM mètre linéaire, KG kilogramme, LOT lot. Une valeur inconnue est remplacée par UNIT.

Valeurs permises :HOURDAYUNITFIXEDSQMLMKGLOT
  • Par défaut : UNIT
source
string

Origine de la demande, mémorisée sur la fiche d'un client nouvellement créé (par exemple le nom de votre site).

validityDays
integer | string | null

Durée de validité du devis, en jours. Par défaut 30, de même qu'une valeur null, nulle ou négative. Une chaîne numérique est tolérée. Une valeur illisible, ou si grande que la date sortirait du calendrier, est refusée (erreur 400).

  • Par défaut : 30
Exemple de requête
curl -X POST "https://factupilot.fr/api/v1/quotes/create" \
  -H "Authorization: Bearer $FACTUPILOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "client": {
    "name": "Camille Martin",
    "email": "camille.martin@example.fr",
    "phone": "06 12 34 56 78",
    "city": "Toulouse",
    "zipCode": "31000"
  },
  "subject": "Demande de devis depuis le formulaire de contact",
  "notes": "Souhaite une refonte de son site de photographe.",
  "items": [
    {
      "description": "Audit du site existant",
      "quantity": 1,
      "unitPriceHt": 300,
      "vatRate": 20,
      "unit": "FIXED"
    }
  ],
  "source": "site-vitrine",
  "validityDays": 30
}'
Réponses
  • 201

    Devis créé en brouillon, avec son numéro légal.

    application/json
    Exemple de réponse
    {
      "success": true,
      "quote": {
        "id": "jh72v9c4x6m1q8n3w5d0r7tb4y2z9kae",
        "number": "DEV-2026-0019",
        "status": "DRAFT",
        "totalTtc": 360,
        "client": {
          "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
          "name": "Camille Martin",
          "email": "camille.martin@example.fr"
        },
        "createdAt": "2026-10-16T09:31:05.226Z"
      }
    }
    Champs de la réponse
    Champs de la réponse 201
    ChampTypeDescription
    success
    requis
    boolean
    Valeurs permises :true
    quote
    requis
    object
    quote.id
    requis
    string
    quote.number
    requis
    string

    Numéro légal attribué à la création.

    quote.status
    requis
    string
    Valeurs permises :DRAFT
    quote.totalTtc
    requis
    number

    Total TTC en euros.

    quote.client
    object | null

    Client rattaché à un document. Vaut null si le client n'existe plus.

    quote.client.id
    requis
    string

    Identifiant du client.

    quote.client.name
    requis
    string
    quote.client.email
    requis
    string | null
    quote.createdAt
    requis
    string (date-time)
  • 400

    Requête invalide : corps illisible, client.name absent, champ texte d'un autre type, validityDays illisible, ou ligne invalide (pas un objet, description vide, valeur négative ou illisible).

    application/json
    Exemples de réponse (4)

    Corps illisible

    {
      "error": "Le corps de la requête n'est pas un JSON valide : envoyez un objet JSON avec l'en-tête Content-Type: application/json."
    }

    client.name absent

    {
      "error": "client.name est requis"
    }

    Description de ligne vide

    {
      "error": "Ligne 1 : description requise (texte non vide)"
    }

    Durée de validité invalide

    {
      "error": "Le champ 'validityDays' doit être un nombre de jours, par exemple 30."
    }
  • 401

    Clé absente, inconnue, expirée ou mal formée.

    application/json
    Exemple de réponse
    {
      "error": "Non autorisé"
    }
  • 403

    Accès refusé. Deux causes possibles : la clé n'a pas la portée quotes:write, ou l'entreprise n'a plus l'accès API (plan Business).

    application/json
    Exemples de réponse (2)

    Portée manquante

    {
      "error": "Permission refusée · quotes:write requise"
    }

    Plan Business perdu

    {
      "error": "L'accès API est inclus dans le plan Business. Votre entreprise n'y a plus accès."
    }
  • 429

    Trop de requêtes : limite de débit atteinte. Réessayez après le délai de Retry-After.

    application/json
    • En-tête Retry-After : Délai d'attente avant de réessayer, en secondes. (exemple : 60)
    Exemple de réponse
    {
      "error": "Trop de requêtes. Réessayez dans un instant."
    }
  • 500

    Erreur interne du serveur. Le détail n'est pas communiqué.

    application/json
    Exemple de réponse
    {
      "error": "Erreur interne du serveur"
    }

Webhooks

FactuPilot envoie chaque événement à l'adresse de réception de votre webhook, en requête POST au format JSON. Vous créez vos webhooks et vous récupérez leur secret depuis l'onglet API des Paramètres.

  • Chaque requête porte les en-têtes X-FactuPilot-Evenement (nom de l'événement) et X-FactuPilot-Livraison (identifiant de l'événement, identique à chaque nouvelle tentative : servez-vous-en pour ignorer un doublon).
  • Vérifiez la signature avant d'agir. L'en-tête X-FactuPilot-Signature vaut t=<horodatage>,v1=<signature> : la signature est le HMAC SHA-256, en hexadécimal, de <horodatage>.<corps>, calculé avec votre secret entier (préfixe whsec_ compris) sur le corps reçu, octet pour octet. Nous conseillons de refuser un horodatage de plus de 5 minutes.
  • Répondez par un code HTTP 2xx en moins de 10 secondes. Sans réponse valide, FactuPilot réessaie (6 tentatives au plus, espacées de 1 minute, 5 minutes, 30 minutes, 2 heures puis 12 heures), puis abandonne.

Facture créée

POSTfacture.creee

Envoyé quand une facture vient d'être créée, quel que soit son canal : API, tableau de bord, devis accepté, acompte, solde, facture partielle, avoir, duplication. Une facture créée directement émise déclenche facture.creee puis facture.emise.

Charge envoyée

Type de contenu : application/json

Champs de la charge de facture.creee
ChampTypeDescription
id
requis
string

Identifiant de l'événement, unique par livraison. Une nouvelle tentative GARDE le même : c'est votre clé d'idempotence (ignorez un identifiant déjà traité). Il est aussi dans l'en-tête X-FactuPilot-Livraison.

type
requis
string

Type de l'événement, aussi dans l'en-tête X-FactuPilot-Evenement.

Valeurs permises :facture.creeefacture.emisefacture.payeefacture.statut_plateforme
creeLe
requis
string (date-time)

Instant de l'événement (UTC).

donnees
requis
object
donnees.facture
requis
object

Facture telle que portée par un événement. Les noms de champs sont ceux des webhooks, en français.

donnees.facture.id
requis
string

Identifiant de la facture (le même que le champ id de l'API REST).

donnees.facture.numero
requis
string

Numéro légal.

donnees.facture.statut
requis
string

Statut de la facture à l'instant de l'événement, mêmes valeurs que le champ status de l'API REST.

Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
donnees.facture.typeDocument
requis
string

Type de document, comme invoiceType dans l'API REST.

Valeurs permises :STANDARDACOMPTESOLDEAVOIRPARTIELLE
donnees.facture.totalTtc
requis
number

Total TTC en euros (négatif pour un avoir).

donnees.facture.client
requis
object | null
donnees.facture.client.id
requis
string
donnees.facture.client.nom
requis
string
donnees.facture.plateforme
requis
object | null

Suivi par la plateforme agréée ; null tant que la facture ne lui a jamais été présentée (dépôt manuel compris).

donnees.facture.plateforme.nom
requis
string

Nom de la plateforme, par exemple Super PDP ou Iopole.

donnees.facture.plateforme.statut
requis
string

Étape de suivi, stable d'une plateforme à l'autre : c'est elle qu'il faut tester.

Valeurs permises :non_soumiseerreurteleverseerejeteeaccepteeen_transittransmise_sans_identifiant
donnees.facture.plateforme.code
requis
string | null

Statut brut de la plateforme, null s'il n'y en a pas.

donnees.facture.plateforme.statutTexte
requis
string

Libellé lisible, avec le motif en cas de rejet ou d'erreur.

Exemple de charge

{
  "id": "evt_3a7d5e1c9b2f40886d1e7a35",
  "type": "facture.creee",
  "creeLe": "2026-10-16T09:12:44.318Z",
  "donnees": {
    "facture": {
      "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
      "numero": "FA-2026-0043",
      "statut": "DRAFT",
      "typeDocument": "STANDARD",
      "totalTtc": 1032,
      "client": {
        "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
        "nom": "Boulangerie Dupont"
      },
      "plateforme": null
    }
  }
}

Réponse attendue

  • 200

    Accusé de réception : tout code 2xx reçu en moins de 10 secondes. Le corps de la réponse est ignoré. Tout autre résultat déclenche une nouvelle tentative (6 au plus : immédiate, puis après 1 min, 5 min, 30 min, 2 h et 12 h).

Facture émise

POSTfacture.emise

Envoyé quand une facture vient d'être émise : brouillon passé en SENT (depuis FactuPilot ou par POST /invoices/{id}/emettre), devis converti en facture émise, facture créée émise d'emblée ("emettre": true), ou brouillon soldé par un paiement en ligne (il reçoit alors facture.emise puis facture.payee). Le passage de « en retard » à « émise » n'en est pas une.

Charge envoyée

Type de contenu : application/json

Champs de la charge de facture.emise
ChampTypeDescription
id
requis
string

Identifiant de l'événement, unique par livraison. Une nouvelle tentative GARDE le même : c'est votre clé d'idempotence (ignorez un identifiant déjà traité). Il est aussi dans l'en-tête X-FactuPilot-Livraison.

type
requis
string

Type de l'événement, aussi dans l'en-tête X-FactuPilot-Evenement.

Valeurs permises :facture.creeefacture.emisefacture.payeefacture.statut_plateforme
creeLe
requis
string (date-time)

Instant de l'événement (UTC).

donnees
requis
object
donnees.facture
requis
object

Facture telle que portée par un événement. Les noms de champs sont ceux des webhooks, en français.

donnees.facture.id
requis
string

Identifiant de la facture (le même que le champ id de l'API REST).

donnees.facture.numero
requis
string

Numéro légal.

donnees.facture.statut
requis
string

Statut de la facture à l'instant de l'événement, mêmes valeurs que le champ status de l'API REST.

Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
donnees.facture.typeDocument
requis
string

Type de document, comme invoiceType dans l'API REST.

Valeurs permises :STANDARDACOMPTESOLDEAVOIRPARTIELLE
donnees.facture.totalTtc
requis
number

Total TTC en euros (négatif pour un avoir).

donnees.facture.client
requis
object | null
donnees.facture.client.id
requis
string
donnees.facture.client.nom
requis
string
donnees.facture.plateforme
requis
object | null

Suivi par la plateforme agréée ; null tant que la facture ne lui a jamais été présentée (dépôt manuel compris).

donnees.facture.plateforme.nom
requis
string

Nom de la plateforme, par exemple Super PDP ou Iopole.

donnees.facture.plateforme.statut
requis
string

Étape de suivi, stable d'une plateforme à l'autre : c'est elle qu'il faut tester.

Valeurs permises :non_soumiseerreurteleverseerejeteeaccepteeen_transittransmise_sans_identifiant
donnees.facture.plateforme.code
requis
string | null

Statut brut de la plateforme, null s'il n'y en a pas.

donnees.facture.plateforme.statutTexte
requis
string

Libellé lisible, avec le motif en cas de rejet ou d'erreur.

Exemple de charge

{
  "id": "evt_9f2c4a7e1b3d58606a1c0e2f",
  "type": "facture.emise",
  "creeLe": "2026-10-16T09:12:44.318Z",
  "donnees": {
    "facture": {
      "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
      "numero": "FA-2026-0043",
      "statut": "SENT",
      "typeDocument": "STANDARD",
      "totalTtc": 1032,
      "client": {
        "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
        "nom": "Boulangerie Dupont"
      },
      "plateforme": null
    }
  }
}

Réponse attendue

  • 200

    Accusé de réception : tout code 2xx reçu en moins de 10 secondes. Le corps de la réponse est ignoré. Tout autre résultat déclenche une nouvelle tentative (6 au plus : immédiate, puis après 1 min, 5 min, 30 min, 2 h et 12 h).

Facture payée

POSTfacture.payee

Envoyé quand une facture vient d'être entièrement payée : marquée payée, encaissement saisi qui solde le reste dû, ou paiement en ligne qui la solde (carte, PayPal, prélèvement, dernière échéance d'un paiement en plusieurs fois). Un paiement partiel ne déclenche rien, et un avoir ne se paie pas.

Charge envoyée

Type de contenu : application/json

Champs de la charge de facture.payee
ChampTypeDescription
id
requis
string

Identifiant de l'événement, unique par livraison. Une nouvelle tentative GARDE le même : c'est votre clé d'idempotence (ignorez un identifiant déjà traité). Il est aussi dans l'en-tête X-FactuPilot-Livraison.

type
requis
string

Type de l'événement, aussi dans l'en-tête X-FactuPilot-Evenement.

Valeurs permises :facture.creeefacture.emisefacture.payeefacture.statut_plateforme
creeLe
requis
string (date-time)

Instant de l'événement (UTC).

donnees
requis
object
donnees.facture
requis
object

Facture telle que portée par un événement. Les noms de champs sont ceux des webhooks, en français.

donnees.facture.id
requis
string

Identifiant de la facture (le même que le champ id de l'API REST).

donnees.facture.numero
requis
string

Numéro légal.

donnees.facture.statut
requis
string

Statut de la facture à l'instant de l'événement, mêmes valeurs que le champ status de l'API REST.

Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
donnees.facture.typeDocument
requis
string

Type de document, comme invoiceType dans l'API REST.

Valeurs permises :STANDARDACOMPTESOLDEAVOIRPARTIELLE
donnees.facture.totalTtc
requis
number

Total TTC en euros (négatif pour un avoir).

donnees.facture.client
requis
object | null
donnees.facture.client.id
requis
string
donnees.facture.client.nom
requis
string
donnees.facture.plateforme
requis
object | null

Suivi par la plateforme agréée ; null tant que la facture ne lui a jamais été présentée (dépôt manuel compris).

donnees.facture.plateforme.nom
requis
string

Nom de la plateforme, par exemple Super PDP ou Iopole.

donnees.facture.plateforme.statut
requis
string

Étape de suivi, stable d'une plateforme à l'autre : c'est elle qu'il faut tester.

Valeurs permises :non_soumiseerreurteleverseerejeteeaccepteeen_transittransmise_sans_identifiant
donnees.facture.plateforme.code
requis
string | null

Statut brut de la plateforme, null s'il n'y en a pas.

donnees.facture.plateforme.statutTexte
requis
string

Libellé lisible, avec le motif en cas de rejet ou d'erreur.

Exemple de charge

{
  "id": "evt_c41b8f20e6d9375a2b0f1c84",
  "type": "facture.payee",
  "creeLe": "2026-10-19T13:41:05.090Z",
  "donnees": {
    "facture": {
      "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
      "numero": "FA-2026-0043",
      "statut": "PAID",
      "typeDocument": "STANDARD",
      "totalTtc": 1032,
      "client": {
        "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
        "nom": "Boulangerie Dupont"
      },
      "plateforme": null
    }
  }
}

Réponse attendue

  • 200

    Accusé de réception : tout code 2xx reçu en moins de 10 secondes. Le corps de la réponse est ignoré. Tout autre résultat déclenche une nouvelle tentative (6 au plus : immédiate, puis après 1 min, 5 min, 30 min, 2 h et 12 h).

Statut de la plateforme agréée modifié

POSTfacture.statut_plateforme

Envoyé quand le suivi de la facture par la plateforme agréée change : remise à la plateforme, acceptation, rejet, erreur de transmission. Rejouer le même statut ne déclenche rien. Fiez-vous à donnees.facture.plateforme.statut (étape stable d'une plateforme à l'autre) plutôt qu'au code brut.

Charge envoyée

Type de contenu : application/json

Champs de la charge de facture.statut_plateforme
ChampTypeDescription
id
requis
string

Identifiant de l'événement, unique par livraison. Une nouvelle tentative GARDE le même : c'est votre clé d'idempotence (ignorez un identifiant déjà traité). Il est aussi dans l'en-tête X-FactuPilot-Livraison.

type
requis
string

Type de l'événement, aussi dans l'en-tête X-FactuPilot-Evenement.

Valeurs permises :facture.creeefacture.emisefacture.payeefacture.statut_plateforme
creeLe
requis
string (date-time)

Instant de l'événement (UTC).

donnees
requis
object
donnees.facture
requis
object

Facture telle que portée par un événement. Les noms de champs sont ceux des webhooks, en français.

donnees.facture.id
requis
string

Identifiant de la facture (le même que le champ id de l'API REST).

donnees.facture.numero
requis
string

Numéro légal.

donnees.facture.statut
requis
string

Statut de la facture à l'instant de l'événement, mêmes valeurs que le champ status de l'API REST.

Valeurs permises :DRAFTSENTPAIDOVERDUECANCELLEDREFUNDEDPARTIALLY_REFUNDED
donnees.facture.typeDocument
requis
string

Type de document, comme invoiceType dans l'API REST.

Valeurs permises :STANDARDACOMPTESOLDEAVOIRPARTIELLE
donnees.facture.totalTtc
requis
number

Total TTC en euros (négatif pour un avoir).

donnees.facture.client
requis
object | null
donnees.facture.client.id
requis
string
donnees.facture.client.nom
requis
string
donnees.facture.plateforme
requis
object | null

Suivi par la plateforme agréée ; null tant que la facture ne lui a jamais été présentée (dépôt manuel compris).

donnees.facture.plateforme.nom
requis
string

Nom de la plateforme, par exemple Super PDP ou Iopole.

donnees.facture.plateforme.statut
requis
string

Étape de suivi, stable d'une plateforme à l'autre : c'est elle qu'il faut tester.

Valeurs permises :non_soumiseerreurteleverseerejeteeaccepteeen_transittransmise_sans_identifiant
donnees.facture.plateforme.code
requis
string | null

Statut brut de la plateforme, null s'il n'y en a pas.

donnees.facture.plateforme.statutTexte
requis
string

Libellé lisible, avec le motif en cas de rejet ou d'erreur.

Exemple de charge

{
  "id": "evt_5e0a92d7b13c46f8a9d2e61b",
  "type": "facture.statut_plateforme",
  "creeLe": "2026-10-16T09:12:51.774Z",
  "donnees": {
    "facture": {
      "id": "jd7c4k9x2p6m8v1q3n5w0r7t4y9b2zhe",
      "numero": "FA-2026-0043",
      "statut": "SENT",
      "typeDocument": "STANDARD",
      "totalTtc": 1032,
      "client": {
        "id": "k5738x2vq9m4c1nw6d0h8rtb5y3zj7ae",
        "nom": "Boulangerie Dupont"
      },
      "plateforme": {
        "nom": "Super PDP",
        "statut": "televersee",
        "code": "api:uploaded",
        "statutTexte": "Téléversée"
      }
    }
  }
}

Réponse attendue

  • 200

    Accusé de réception : tout code 2xx reçu en moins de 10 secondes. Le corps de la réponse est ignoré. Tout autre résultat déclenche une nouvelle tentative (6 au plus : immédiate, puis après 1 min, 5 min, 30 min, 2 h et 12 h).

Événement de test

POSTwebhook.test

Envoyé par le bouton « Envoyer un test » de FactuPilot, même si le webhook est désactivé, pour vérifier votre adresse et votre contrôle de signature avant de l'activer. On ne s'y abonne pas : il part toujours, avec les mêmes en-têtes, la même signature et les mêmes nouvelles tentatives que les vrais événements.

Charge envoyée

Type de contenu : application/json

Champs de la charge de webhook.test
ChampTypeDescription
id
requis
string
type
requis
string
Valeurs permises :webhook.test
creeLe
requis
string (date-time)
donnees
requis
object
donnees.message
requis
string
donnees.entreprise
requis
object
donnees.entreprise.id
requis
string
donnees.entreprise.nom
requis
string

Exemple de charge

{
  "id": "evt_0b8d3c6a2f4e71955c1d9a07",
  "type": "webhook.test",
  "creeLe": "2026-10-16T09:30:02.114Z",
  "donnees": {
    "message": "Événement de test envoyé depuis FactuPilot.",
    "entreprise": {
      "id": "kh7c2x9m4v1q8n3w5d0r6tb4y2z9jaed",
      "nom": "Atelier Dupont"
    }
  }
}

Réponse attendue

  • 200

    Accusé de réception : tout code 2xx reçu en moins de 10 secondes. Le corps de la réponse est ignoré. Tout autre résultat déclenche une nouvelle tentative (6 au plus : immédiate, puis après 1 min, 5 min, 30 min, 2 h et 12 h).