Aller au contenu principal

Clients et fournisseurs

Chaque facture rattache un vendeur et un acheteur ; l'annuaire de votre workspace porte ces tiers sous forme de fiches : les clients (customers) auxquels une entreprise du workspace vend, les fournisseurs (suppliers) qui lui facturent. Créez la fiche une fois, puis référencez-la par party_id à chaque création de facture : Scribee recopie ses données sur la facture, et vos appels de facturation se réduisent aux montants et aux lignes. Les endpoints de cette page couvrent la création, la lecture, la mise à jour et la suppression de ces fiches ; aucun d'eux ne transmet quoi que ce soit au PPF (Portail Public de Facturation), au réseau Peppol ou au tiers lui-même.

Ce que Scribee fait pour vous​

  • Normalise les identifiants à l'enregistrement : vat_identifier, legal_registration_id, identifier et endpoint_id sont débarrassés des espaces en début et fin de valeur puis mis en majuscules ; l'IBAN perd tous ses espaces.
  • Contrôle les formats à l'écriture : IBAN (format et clé de contrôle), numéro de TVA (format général et format français), cohérence entre legal_registration_id et le schéma déclaré (9 chiffres pour un SIREN, 14 pour un SIRET).
  • Applique le français par défaut : communication_language et billing_language valent fr sur toute fiche créée sans ces champs.
  • Recopie la fiche sur chaque facture qui la référence par party_id : la facture garde sa propre copie des données, insensible aux modifications ultérieures de la fiche.

Deux annuaires, une même forme​

  • GET / POST /api/v1/workspaces/{workspace_id}/customers - lister et créer des clients
  • GET / PATCH / DELETE /api/v1/customers/{id} - lire, modifier, supprimer une fiche client
  • Même forme côté fournisseurs : GET / POST /api/v1/workspaces/{workspace_id}/suppliers et GET / PATCH / DELETE /api/v1/suppliers/{id}

Chaque fiche appartient à une entreprise du workspace : company_id la désigne à la création (Entreprises et établissements). Sans company_id, l'entreprise retenue est celle qui porte le plus petit id parmi les entreprises éligibles du workspace - toutes les entreprises côté fournisseur, les seules entreprises disposant de la capacité de vente côté client (voir Clients invisibles). Si aucune entreprise n'est éligible, l'appel retourne 404. L'entreprise de rattachement est fixée à la création : voir l'étape 4.

category_id rattache la fiche à une catégorie de votre workspace (voir Catégories). La fiche client porte quatre champs que la fiche fournisseur n'a pas : customer_type, first_name et last_name (voir l'étape 2), ainsi que einvoice_format (voir Le format de facture électronique).

Les types d'identifiants​

Trois paires de champs portent les identifiants d'une fiche : legal_registration_id + legal_registration_scheme_id (immatriculation légale), identifier + identifier_scheme_id (identifiant additionnel) et endpoint_id + endpoint_scheme_id. En écriture, le champ de schéma accepte la clé Scribee (siret) ou son code équivalent (0009) ; les réponses retournent toujours la clé.

Les trois champs de schéma acceptent le même jeu de clés, et ce jeu dépasse largement les identifiants français : Scribee reçoit des factures de tout le réseau et doit pouvoir enregistrer le schéma déclaré par un émetteur belge ou italien aussi bien que le vôtre. La liste des clés acceptées est publiée comme l'enum du paramètre legal_registration_scheme_id dans la référence API (Lister les clients) - c'est la liste qui fait foi, et elle suit le code. Le tableau ci-dessous ne reprend que les identifiants qu'une intégration française pose en pratique.

Clé APICode acceptéLibelléFormat exigé de legal_registration_id
siren0002SIREN9 chiffres
siret0009SIRET14 chiffres
ridet0228RIDET6 à 10 chiffres
tahiti0229Tahiti6 à 9 chiffres
european_union0223Union Européenne2 lettres puis 2 à 30 lettres ou chiffres
international0227International1 à 50 caractères sans espace
routing_code0224Code de routage14 chiffres
b2c0226B2C1 à 30 lettres, chiffres ou tirets
aife0225AIFE (Agence pour l'informatique financière de l'État)14 chiffres
it_partita_iva0211Partita IVA (Italie)IT puis 11 chiffres
global_location_number0088Global Location Number13 chiffres
france_platform0238Plateforme d'administration française14 chiffres

La dernière colonne ne contrôle que legal_registration_id. identifier et endpoint_id acceptent n'importe quelle valeur : leur schéma est enregistré, jamais confronté à la valeur. La valeur est mise en majuscules avant contrôle, vous pouvez donc l'envoyer en minuscules.

Le contrôle de format ne couvre pas tous les schémas acceptés. Les douze schémas du tableau ci-dessus le sont, et ils ne sont pas les seuls : 36 des 91 clés de l'enum portent un format, dont les schémas Peppol pour lesquels la codelist en publie un - be_en (0208), lei (0199) ou iban (9918) par exemple. Sous les autres, legal_registration_id est enregistré tel quel : l'absence de 422 ne confirme alors pas que l'identifiant est bien formé. nl_kvk (0106) est de ceux-là bien que la codelist publie un format, celle-ci se contredisant sur ce point.

Une clé ou un code absent de l'enum de la référence retourne 422 : le message porté par details.base est alors produit par le framework, en anglais et non traduit, et non un message de validation Scribee.

single_taxable_entity (0231) fait exception. Ce schéma désigne le SIREN de l'assujetti unique dont le vendeur d'une facture reçue est membre, et ce n'est pas un schéma que vous pouvez déclarer : envoyé sur legal_registration_scheme_id, identifier_scheme_id ou endpoint_scheme_id, sous sa clé comme sous son code, il est refusé en 422 avec code à validation_failed. details porte alors le champ en cause, avec le message Scribee "doit être un schéma que vous pouvez déclarer ; single_taxable_entity (0231) n'est lu que sur l'identifiant d'un vendeur reçu". La fiche n'est ni créée ni modifiée. Sur vos propres factures de vente, c'est Scribee qui émet ce schéma, depuis les paramètres d'assujetti unique de la société (Émettre une facture de vente).

À la réception, la règle est inverse : sur une facture reçue d'un tiers, un code de schéma absent de cette même liste est écarté au lieu de faire échouer l'import - legal_registration_id et identifier sont enregistrés tels quels et leur champ de schéma vaut null, sans erreur d'import ni rejet. Le couple endpoint_id / endpoint_scheme_id fait exception sur une facture CII (dont Factur-X) : un schéma d'adresse électronique reconnu y est enregistré normalement, avec son identifiant. C'est uniquement lorsque ce schéma est absent ou non reconnu que le couple entier est écarté et que les deux champs valent null - l'identifiant n'y est pas conservé sans son schéma.

Le vendeur d'une facture reçue fait l'objet d'une exception de plus : un identifiant qu'il déclare sous le schéma 0231, le SIREN de l'assujetti unique dont il est membre, n'est jamais repris dans identifier. Ce champ porte le premier des autres identifiants que la facture lui donne, ou null s'il n'y en a aucun. 0231 ne figure pas dans l'enum publié : le filtre legal_registration_scheme_id le refuse en 400 comme toute valeur hors liste.

Un quatrième couple porte votre propre référence externe : external_source et external_id. Ce sont des chaînes libres, plafonnées à 255 caractères chacune, sans validation de format ni de correspondance avec le tableau ci-dessus. La règle n'est pas symétrique : external_id exige external_source, mais l'inverse n'est pas vrai - une fiche peut porter un external_source sans external_id. La paire external_source + external_id doit être unique au sein d'une même entreprise et d'un même annuaire (clients ou fournisseurs) : si vous l'utilisez comme clé d'idempotence, une deuxième création avec la même paire retourne 422, avec external_id portant "a déjà été pris" dans details.

Les conditions de paiement​

payment_terms accepte une des huit valeurs suivantes. Le champ n'a pas de valeur par défaut : il reste vide tant que vous ne le renseignez pas.

payment_termsLibellé
seven_days7 jours
fifteen_days15 jours
thirty_days30 jours
thirty_days_end_of_month30 jours fin de mois
forty_five_days45 jours
forty_five_days_end_of_month45 jours fin de mois
on_receiptÀ réception
customPersonnalisé

Le format de facture électronique​

einvoice_format porte le format de facture électronique propre à un client. Il remplace, pour ce seul client, le réglage de l'entreprise qui lui vend. Le champ accepte trois valeurs, ou null :

einvoice_formatFormat
ublUBL XML
ciiCII XML
facturxFactur-X
nullAucun format propre : le client suit le réglage de son entreprise

null est la valeur de toute fiche créée sans ce champ. Le réglage de l'entreprise vaut UBL par défaut et se modifie depuis l'interface Scribee ; l'API ne l'expose pas. La réponse porte la valeur posée sur la fiche, jamais la valeur héritée : un null signifie que le client suit son entreprise, pas qu'aucun format ne s'applique.

Le champ se lit sur toute fiche client. Il ne s'écrit, en création comme en mise à jour, que lorsque le choix du format de facture électronique est activé pour votre workspace. Cette fonctionnalité n'est pas activée par défaut : Scribee l'active workspace par workspace. Tant qu'elle ne l'est pas, einvoice_format est ignoré sans erreur - la réponse est 201 ou 200 et le champ garde sa valeur. Contrôlez la valeur retournée plutôt que le code de statut. Une fois la fonctionnalité activée, envoyez null en mise à jour pour que le client suive à nouveau le réglage de son entreprise ; une valeur autre que ubl, cii ou facturx retourne 422, avec einvoice_format dans details. Les fiches fournisseur ne portent pas ce champ.

Une fois la fonctionnalité activée, le format résolu - celui de la fiche, sinon le réglage de l'entreprise - est la syntaxe de la copie de facture que Scribee envoie sur le réseau Peppol à la plateforme du client : UBL, CII, ou Factur-X (un PDF qui embarque le CII). Si la plateforme du client ne publie pas ce format dans l'annuaire Peppol, la copie part en UBL ; tant que la fonctionnalité n'est pas activée, elle part toujours en UBL. L'envoi des factures par e-mail n'est pas concerné.

Le paramétrage comptable​

Quatre champs décrivent le paramétrage comptable d'une fiche - celui qui fait tomber vos écritures dans le bon journal et sur le bon compte. Ils se lisent et s'écrivent sur les clients comme sur les fournisseurs.

  • accounting_ledger_id désigne le journal comptable de la fiche. Il vaut null tant que vous ne le posez pas, et le journal doit appartenir à la même entreprise que la fiche (Journaux comptables).
  • auxiliary_account_number est le compte auxiliaire qui identifie la fiche dans votre logiciel de comptabilité. Chaîne libre, null par défaut.
  • offset_accounts porte les comptes de contrepartie de la fiche.
  • accounting_setup_complete est en lecture seule et dérivé : il vaut true uniquement quand la fiche porte à la fois un accounting_ledger_id, un auxiliary_account_number et au moins un compte de contrepartie. Envoyé en écriture, il est ignoré.

Un compte de contrepartie peut apparaître en dehors de vos appels. Quand un compte de charge ou un compte de produit par défaut est déclaré depuis l'interface Scribee (ce paramètre ne se pose pas par l'API), la génération d'une écriture comptable pour une fiche qui ne porte encore aucun compte de contrepartie en crée un sur cette fiche : le compte de charge pour un fournisseur, le compte de produit pour un client, enregistré avec default: true et tax_rate à null. Ce paramètre se déclare à deux endroits : sur la connexion de l'entreprise à son logiciel de comptabilité, et sur le workspace. La valeur de la connexion l'emporte, celle du workspace ne sert que de socle : un workspace dont le paramètre est vide produit quand même un compte de contrepartie lorsque la connexion de l'entreprise en déclare un. Il faut donc que les deux soient vides pour que rien ne soit créé. Une fiche qui porte déjà au moins un compte de contrepartie n'est jamais touchée. Ne considérez donc pas un accounting_setup_complete à false comme un état stable, ni offset_accounts comme un tableau que vous seul alimentez : entre deux lectures, sans aucun appel de votre part, une fiche peut gagner une entrée et le champ passer à true. Cette création ne touche que offset_accounts : elle ne pose ni accounting_ledger_id ni auxiliary_account_number.

Le account_number d'une entrée existante peut lui aussi changer, sous un id inchangé. Quand l'entreprise de la fiche est reliée à un logiciel de comptabilité depuis l'interface Scribee et que le compte posé par défaut est corrigé dans ce logiciel, la synchronisation suivante réécrit cette entrée au lieu d'en ajouter une seconde : l'entrée garde son id et son default, et son account_number prend la valeur corrigée. Cette réécriture ne vise que l'entrée que Scribee avait créée faute de compte de contrepartie, et seulement tant qu'elle est la seule entrée de la fiche et que personne n'a changé son account_number. Corriger ce numéro vous-même met donc l'entrée hors d'atteinte de la synchronisation : votre PATCH n'est pas défait au passage suivant, et l'entrée est traitée comme celles que vous créez. Une entrée que vous avez créée, ou qu'une synchronisation antérieure avait déjà rapportée, n'est jamais réécrite ainsi : un numéro différent la concernant s'ajoute comme une entrée distincte. Identifiez donc un compte de contrepartie par son id, pas par son account_number.

Une entrée de offset_accounts porte sept champs :

{
"offset_accounts": [
{
"id": 71,
"account_number": "706000",
"category": "customers",
"cost_center": "CC-PARIS",
"default": true,
"product_type": "Prod_Functional_Costs",
"tax_rate": 20.0
}
]
}

account_number est obligatoire ; category, cost_center et product_type sont des chaînes libres et valent null par défaut. tax_rate est un nombre JSON ou null, et n'accepte que les taux du catalogue français : 0, 0.9, 1.05, 1.75, 2.1, 5.5, 7, 8.5, 9.2, 9.6, 10, 13, 19.6, 20, 20.6. null signifie que le compte ne vise aucun taux en particulier. En lecture, la valeur revient arrondie à deux décimales : 20 envoyé ressort en 20.0.

product_type porte le worktag Product Type que l'export comptable Workday écrit pour ce compte. Laissé à null, ce compte ne porte aucune valeur propre : celle configurée au niveau de l'entreprise s'applique. Ne le renseignez que si cette fiche doit porter la sienne, et envoyez product_type: null en mise à jour pour effacer la valeur posée.

default vaut false par défaut et n'est jamais déduit : Scribee ne promeut pas le premier compte d'un taux, c'est vous qui posez la valeur. Une fiche ne porte qu'un seul compte default: true par tax_rate.

Écrire les comptes de contrepartie​

À la création, chaque entrée de offset_accounts crée un compte. account_number est obligatoire ; n'envoyez ni id ni _destroy, la fiche n'ayant encore aucun compte à retrouver - un id envoyé à la création retourne 404, comme pour addresses et contacts.

À la mise à jour, le tableau fusionne par id :

  • une entrée sans id ajoute un compte ;
  • une entrée avec id modifie le compte correspondant et laisse inchangés les champs omis. Contrairement à addresses et contacts, aucun champ n'est à renvoyer pour que la modification s'applique : { "id": 71, "default": false } suffit ;
  • une entrée avec id et _destroy: true supprime le compte ;
  • les comptes que le tableau ne mentionne pas restent en place.
curl -X PATCH https://app.scribee.tech/api/v1/customers/12 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"accounting_ledger_id": 12,
"auxiliary_account_number": "411AUX001",
"offset_accounts": [
{
"account_number": "706000",
"category": "customers",
"tax_rate": 20,
"default": true
}
]
}
}'

Changer le compte par défaut d'un taux​

Le compte default: true d'un tax_rate est protégé par une contrainte d'unicité en base, appliquée ligne par ligne au moment de l'écriture. Trois façons de changer ce compte, dont une seule échoue :

  • promouvoir un compte déjà enregistré dans le même appel que la rétrogradation de celui en place échoue, quel que soit l'ordre des entrées dans le tableau. La réponse est 422 avec le code duplicate_record, et aucune des deux lignes n'est écrite ;
  • créer un nouveau compte default: true dans le même appel que la rétrogradation de l'ancien fonctionne, dans les deux ordres ;
  • supprimer l'ancien avec _destroy: true en promouvant un compte existant dans le même appel fonctionne aussi.

Deux PATCH successifs - rétrograder d'abord, promouvoir ensuite - couvrent tous les cas. C'est la séquence à retenir si vous ne voulez qu'une seule règle.

{
"error": "unprocessable_entity",
"code": "duplicate_record",
"message": "La validation a échoué",
"details": {
"base": ["Un enregistrement avec cet identifiant existe déjà"]
}
}

Étape 1 : créer un client​

Cet appel crée une fiche dans l'annuaire du workspace. Il ne transmet rien vers l'extérieur et se défait avec le DELETE de l'étape 6. Seul name est obligatoire ; adresses et contacts se déclarent dans le même appel. Tous les champs de la fiche vont sous une clé englobante customer (supplier côté fournisseurs) : un corps envoyé sans elle retourne 400.

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/customers \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"name": "Acme Corp",
"vat_identifier": "FR32443061841",
"legal_registration_id": "44306184100025",
"legal_registration_scheme_id": "siret",
"payment_terms": "thirty_days",
"addresses": [
{
"label": "Siège social",
"address_line_1": "15 avenue des Champs-Élysées",
"postal_code": "75008",
"city": "Paris",
"country_code": "FR",
"address_type": "billing",
"default_billing": true
}
],
"contacts": [
{
"name": "Marie Martin",
"email": "marie.martin@example.com",
"default": true
}
]
}
}'

Réponse 201, abrégée aux champs utiles ici :

{
"data": {
"id": 12,
"type": "customer",
"company_id": 1,
"name": "Acme Corp",
"customer_type": "company",
"einvoice_format": null,
"vat_identifier": "FR32443061841",
"legal_registration_id": "44306184100025",
"legal_registration_scheme_id": "siret",
"payment_terms": "thirty_days",
"communication_language": "fr",
"billing_language": "fr",
"payment_methods": [],
"addresses": [
{
"id": 34,
"address_line_1": "15 avenue des Champs-Élysées",
"postal_code": "75008",
"city": "Paris",
"country_code": "FR",
"address_type": "billing",
"default_billing": true,
"default_delivery": false
}
],
"contacts": [
{
"id": 21,
"name": "Marie Martin",
"email": "marie.martin@example.com",
"phone": null,
"default": true
}
]
}
}

Relevez le id : c'est le party_id que vos factures référencent.

Aucun champ de la fiche elle-même n'est conditionnel : toute fiche, en liste comme à l'unité, porte l'intégralité de ces champs (id, type, company_id, category_id, name, trading_name, vat_identifier, les trois paires identifiant + schéma, directory_routing_identifier, external_source, external_id, company_legal_form, iban, payment_terms, communication_language, billing_language, accounting_ledger_id, auxiliary_account_number, accounting_setup_complete, created_at, updated_at, payment_methods, addresses, contacts, offset_accounts, plus customer_type, first_name, last_name et einvoice_format sur un client), en dehors de l'extension include (voir l'étape 3). Les extraits JSON des étapes suivantes sont abrégés de la même manière.

Parmi ces champs, directory_routing_identifier porte l'identifiant d'adressage retenu dans l'annuaire. Il se lit sur toute fiche et s'écrit sur les deux endpoints de fiche, en création comme en mise à jour ; c'est lui que Scribee émet en BT-34 / BT-49 sous le schéma 0225 (Annuaire national).

Les collections imbriquées​

  • addresses et contacts sont des tableaux JSON d'objets, payment_methods un tableau JSON de chaînes. Une valeur d'une autre forme - un objet seul, une chaîne seule - est écartée sans erreur : vérifiez la collection renvoyée dans la réponse.
  • Une adresse exige address_line_1, postal_code, city et un country_code de 2 caractères. Seule la longueur est contrôlée, pas le contenu : 12 ou ?? sont acceptés au même titre que FR. Envoyez un code ISO 3166-1 alpha-2 en majuscules - rien côté API ne rattrapera une valeur fantaisiste, et elle ressortira telle quelle dans les factures et les flux réglementaires. address_type vaut billing par défaut et accepte billing, delivery ou both. default_billing exige address_type billing ou both, default_delivery exige delivery ou both ; une fiche ne porte qu'une adresse default_billing et une adresse default_delivery. default_billing désigne l'adresse retenue quand la fiche en compte plusieurs.
  • Un contact exige name et au moins un de email ou phone. Un email renseigné doit par ailleurs être une adresse bien formée, sans quoi la fiche est refusée par un 422. Une fiche ne porte qu'un contact default.
  • Une entrée addresses dont address_line_1, city, postal_code et country_code sont tous vides, ou une entrée contacts dont name, email et phone sont tous vides, est écartée avant validation : la réponse est 201 et l'entrée est simplement absente. Une entrée partiellement remplie, elle, retourne 422.
  • N'envoyez aucun id dans addresses ni contacts à la création : la fiche n'a pas encore d'éléments à retrouver, et l'appel retourne 404. Reposter tel quel le corps d'une réponse GET déclenche ce cas.

Les moyens de paiement​

payment_methods accepte cinq valeurs : bank_transfer, check, direct_debit, cash et card. Une valeur non reconnue est écartée sans erreur - la réponse est 201 et la valeur est absente de payment_methods renvoyé. Contrôlez le tableau retourné plutôt que le code de statut.

Étape 2 : créer un client particulier (B2C)​

Un client est une entreprise (customer_type: "company", la valeur par défaut) ou un particulier (customer_type: "consumer"). Pour un particulier, first_name et last_name sont obligatoires et name est recalculé sous la forme "Prénom Nom" ; le nom du contact par défaut est aligné sur celui de la fiche. Le schéma d'identifiant b2c du tableau ci-dessus est prévu pour porter l'identifiant d'un particulier.

Ne confondez pas customer_type avec counterparty_type : ce dernier n'est pas un champ de l'API. Envoyé en création ou en mise à jour, sur un client comme sur un fournisseur, il est ignoré sans erreur, et il n'apparaît dans aucune réponse.

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/customers \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"customer_type": "consumer",
"name": "Jean Dupont",
"first_name": "Jean",
"last_name": "Dupont"
}
}'
{
"data": {
"id": 13,
"type": "customer",
"customer_type": "consumer",
"name": "Jean Dupont",
"first_name": "Jean",
"last_name": "Dupont"
}
}

Étape 3 : lister et retrouver une fiche​

GET /api/v1/workspaces/{workspace_id}/customers retourne les fiches du workspace, paginées (page, per_page - 20 par défaut, 100 au maximum) et triables (sort_by : name, trading_name, vat_identifier, created_at ou updated_at ; sort_order : asc ou desc ; tri par défaut : name croissant). L'enveloppe data + meta suit Conventions de l'API.

curl "https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/customers?sort_by=created_at&sort_order=desc" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": [
{
"id": 12,
"type": "customer",
"name": "Acme Corp",
"vat_identifier": "FR32443061841"
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total_pages": 1,
"total_count": 1
}
}

La liste accepte huit paramètres de filtre, combinables entre eux : company_ids (un ou plusieurs id d'entreprise, par exemple ?company_ids[]=1&company_ids[]=2), vat_identifier, legal_registration_id, legal_registration_scheme_id, external_source, external_id, auxiliary_account_number et accounting_setup_status. Pour les sept derniers, un paramètre laissé vide est ignoré - il ne filtre rien, plutôt que de ne renvoyer aucun résultat. company_ids se comporte différemment : ?company_ids[]= sans valeur est transmis comme un tableau contenant une chaîne vide, ce que l'API ne traite pas comme un paramètre absent - le filtre s'applique alors avec cette valeur vide, qui ne correspond à aucune fiche, et la réponse est 200 avec une liste vide. vat_identifier et legal_registration_id sont comparés après la même normalisation qu'à l'écriture (débarrassés des espaces en début et fin de valeur puis mis en majuscules) ; external_source, external_id et auxiliary_account_number sont comparés sans normalisation : la valeur soumise n'est ni rognée de ses espaces ni transformée. La comparaison elle-même reste celle de la base, insensible à la casse et aux accents. legal_registration_scheme_id n'accepte que les clés publiées dans l'enum du paramètre (Les types d'identifiants) - une valeur hors de cette liste retourne 400 (voir 400 Bad Request). accounting_setup_status n'accepte que complete et incomplete : complete ne retourne que les fiches portant à la fois un accounting_ledger_id, un auxiliary_account_number et au moins un compte de contrepartie (Le paramétrage comptable), incomplete retourne toutes celles à qui il manque au moins une des trois pièces. Toute autre valeur retourne 400. Aucun filtre par nom, catégorie ni date, et pas de recherche plein texte : conservez le id retourné à la création pour retrouver une fiche que ces huit filtres ne suffisent pas à isoler.

GET /api/v1/customers/{id} retourne une fiche seule - le chemin référence la fiche directement, sans workspace_id : l'identifiant est résolu sur l'ensemble des workspaces rattachés à votre client OAuth. Sur la liste comme sur la fiche seule, include=company ajoute l'entreprise de rattachement (id, name, legal_identifier) à la réponse.

Étape 4 : mettre à jour une fiche​

PATCH accepte les mêmes champs que la création, tous optionnels : envoyez les champs à changer. Une exception : company_id est accepté puis ignoré. L'entreprise de rattachement est fixée à la création, la réponse est 200 et la fiche est inchangée sur ce point ; pour déplacer une fiche, supprimez-la et recréez-la sous l'entreprise voulue.

Dans addresses et contacts :

  • une entrée sans id ajoute un élément ;
  • une entrée avec id et _destroy: true supprime l'élément ;
  • une entrée avec id modifie l'élément, à condition de porter au moins un des champs qui décident du rejet : address_line_1, city, postal_code ou country_code pour une adresse, name, email ou phone pour un contact. Une entrée qui n'en porte aucun - par exemple { "id": 21, "default": true } - est écartée en silence : la réponse est 200 et l'élément est inchangé. Renvoyez un de ces champs à côté de la valeur à modifier pour que la mise à jour s'applique ;
  • un id qui n'appartient pas à cette fiche retourne 404, pas 422.

payment_methods remplace la sélection au lieu de la compléter : la liste envoyée devient la liste exacte et les moyens omis sont désactivés. Omettez le champ pour conserver la sélection en place, envoyez [] pour tout désactiver. addresses et contacts, eux, fusionnent par id.

accounting_ledger_id, auxiliary_account_number et offset_accounts s'écrivent dans le même appel, avec leurs propres règles de fusion : voir Le paramétrage comptable.

curl -X PATCH https://app.scribee.tech/api/v1/customers/12 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"payment_terms": "on_receipt",
"contacts": [
{ "id": 21, "phone": "+33 1 42 68 53 00" }
]
}
}'

Réponse 200 avec la fiche mise à jour. Les factures déjà créées ne changent pas : chacune conserve la copie des données prise à sa création. Pour faire compléter la fiche par le tiers lui-même, voir Invitations de mise à jour.

Étape 5 : les fournisseurs, même mécanique​

Les endpoints fournisseurs ont la même forme, la même pagination, le même tri et les mêmes champs, sans customer_type, first_name, last_name ni einvoice_format. Deux différences de comportement : les fournisseurs échappent au filtrage par capacité de vente qui s'applique aux clients (Clients invisibles), et leur suppression a un mode d'échec propre (étape 6).

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/suppliers \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"supplier": {
"name": "Tech Solutions SAS",
"vat_identifier": "FR32443061841",
"legal_registration_id": "44306184100025",
"legal_registration_scheme_id": "siret"
}
}'
{
"data": {
"id": 27,
"type": "supplier",
"name": "Tech Solutions SAS",
"vat_identifier": "FR32443061841",
"legal_registration_id": "44306184100025",
"legal_registration_scheme_id": "siret"
}
}

Étape 6 : supprimer une fiche​

Cet appel supprime la fiche avec ses adresses, ses contacts, ses documents de référence, ses invitations de mise à jour, ses accès portail et ses comptes de contrepartie comptables - la suppression est définitive. Les factures existantes ne sont pas modifiées : chacune garde sa copie des données, seule la référence vers la fiche est effacée. Une fiche référencée par des factures peut donc être supprimée. Côté fournisseur, les produits rattachés sont conservés et perdent leur référence au fournisseur.

curl -X DELETE https://app.scribee.tech/api/v1/customers/12 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Réponse 204 sans corps. Le scope requis est destroy ou write. Même chemin côté fournisseurs : DELETE /api/v1/suppliers/{id}.

Deux références bloquent la suppression, et elles se signalent de la même façon : un 422 portant le code dependent_records, dont le message nomme la dépendance bloquante. Ce corps ne porte pas de clé details, contrairement au 422 de validation décrit plus bas.

Un fournisseur référencé par une commande d'achat ou une demande d'achat retourne :

{
"error": "unprocessable_entity",
"code": "dependent_records",
"message": "Vous ne pouvez pas supprimer l'enregistrement parce que les commandes d'achat dépendants existent"
}

Un client porteur d'un mandat de prélèvement SEPA GoCardless ou d'une demande de paiement GoCardless retourne le même corps, avec le nom de sa propre dépendance bloquante dans le message. Branchez-vous sur le code plutôt que sur le texte. Détachez la fiche des documents en cause, puis rejouez la suppression ; les mandats et demandes de prélèvement GoCardless se traitent depuis l'interface Scribee.

Ce qui se passe ensuite​

  • À la création d'une facture (Émettre une facture), une entrée de parties portant party_id est résolue contre l'annuaire : Scribee recopie le nom, l'immatriculation légale, le numéro de TVA, l'adresse de facturation et le contact par défaut de la fiche. Un champ passé explicitement dans l'entrée l'emporte sur la valeur de la fiche, et billing_address_id désigne l'adresse à retenir quand la fiche en compte plusieurs. Deux limites à connaître : party_id est résolu parmi les clients et fournisseurs de l'entreprise de la facture, pas de tout le workspace - un identifiant appartenant à une autre entreprise n'est pas trouvé, et la création de la facture est alors refusée par un 422, pas silencieusement dégradée. Et un billing_address_id inconnu, appartenant à une autre fiche, ou pointant vers une adresse de livraison seule est ignoré sans erreur : Scribee retombe alors sur l'adresse default_billing de la fiche, ou à défaut sur sa plus ancienne adresse de facturation. Vérifiez l'adresse effectivement enregistrée dans la réponse de création.
  • Les documents de référence d'une fiche (coordonnées bancaires, contrat, extrait Kbis) se déposent sur POST /api/v1/parties/{party_id}/supporting_documents (Pièces jointes).
  • Pour que le tiers renseigne lui-même ses informations, envoyez-lui une invitation de mise à jour (Invitations de mise à jour).
  • La fiche peut changer en dehors de vos appels. Quand son entreprise est reliée à un logiciel de comptabilité depuis l'interface Scribee, la synchronisation de ce logiciel peut réécrire name, vat_identifier, legal_registration_id, legal_registration_scheme_id, auxiliary_account_number et les comptes de contrepartie de la fiche, et poser accounting_ledger_id tant qu'il vaut null ; toute écriture sur la fiche elle-même fait avancer updated_at. Seuls les champs que le logiciel porte sont concernés, les autres sont laissés intacts. Cette synchronisation ne fait pas que réécrire les fiches que vous avez créées : elle crée aussi des clients et des fournisseurs que vous n'avez jamais postés, à partir des tiers du logiciel relié. Une fiche ainsi créée porte le compte auxiliaire du logiciel, mais pas forcément de compte de contrepartie : elle revient alors avec offset_accounts vide et accounting_setup_complete à false, et elle est retournée par le filtre accounting_setup_status=incomplete. Un rapprochement de votre propre annuaire avec la liste, ou un comptage des fiches incomplete, doit donc s'attendre à des fiches apparues sans aucun appel de votre part. L'API n'expose ni le logiciel relié ni la date de la dernière synchronisation : relisez la fiche (GET /api/v1/customers/{id}) au lieu de tenir votre dernier PATCH pour l'état courant.

Erreurs et cas limites​

422 : validation échouée​

Une fiche sans name, un identifiant mal formé ou un client particulier sans prénom retournent 422, les causes dans details sous une clé par champ fautif - le nom du champ tel qu'il apparaît dans l'API, sans préfixe ni humanisation :

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"name": ["doit être rempli(e)"]
}
}

Autres messages du même format, chacun sous la clé du champ qu'il concerne : vat_identifier porte "n'est pas un numéro de TVA valide" ou "n'est pas un numéro de TVA français valide (FR + 2 caractères + 9 chiffres)" ; legal_registration_id porte "ne correspond pas au format du type d'identifiant déclaré" (par exemple un legal_registration_id qui ne fait pas 14 chiffres avec le schéma siret) ; iban porte "n'est pas un IBAN valide" ou "ne passe pas la vérification de clé IBAN" ; first_name porte "doit être rempli(e)" sur un client consumer sans prénom. Corrigez le champ nommé et rejouez l'appel.

Une erreur portant sur une adresse ou un contact est rangée sous une clé composée : le nom de la collection (party_addresses, party_contacts), un point, puis le nom du champ fautif - base quand l'erreur ne porte sur aucun champ précis :

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"party_addresses.city": ["doit être rempli(e)"],
"party_addresses.default_delivery": ["ne peut pas être définie sur une adresse de facturation uniquement"],
"party_contacts.base": ["doit avoir un email ou un numéro de téléphone"]
}
}

Ni la position de l'entrée fautive dans le tableau addresses ou contacts, ni son id, ne sont indiqués - seul le nom du champ l'est.

Les comptes de contrepartie suivent la même forme, sous le préfixe offset_accounts - le nom de la collection tel qu'il apparaît dans l'API, et non un nom interne comme pour les deux collections ci-dessus. Le journal comptable, lui, se range sous accounting_ledger :

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"accounting_ledger": ["doit exister"],
"offset_accounts.account_number": ["doit être rempli(e)"],
"offset_accounts.tax_rate": ["n'est pas inclus(e) dans la liste"],
"offset_accounts.default": ["est déjà défini par défaut pour ce taux de TVA"]
}
}

accounting_ledger porte le message "doit exister" dans les trois cas : un accounting_ledger_id inconnu, un journal hors de votre workspace, et un journal appartenant à une autre entreprise de votre workspace. Les trois sont volontairement indiscernables : une réponse distincte permettrait de sonder l'existence de journaux hors de votre périmètre.

offset_accounts.default signale deux comptes default: true sur le même tax_rate dans le même appel. La promotion d'un compte déjà enregistré, elle, se signale par un 422 de code duplicate_record (Changer le compte par défaut d'un taux).

Une valeur hors liste sur legal_registration_scheme_id, identifier_scheme_id, endpoint_scheme_id, payment_terms, customer_type, communication_language, billing_language ou addresses[].address_type retourne aussi 422, mais le message porté par details.base est produit par le framework, en anglais et non traduit. Seul 0231 sur les trois champs de schéma reçoit un message Scribee, sous le nom du champ (Les types d'identifiants).

404 Not Found​

{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}

Les causes, indiscernables les unes des autres dans la réponse :

  • la fiche n'existe pas ;
  • la fiche appartient à un workspace hors du périmètre de votre application ;
  • sur GET, PATCH et DELETE /api/v1/customers/{id} ou /api/v1/suppliers/{id}, la fiche appartient à un workspace dont l'allowlist d'adresses IP refuse l'adresse appelante ;
  • le company_id passé à la création est inconnu dans le workspace, ou, pour un client, n'a pas la capacité de vente ;
  • company_id est omis à la création et aucune entreprise du workspace n'est éligible ;
  • la fiche est un client rattaché à une entreprise sans capacité de vente (voir ci-dessous) ;
  • une entrée addresses, contacts ou offset_accounts porte un id qui n'appartient pas à cette fiche, ou porte un id alors que l'appel est une création.

Vérifiez l'identifiant et le rattachement du workspace (Votre premier appel).

403 Forbidden​

{
"error": "forbidden",
"message": "Vous n'êtes pas autorisé à effectuer cette action"
}

Sur les endpoints préfixés par {workspace_id}, un workspace non rattaché à votre client OAuth retourne "L'application n'a pas accès à cet espace de travail", et un workspace dont l'allowlist d'adresses IP refuse l'adresse appelante retourne "Cette adresse IP n'est pas autorisée pour cet espace de travail". Les endpoints /api/v1/customers/{id} et /api/v1/suppliers/{id} répondent 404 dans ce second cas, pas 403.

Les scopes exigés : write pour POST et PATCH, destroy ou write pour DELETE. Les deux GET exigent read - c'est aussi ce que porte un token demandé sans scope. Demandez scope=read write pour couvrir la page entière (Authentification).

400 Bad Request​

Quatre causes distinctes :

  • sur les listes, demander une page au-delà de la dernière page d'une collection non vide retourne "Le numéro de page dépasse le nombre de pages disponibles" dans l'enveloppe d'erreur habituelle. Bornez vos requêtes avec meta.total_pages ;
  • sur les listes, une valeur de legal_registration_scheme_id absente de l'enum du paramètre retourne "Valeur legal_registration_scheme_id invalide. Valeurs autorisées : ..." dans l'enveloppe d'erreur habituelle, où le message énumère ensuite toutes les clés acceptées. Branchez votre traitement sur error, qui vaut bad_request, et non sur cette énumération : elle s'allonge à chaque schéma ajouté ;
  • sur les listes, une valeur de accounting_setup_status autre que complete ou incomplete retourne "Valeur accounting_setup_status invalide. Valeurs autorisées : complete, incomplete" dans la même enveloppe ;
  • sur POST et PATCH, un corps de requête sans la clé englobante customer ou supplier retourne un 400 dans l'enveloppe {error, message} habituelle, avec error à bad_request et le message "Le corps de la requête est manquant ou mal formé".

401 Unauthorized​

Token absent, expiré ou révoqué ; le corps de la réponse est vide. Demandez un nouveau token (Authentification).

Clients invisibles​

Dans un workspace de type cabinet comptable, une entreprise dont l'offre souscrite n'inclut pas la vente n'expose aucun de ses clients par l'API - sauf si sa forme juridique l'en dispense : une entreprise dont le company_legal_form vaut SCI ou LMNP (la casse est indifférente) expose ses clients quelle que soit l'offre souscrite. Aucun appel ne dit pourquoi :

  • GET /api/v1/workspaces/{workspace_id}/customers retourne 200 avec "data": [] ;
  • GET, PATCH et DELETE /api/v1/customers/{id} retournent 404 sur des fiches qui existent bel et bien ;
  • POST /api/v1/workspaces/{workspace_id}/customers retourne 404, que company_id désigne une de ces entreprises ou qu'il soit omis et qu'aucune entreprise éligible ne reste.

Les fournisseurs du même workspace continuent de répondre normalement : cette asymétrie entre les deux annuaires est le seul diagnostic disponible. L'API n'expose ni le type du workspace ni l'offre souscrite. La résolution passe par le workspace, qui doit souscrire pour cette entreprise une offre incluant la vente - à moins que la forme juridique de l'entreprise ne l'en dispense déjà, auquel cas ses clients sont visibles sans changer d'offre ; corriger l'identifiant ou les scopes ne change rien.

Pages liées​