Lisez vos factures depuis votre comptabilité, créez vos clients depuis votre CRM.
Chaque requête porte une clé d’API, créée depuis vos paramètres. Une clé identifie un espace, pas une personne, et ne peut faire que ce que ses portées autorisent.
curl https://syllodi-digital.fr/api/v1/invoices \ -H "Authorization: Bearer sd_live_a1b2c3d4e5f6_…"
La clé n’est affichée qu’une seule fois, à sa création. Nous n’en conservons qu’une empreinte : pouvoir la réafficher signifierait la stocker en clair.
Une facture de 1 320 € renvoie 132000, et non « 1320.00 ».
Ce n’est pas un détail. Les nombres à virgule flottante ne représentent pas exactement les décimales : 0.1 + 0.2 ne vaut pas 0.3. Sur une facture, cet écart devient une erreur comptable. Le centime entier ne souffre d’aucune ambiguïté.
"amounts": {
"total_excl_tax": 120000, // 1 200,00 €
"total_tax": 12000, // 120,00 €
"total_incl_tax": 132000, // 1 320,00 €
"formatted_total": "1 320,00 €"
}N’accordez à chaque clé que ce dont elle a besoin.
Par curseur, pas par numéro de page : sur une liste qui bouge entre deux appels, la page 2 peut sauter ou répéter des éléments.
# Première page
GET /api/v1/invoices?limit=50
{ "data": [...], "has_more": true, "next_cursor": "8f3a…" }
# Suivante — on suit le curseur tant que has_more vaut true
GET /api/v1/invoices?limit=50&cursor=8f3a…GET/api/v1/customerscustomers:read
Liste des clients. Paramètres : limit (1 à 100), cursor, search (nom, référence ou e-mail).
POST/api/v1/customerscustomers:write
Crée un client. Seul le nom est obligatoire.
curl -X POST https://syllodi-digital.fr/api/v1/customers \
-H "Authorization: Bearer sd_live_…" \
-H "Content-Type: application/json" \
-d '{
"type": "COMPANY",
"name": "PLOMBERIE MARTIN",
"email": "contact@exemple.fr",
"siret": "79234156700018"
}'GET/api/v1/customers/{id}customers:read
Détail d’un client, avec ses statistiques : nombre de devis et de factures, total facturé, total réglé, encours.
GET/api/v1/invoicesinvoices:read
Liste des factures. Filtres : status, issued_after, issued_before.
# Les factures réglées de septembre GET /api/v1/invoices?status=PAID&issued_after=2026-09-01&issued_before=2026-09-30
GET/api/v1/invoices/{id}invoices:read
Détail d’une facture, lignes comprises, avec son bloc integrity : l’empreinte qui permet de vérifier qu’elle n’a pas été modifiée depuis son émission.
Elle n’émet pas de factures. Émettre attribue un numéro légal définitif et scelle le document de façon irréversible. Un script rejoué deux fois créerait des numéros orphelins dans une séquence que la loi impose de garder continue (art. 242 nonies A de l’annexe II au CGI).
L’émission passe par l’interface, où elle est confirmée explicitement. L’usage réel de cette API est la lecture : synchroniser une comptabilité, alimenter un tableau de bord, rapprocher des règlements.
Plutôt que d’interroger l’API en boucle pour savoir si un devis a été signé, vous pouvez nous demander de prévenir votre outil. Chaque événement déclenche un POST vers l’adresse de votre choix, avec le contenu en JSON.
Vérifier que le message vient bien de nous
Chaque appel porte un en-tête x-syllodi-signature de la forme t=1789…,v1=a3f…. Recalculez l’empreinte et comparez :
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifier(corpsBrut, entete, secret) {
const champs = Object.fromEntries(
entete.split(',').map((p) => p.trim().split('=')),
);
const horodatage = Number(champs.t);
// L'écart est pris en valeur absolue : un horodatage dans le FUTUR
// doit être refusé lui aussi.
if (Math.abs(Date.now() / 1000 - horodatage) > 300) return false;
const attendu = createHmac('sha256', secret)
.update(`${horodatage}.${corpsBrut}`)
.digest('hex');
if (champs.v1.length !== attendu.length) return false;
return timingSafeEqual(Buffer.from(champs.v1), Buffer.from(attendu));
}Signez le corps brut, tel que reçu. Le reformater — même en réordonnant des clés — change l’empreinte et fait échouer la vérification.
Ce que nous attendons de votre réponse
Le même événement peut arriver deux fois
C’est le propre d’un système qui réessaie : votre réponse peut s’être perdue après que vous avez traité le message. Utilisez l’identifiant de l’en-tête x-syllodi-delivery — stable d’un essai à l’autre — pour ignorer un doublon.
Contraintes sur l’adresse
Elle doit être en https et publiquement joignable. Les adresses de réseau privé sont refusées, et les redirections ne sont pas suivies : sans cela, une adresse pourrait servir à faire appeler, par nos serveurs, des machines internes.
L’API accepte 120 appels par minute et par clé. Une requête sans clé valable est plus sévèrement bornée — 20 par minute et par adresse — parce qu’il s’agit alors d’un essai à l’aveugle.
Chaque réponse porte l’état de votre quota :
ratelimit-limit: 120
ratelimit-remaining: 117
ratelimit-reset: 43Au-delà, la réponse est un 429 accompagné d’un en-tête retry-after en secondes. Respectez-le : un appel refusé compte quand même, donc insister prolonge l’attente.
La fenêtre est glissante. Un quota épuisé ne se rouvre pas d’un coup à la minute suivante : il se libère progressivement.
{ "error": { "code": "insufficient_scope", "message": "…" } }missing_credentialsEn-tête Authorization absentinvalid_keyClé inconnue, révoquée ou incorrecteexpired_keyClé arrivée à échéanceinsufficient_scopeLa clé n’a pas la portée demandéeworkspace_suspendedL’espace est suspendunot_foundRessource inexistante — ou appartenant à un autre espacevalidation_failedDonnées invalides (le détail est renvoyé)Une ressource appartenant à un autre espace répond 404 et non 403 : répondre « interdit » confirmerait son existence.