Logistivo CLI : pilotez votre activité logistique depuis un terminal et depuis votre ERP

Un seul catalogue de commandes alimente trois surfaces : le client terminal lg, l’assistant IA intégré au tableau de bord et le canal auquel se connectent les assistants IA externes. Une fonctionnalité est écrite une seule fois et apparaît dans les trois — et chaque action d’écriture de l’interface web dispose désormais d’une commande, si bien que vos scripts et vos propres agents IA peuvent accomplir le travail que votre équipe fait à l’écran. Les paiements, le consentement juridique et la clôture du compte restent volontairement dans le navigateur. Cette page est un contrat. Le tableau des commandes ci-dessous n’est pas rédigé à la main ; il est généré à partir du registre de commandes en direct, à chaque requête.

Qu’est-ce que la CLI Logistivo ?

La CLI Logistivo (lg) est un client en ligne de commande qui exécute le travail de votre compte Logistivo — chargements et voyages, demandes, offres et appels d’offres, flotte et conducteurs, documents, contacts et comptes courants, factures et comptabilité, entrepôt et stock, membres de l’équipe et tarifs douaniers — depuis un terminal, un script ou votre propre agent IA. Les commandes ne sont pas figées dans le client : celui-ci récupère le catalogue de commandes auprès du serveur et se construit à partir de lui. Les mêmes commandes peuvent être appelées en HTTP sans rien installer — c’est la voie qu’empruntent les intégrations ERP et CRM — ainsi que via le canal MCP auquel se connectent les assistants IA externes.

Ce qui est disponible aujourd’hui, et ce qui arrive

Cette section se trouve en haut de la page à dessein. L’erreur la plus coûteuse que puisse commettre un document d’intégration est de décrire un endpoint qui n’est pas encore ouvert comme s’il l’était : l’intégrateur écrit le code, l’endpoint renvoie 404, et la confiance ne revient pas.

Comme le contrat est gelé, le code d’intégration que vous écrivez aujourd’hui continue de fonctionner à mesure que les éléments restants s’ouvrent. Les champs que vous ne connaissez pas doivent être ignorés ; c’est cette règle qui nous permet d’ajouter sans rien casser.

Ce qui fait la différence : le client ne connaît aucune commande, il les tire du serveur

C’est l’unique décision qui donne son sens au reste de la page. Dans une CLI ordinaire, les commandes vivent dans le client ; chez Logistivo, elles vivent sur le serveur.

Dans l’organisation classique, quand le serveur gagne une fonctionnalité, le client a besoin d’une nouvelle version et l’utilisateur doit l’installer. Entre-temps, les deux côtés savent des choses différentes — et c’est le mode de défaillance le plus silencieux des intégrations : le script tourne, le code de sortie vaut 0, et la commande ne décrit tout simplement pas ce que le serveur sait faire.

Chez Logistivo, la définition d’une commande est un enregistrement unique : son nom, ce qu’elle fait, ses paramètres JSON Schema, les rôles autorisés à la voir, si elle exige une confirmation et si son effet est réversible. Au démarrage, lg récupère ce catalogue et en génère son texte d’aide, ses options et la validation de ses entrées. Dès qu’une commande est ajoutée sur le serveur, lg la connaît — sans mise à jour du client.

Le même catalogue alimente l’assistant IA du tableau de bord et le canal auquel se connectent les assistants IA externes. Une fonctionnalité est écrite une seule fois et apparaît à trois endroits — et tous trois passent par la même barrière de rôle, la même barrière de confirmation et le même registre d’audit. Il n’existe pas d’inventaire distinct des commandes CLI ; s’il en existait un, les trois surfaces divergeraient en silence en moins de six mois.

La règle de nommage — comment les noms de commandes évoluent

Le catalogue va s’agrandir. Les nouvelles commandes ne sont pas nommées au cas par cas ; six règles s’appliquent. Nous les publions ici parce que ce sont les noms que votre intégration écrira pendant des années, et que leur prévisibilité a de la valeur pour vous.

Couverture : tout ce que fait l’interface web, par une commande

De nombreuses équipes branchent Logistivo sur leur propre agent IA ou leur propre harnais d’automatisation, et un agent n’est utile que s’il peut aller au bout du travail. La règle est donc simple : tout ce qu’un utilisateur de bureau peut modifier dans l’interface web, une commande peut le modifier aussi — derrière la même barrière de rôle, la même barrière de confirmation et le même registre d’audit.

C’est mesuré, pas affirmé. Chaque endpoint d’écriture derrière l’interface web est confronté au registre de commandes, et un endpoint n’est compté comme couvert que lorsqu’une commande le déclare comme son miroir HTTP ou appelle le même service partagé. Parmi les endpoints d’écriture du périmètre, 100 % sont couverts et aucun ne reste sans preuve. Chaque endpoint laissé de côté porte un motif écrit — étapes d’identité et de paiement, plan de contrôle de l’intégration, endpoints propres à l’application conducteur, télémétrie client, copies mortes sans appelant — et ceux qui comptent pour un intégrateur sont listés ci-dessous.

Les commandes qui prennent un fichier relèvent de la même règle : 33 commandes du tableau prennent un document ou une photo, et toutes fonctionnent depuis lg, en HTTP et via MCP (voir Commandes qui prennent des fichiers).

Les tâches de gestion sont couvertes elles aussi, derrière une clé plus puissante : 62 commandes du tableau — membres de l’équipe et rôles, paramètres de l’entreprise, comptes bancaires, cartes enregistrées, clés d’intégration — exigent une clé d’intégration de niveau manager. Une clé de niveau employee, la valeur par défaut, exécute les opérations courantes et se voit refuser ces commandes avec INSUFFICIENT_KEY_LEVEL.

Les comptes affiliés (apporteurs d’affaires) disposent de leur propre catalogue : 55 commandes réparties sur 20 domaines — recommandations, prospects, notes et tâches CRM, présentations commerciales, gains et versements, ainsi que les commandes de compte et d’équipe dont dispose chaque rôle. Les commandes réservées aux affiliés ne figurent pas dans le tableau public ci-dessous, qui liste les trois rôles de l’interface web ; un compte affilié les voit dans GET /api/common/commands et dans lg commands. Une entreprise qui est à la fois affiliée et chargeur, transporteur ou commissionnaire en douane voit les deux ensembles.

Un agent devrait commencer par lg users get (nom d’API get_current_user) : cette commande renvoie le rôle, le niveau d’accès de la clé utilisée, les permissions effectives, et indique si une approbation juridique en attente ou un numéro de téléphone non vérifié bloque actuellement les écritures — l’agent peut ainsi anticiper un refus au lieu de le subir.

La couverture est mesurée par un rapport qui compare le registre de commandes aux endpoints d’écriture de l’interface web ; un endpoint de l’interface web ajouté sans commande y apparaît comme non prouvé. Si vous trouvez une action de l’interface web que vous ne pouvez pas atteindre par une commande, signalez-la au support.

Installation et identité

lg est un script Node en un seul fichier, sans dépendance (Node 18+), publié sur npm sous le nom @lgstv/lg. Le paquet installe deux commandes identiques, lg et logistivo — utilisez logistivo si lg est déjà un alias sur votre machine. Comme il s’agit d’un seul fichier, copier lg.mjs sur le serveur ERP d’un client fonctionne aussi. Vous n’avez pas besoin du client pour construire une intégration ERP/CRM — les mêmes commandes sont disponibles en HTTP.

1. Installer — Node 18 ou plus récent est tout ce qu’il vous faut ; il n’y a aucune autre dépendance. Installez-le globalement ou exécutez-le sans installation via npx.

2. Se connecter avec un code d’approbation — lg login affiche un court code d’approbation et ouvre logistivo.com/connect dans votre navigateur ; via SSH, en CI ou avec --no-browser, il affiche seulement l’adresse, que vous pouvez ouvrir sur n’importe quel appareil, y compris votre téléphone. Connectez-vous-y comme d’habitude, vérifiez que le code affiché sur la page est bien celui de votre terminal, choisissez un accès en lecture seule ou en lecture et action, puis approuvez. Rien de secret n’est saisi dans le terminal : ni mot de passe, ni clé, ni code à usage unique. La session reste ensuite ouverte sur cet ordinateur — fermer le terminal ne vous déconnecte pas, et lg renouvelle le token d’accès de lui-même avant son expiration. lg logout met aussi fin à la session sur le serveur, et vous pouvez la supprimer dans l’interface web sous Settings › Integration keys › AI and CLI connections. lg login --password (e-mail et mot de passe) reste disponible comme solution de repli. Nécessite lg 0.11 ou une version ultérieure.

3. Pour les serveurs et la CI : une clé, pas un code d’approbation — Sur un serveur ou dans un job CI, personne n’est là pour approuver un code dans un navigateur. Émettez une clé dans l’interface web (Settings › Integration keys) ou avec lg keys create — éventuellement au nom d’un membre de l’équipe dédié, aux droits strictement limités — et placez-la dans LOGISTIVO_TOKEN, ou enregistrez-la avec lg login --api-key. Tant que la variable est définie, le fichier de configuration n’est jamais lu. Détails : la section « Se connecter avec une clé API ».

4. Vérifier et explorer le catalogue — lg commands liste toutes les commandes que votre rôle peut voir, regroupées par domaine ; lg help affiche les verbes de ce domaine, et lg help affiche la liste complète des paramètres d’une commande avec leurs types et ceux qui sont obligatoires. Aucun de ces textes ne vit dans le client — tout provient du catalogue. N’oubliez pas que la liste dépend de votre rôle : les verbes d’un même domaine ne sont pas les mêmes pour un transporteur et pour un chargeur (loads set-status, par exemple, ne figure que dans le catalogue transporteur). S’il manque un verbe, vérifiez votre rôle avant de vérifier votre installation.

Ne mettez jamais la clé dans le contrôle de version, dans un journal CI ou dans une fenêtre de chat. Si vous vous êtes connecté avec un code d’approbation ou une clé d’intégration, lg logout met aussi fin à la connexion sur le serveur ; dans une session par mot de passe (lg login --password), il supprime uniquement la copie LOCALE et le dit explicitement. Le registre d’audit enregistre vos arguments mais masque en *** tout champ dont le nom contient token, password, IBAN, card ou OTP, ainsi que tout paramètre qu’une commande déclare secret.

Ergonomie du client

Ces fonctionnalités facilitent l’usage quotidien du client `lg`. Aucune ne modifie le contrat HTTP — ignorez cette section si vous intégrez directement en HTTP.

Se connecter avec une clé API

Un ERP, un CRM, un script ou un agent IA se connecte à Logistivo avec une clé d’intégration émise dans l’interface web ou avec lg keys create. La clé agit au nom d’un véritable membre de l’équipe : le rôle de cette personne détermine la liste des commandes, le niveau d’accès de la clé détermine l’importance du travail autorisé, et lorsque cette personne quitte l’équipe, la clé disparaît avec elle. Il n’existe pas de « compte de service » synthétique et il n’y en aura pas — l’isolation multi-tenant est liée à l’utilisateur, et une passerelle sans propriétaire est de celles qui continuent d’écrire en silence pendant des années.

1. Émettre une clé dans l’interface web — Settings › Integration keys › New key. Donnez-lui un nom (« ERP nightly sync »), choisissez la portée et le niveau d’accès, et raccourcissez éventuellement sa durée de vie. La clé n’est affichée qu’une seule fois, sur cet écran uniquement ; le serveur ne la stocke pas. Si vous la perdez, « Rotate » en émet une nouvelle — l’ancienne devient immédiatement invalide.

2. Se connecter depuis le terminal — lg login --api-key valide la clé, affiche l’identité et la portée, et télécharge le catalogue de commandes. lg whoami demande au serveur sous quel utilisateur, quelle entreprise, quel rôle et quelle portée vous vous exécutez. lg logout révoque aussi la clé sur le serveur, tout comme il y met fin à une session ouverte par code d’approbation (seul le lg logout d’une session par mot de passe ne peut pas le faire).

3. Ou en HTTP pur — La clé est un token d’accès transmis dans l’en-tête Authorization: Bearer et fonctionne directement avec les trois endpoints de commandes. Sur un serveur ou en CI, placez-la dans LOGISTIVO_TOKEN ; tant que cette variable est définie, le fichier de configuration n’est jamais lu.

4. Ou gérer les clés depuis le terminal : lg keys — lg keys est le jumeau en terminal de l’écran de l’interface web : mêmes endpoints, même barrière (la permission de gestion d’équipe, et une clé de niveau manager lorsque vous l’appelez avec une clé). create, rotate et transfer renvoient un nouveau token une seule fois : avec --save-profile=NAME, il va directement dans un profil local et n’est jamais affiché ; sinon, lg l’affiche une fois. En mode --json, le token n’est inclus que si vous ajoutez --show-secret, et un appel --json sans aucune de ces deux options est refusé avant l’envoi de la requête, afin que le token ne puisse pas atterrir en silence dans la sortie d’un script. rotate, transfer et revoke demandent une confirmation (--yes) ; une clé est désignée par son nom exact ou par son uuid, jamais par approximation. Le niveau d’accès est conservé tel quel sur une clé renouvelée ou transférée.

La portée s’applique à toute la surface de l’API, pas seulement au catalogue de commandes : une clé en lecture seule laisse passer les requêtes de lecture (GET/HEAD/OPTIONS), et toute autre requête d’écriture répond 403 forbidden. Il existe deux exceptions délibérées, toutes deux protégées par une barrière plus fine : l’endpoint d’exécution des commandes (la décision vient de l’indicateur read_only propre à la commande, car les commandes qui n’écrivent pas sont elles aussi appelées en POST) et une clé qui se révoque elle-même (lg logout, autorisé aussi sur une clé en lecture seule). La portée, les autorisations par domaine et le niveau d’accès sont trois axes indépendants qui doivent tous être satisfaits : une clé de lecture ne peut pas écrire, même au niveau manager, une clé d’écriture employee ne peut pas gérer l’équipe, et une autorisation de domaine loads:write n’ouvre pas les tâches de gestion. Émettez la clé au nom du membre le moins privilégié qui couvre vos besoins : c’est le rôle qui décide quels enregistrements vous pouvez voir, tout simplement.

Les dix premières minutes : de l’interface web à votre premier appel

Les minutes les plus coûteuses d’une intégration sont les dix premières. Les quatre étapes ci-dessous tracent ce chemin dans l’ordre ; le même ordre existe dans l’interface web sous forme d’assistant (Settings › Integration › Setup wizard), et l’assistant appelle exactement les endpoints décrits ici. Un développeur qui n’a pas accès à l’assistant arrive au même point en lisant cette section.

Aucune des quatre étapes ne se termine par « je l’ai envoyé ». Savoir si la clé fonctionne vraiment, c’est le registre des requêtes qui le dit ; savoir si le webhook arrive vraiment, c’est le registre des livraisons. Les deux sont accessibles dans l’interface web et dans lg. Une configuration jamais mesurée est une configuration dont on croit seulement qu’elle fonctionne.

Sandbox : s’entraîner à écrire sans toucher à la production

Le preflight (dry_run) vous dit ce qu’une commande FERAIT ; mais savoir si l’idempotence rejoue vraiment, si le webhook est vraiment livré, à quoi ressemblent les branches d’erreur et d’où vient le crédit, cela ne se voit que dans une VRAIE écriture. C’est à cela que sert la sandbox : un tenant distinct qui reflète votre entreprise — même code, même catalogue de commandes — et qui ne touche jamais votre registre de production.

La sandbox n’est pas un indicateur de requête, c’est un TENANT. Une entreprise Logistivo distincte ouverte pour la vôtre : son propre utilisateur, son propre forfait, son propre registre de crédits, ses propres abonnements webhook. L’environnement est DÉDUIT de l’entreprise de la clé et ne peut pas être fourni par l’appelant, si bien que même si vous choisissez le mauvais profil, la requête s’arrête côté serveur avec 403 ENVIRONMENT_MISMATCH. « Je croyais écrire dans la sandbox et j’ai écrit en production » est structurellement impossible.

La surface est identique — et c’est mesuré : le catalogue que récupère une clé sandbox est le même que celui de votre clé de production (sur une clé de chargeur, 448 commandes des deux côtés). Une passerelle qui fonctionne dans la sandbox fonctionne en production ; rien de ce que vous apprenez n’est à réapprendre.

La sandbox ne s’ouvre pas vide : elle contient des données d’amorçage générées (et NON copiées) — contacts, chargements dans les deux sens et demandes. Les noms sont ouvertement inventés et les adresses e-mail se trouvent dans le domaine @sandbox.invalid ; aucune ligne d’un vrai tenant n’est jamais lue, clonée ni utilisée comme modèle.

Il n’existe volontairement AUCUN bouton de réinitialisation, et aucun enregistrement, fichier ou document n’est jamais supprimé. Si vous voulez repartir de zéro, le renouvellement ouvre une nouvelle génération et l’ancienne reste, retirée. Quelques centaines de lignes de répétition accumulées ne coûtent rien à côté d’une routine de suppression branchée sur le mauvais tenant.

Le modèle de commande

Il n’y a qu’une seule grammaire, et elle ne connaît pas d’exception. Pour savoir comment appeler une commande, il vous suffit de ce que le catalogue en dit.

lg loads list --status=in_transport --limit=20 --json

Les noms d’options sont les noms de paramètres du schéma, et lg accepte les deux graphies : --load_code et --load-code atteignent le même champ (lg help affiche la forme kebab-case). Voir une option vous indique donc dans quel champ JSON elle aboutit, et passer de la CLI au HTTP ne demande aucune traduction de noms. Un paramètre booléen écrit sans valeur vaut true (--only_open) ; l’inverse est --no-only_open. Écrivez un argument dont la valeur commence par un tiret sous la forme --flag=value, sinon une option oubliée avale silencieusement la suivante comme valeur. Sur une commande qui prend un fichier, placez @ devant un chemin local (--attachment_uuid=@./cmr.pdf) — voir Commandes qui prennent des fichiers.

Exemples réels

Chaque commande ci-dessous existe aujourd’hui dans le catalogue ; aucun nom ni paramètre n’est inventé ici. Les sorties sont abrégées. Elles montrent le niveau `data.result` ; en HTTP, le même objet arrive à l’intérieur de l’enveloppe `{success, data:{…}}` (voir l’API de commandes HTTP).

Sans le client : l’API de commandes HTTP

lg est une commodité, pas la porte d’entrée. Le catalogue et l’exécution passent tous deux par trois endpoints HTTP (les fichiers en ont un quatrième, l’endpoint d’envoi — voir Commandes qui prennent des fichiers) ; une intégration ERP ou CRM s’y connecte directement. L’identité est un token d’accès personnel transmis dans Authorization: Bearer. CHAQUE réponse revient dans la même enveloppe : `{ "success": bool, "data": { … }, "code": "…", "message": { … } }`. Tout le contrat vit dans `data` — `contract_version`, `command`, `status` et, comme sortie de la commande, `result` ; en cas d’échec, `error_code`, `error_key` et `error`. Les exemples de réponses de cette page montrent l’objet `data` plutôt que l’enveloppe, pour gagner de la place ; n’oubliez pas de déballer `data` dans votre propre code. `lg --json` affiche lui aussi l’enveloppe ENTIÈRE, c’est pourquoi les chemins jq commencent par `.data.result…`. La seule exception est `/api/public/status` : cet endpoint est sans enveloppe, afin que les outils de surveillance puissent alerter sans l’analyser.

Demander une confirmation avec 200 + ok:false est interdit et n’arrivera jamais. La raison est simple : les clients traitent 200 comme un succès, donc une demande de confirmation renvoyée en 200 tuerait le flux en silence et personne ne le remarquerait. La confirmation est toujours un 409.

Commandes qui prennent des fichiers

33 commandes du tableau prennent un document ou une photo : joindre une CMR à un chargement, lire les codes HS d’une facture, créer un véhicule à partir de son certificat d’immatriculation, estimer une palette à partir de photos, envoyer un document d’entreprise, rapprocher une facture d’une liste de colisage. Dans le navigateur, vous déposez le fichier dans un formulaire ; partout ailleurs, cela se fait en deux étapes — envoyer le fichier, puis transmettre son identifiant à la commande.

Un paramètre prend un fichier lorsque son nom se termine par attachment_uuid ou attachment_uuids — photos[].attachment_uuid dans une liste, front_attachment_uuid sur un chèque. Vous n’avez pas à vous en souvenir : chaque ligne du catalogue porte file_params avec les chemins exacts, le schéma marque ces champs (format: uuid, x-logistivo-file: true), et la réponse du catalogue porte un bloc attachments avec l’adresse d’envoi, les types acceptés, les limites de taille et la durée de vie. lg help les affiche aussi.

Envoyer un fichier est une écriture : une clé à portée lecture ne peut donc pas en envoyer ; toute clé à portée écriture le peut, quel que soit son niveau d’accès. Les envois appartiennent à l’utilisateur, pas à la clé : deux clés du même utilisateur voient les mêmes envois, et personne ne peut utiliser ceux de quelqu’un d’autre. Un identifiant inconnu, expiré ou qui ne vous appartient pas est simplement introuvable, et le motif n’est pas divulgué.

Les pièces jointes du chat du tableau de bord et les envois des commandes sont des espaces séparés : un fichier joint dans le chat de l’assistant ne peut pas être transmis à une commande par son identifiant, et un envoi de commande n’apparaît jamais dans un fil de discussion.

Un envoi n’est pas une commande et n’a aucun effet métier en soi : rien n’arrive à vos données tant qu’une commande n’utilise pas l’identifiant. C’est aussi pourquoi un envoi ne demande jamais de confirmation — c’est la commande qui consomme le fichier qui porte la barrière.

La barrière de confirmation et les commandes irréversibles

Chaque commande du catalogue porte deux indicateurs : confirmation_needed (exige-t-elle une approbation explicite avant de s’exécuter) et irreversible (son effet peut-il être annulé). Ce sont deux questions distinctes, et toutes deux sont appliquées sur le serveur plutôt que laissées aux bonnes manières du client.

La barrière est une affaire de responsabilité, pas de règles métier. Déposer une offre est le premier pas vers un contrat de fret ; émettre une facture crée une écriture comptable ; un mouvement de stock modifie un registre. Rien de tout cela ne devrait jamais pouvoir être exécuté deux fois par accident.

La barrière est lue sur la commande EFFECTIVE. Lorsqu’une commande en enveloppe une autre — comme le fait l’exécuteur générique du chat —, la barrière provient des indicateurs de la commande qui s’exécutera réellement, pas de l’enveloppe. Avec un indicateur fixe, l’un de deux échecs serait inévitable : soit la commande de facturation s’exécuterait sans confirmation, soit chaque recherche de pays ferait apparaître une carte de confirmation.

read_only et « sans barrière » ne sont pas la même chose. Il existe trois états distincts : invoices preview est sans barrière et n’écrit rien ; loads create est soumis à la barrière et écrit ; invoices create est sans barrière mais écrit — un brouillon voit le jour. Un seul test tranche : au retour de la commande, une ligne de la base de données a-t-elle changé ?

Certaines actions ne sont pas derrière cette barrière parce que ce ne sont pas du tout des commandes : acheter un forfait, ajouter une carte avec 3D Secure, approuver des textes juridiques et clôturer un compte se font dans le navigateur de la personne elle-même. Pour celles-ci, lg handoff-links generate renvoie un lien logistivo.com — gratuit, sans aucune écriture, ne portant aucun secret — et la personne termine l’étape là-bas. Aucun indicateur, quelle que soit sa valeur, ne transforme un paiement ou un consentement en appel d’API.

Il est techniquement possible pour une intégration de franchir automatiquement la barrière à chaque appel, mais ce n’est pas recommandé. Le bon modèle consiste à automatiser la barrière uniquement pour les commandes que vous savez réversibles, et à acheminer les commandes irréversibles vers la file d’attente d’un humain.

Preflight : voir ce que ferait une commande d’écriture, sans l’exécuter

L’étape la plus coûteuse du câblage d’une passerelle ERP consiste à vérifier que votre corps de requête est correct en exécutant RÉELLEMENT la commande : un brouillon de facture dans le registre de production, un crédit brûlé, un nettoyage manuel. dry_run (exécution à blanc) met fin à cela — vous ajoutez un champ à la requête, la commande ne s’exécute pas, et vous lisez ce qui se passerait.

Le registre contient aujourd’hui 519 commandes ; 233 d’entre elles ne font que lire et n’écrivent rien. Sur les 286 commandes d’écriture restantes, 251 sont soumises à la barrière : appelées sans confirm, elles répondent déjà 409 et décrivent ce qu’elles feraient. La lacune concernait les 35 autres — brouillons de factures, brouillons de documents d’exportation, lignes : exactement la chaîne qu’une passerelle câble en premier. Pour celles-là, appeler revenait à faire.

Il existait une seconde lacune, même sur les commandes soumises à la barrière : la validation du schéma s’exécute APRÈS la barrière. Un appel auquel il manquait un argument recevait donc d’abord la carte de confirmation, la confirmait, et n’apprenait qu’ensuite que « ce champ est manquant ». Un preflight répond aux deux questions en un seul aller-retour.

Et le bug de passerelle le plus courant — et le plus tardivement découvert — est le suivant : vous envoyez un champ qui ne figure pas dans le schéma (customer_id là où recipient_contact_id est attendu), le serveur l’ignore en silence, la commande renvoie 200 et l’enregistrement atterrit au mauvais endroit. La réponse du preflight liste ces champs par leur nom sous arguments.ignored ; aucun message d’erreur ne le fera jamais.

Un preflight n’écrit rien, et c’est structurel avant d’être une mesure : le chemin du preflight n’entre jamais dans le handler de la commande, donc cela vaut pour toutes les commandes, y compris celles ajoutées plus tard. Cela a aussi été mesuré lors de la mise en service : chaque instruction SQL émise pendant le preflight de toutes les commandes d’écriture alors présentes dans le catalogue (deux jeux d’arguments, deux rôles, 178 preflights) a été capturée. Pas un seul INSERT/UPDATE/DELETE, pas un seul job mis en file, pas un seul e-mail ; le nombre de lignes de 31 tables et le solde de crédits sont restés inchangés.

Le code de sortie est celui que produirait l’appel réel — s’exécuterait 0, barrière de confirmation 4, conflit d’idempotence 2, tout le reste 1 — si bien que « lg … --dry-run || exit » est une barrière pertinente dans une étape de CI.

Idempotence : pourquoi une nouvelle tentative ne crée pas de doublon

Un ERP ne lit pas votre message d’erreur, ne voit pas votre écran et renverra la même requête après un délai d’expiration. Cette section existe précisément pour ce comportement.

Un appel porteur d’une idempotency_key (clé d’idempotence) s’exécute au plus une fois. Le second appel ne réexécute pas la commande ; il renvoie le résultat enregistré avec le statut replayed. Quand le réseau tombe, quand une requête expire ou quand une file d’attente prend deux fois le même job, vous obtenez le premier résultat au lieu d’une facture en double.

Utilisez une clé sur chaque commande d’écriture. Le champ read_only du catalogue vous donne gratuitement cette distinction : toute commande avec read_only: false mérite une clé d’idempotence.

Votre stratégie de nouvelles tentatives doit utiliser un backoff exponentiel et porter la MÊME clé à chaque tentative. Générer une nouvelle clé à chaque tentative revient à ne pas utiliser l’idempotence du tout. N’envoyez pas la même clé en parallèle : l’exécution au plus une fois est garantie pour des nouvelles tentatives séquentielles, pas pour deux requêtes qui se font la course.

Synchronisation incrémentale et pagination

Une passerelle ERP/CRM doit récupérer ce qui a changé, pas tout, toutes les heures. Chaque commande de liste — loads list, demands list-own, contacts list, invoices list, teammates list, load-groups list, reservations list, drivers list, tenders list, products search, warehouses search — accepte deux paramètres communs et renvoie un champ commun.

Pourquoi il n’y a pas d’offset : sur une table vivante, un offset dérive. Une ligne insérée entre deux pages vous fait sauter ou compter deux fois un enregistrement, en silence. Le curseur n’avance que vers l’avant, donc un enregistrement inséré ou mis à jour en cours de parcours arrive en fin de liste : rien n’est perdu, rien n’est dupliqué.

Un chargement initial complet utilise le même mécanisme : commencez avec updated_since=1970-01-01T00:00:00Z et suivez next_cursor jusqu’à ce qu’il vaille null. Il n’existe pas d’endpoint « export » séparé, car aucun n’est nécessaire.

Même chose avec lg : lg loads list --updated_since=2026-09-01T00:00:00Z --limit=100 --json, puis lg loads list --cursor= --json.

Pour la synchronisation suivante, réglez updated_since sur le plus grand updated_at que vous avez vu. Les horodatages ont une précision d’une seconde, donc un enregistrement situé à la limite peut arriver deux fois — sans conséquence tant que vous faites un upsert par external_ref ou par code de votre côté. Sans updated_since ni cursor, une commande s’exécute en mode chat : ordre pensé pour un humain, 20 lignes au maximum, next_cursor à null et truncated indiquant « il y en a d’autres ». N’utilisez pas ce mode pour la synchronisation.

Clés d’erreur lisibles par machine pour cette section : INVALID_CURSOR, INVALID_UPDATED_SINCE, DUPLICATE_EXTERNAL_REF, EXTERNAL_REF_TOO_LONG, EXTERNAL_REF_INVALID. Toutes répondent HTTP 422 avec error_code: "failed" et result.error_key dans le corps. Branchez sur la clé, jamais sur le texte.

Votre propre clé : external_ref

Ne faites pas de nos identifiants auto-incrémentés (invoice_id, contact_id) la clé métier de votre ERP ; ils sont opaques et propres à votre entreprise. Les chargements, demandes, contacts, factures, produits et entrepôts portent votre clé à la place.

Webhooks sortants : nous vous envoyons un POST quand quelque chose se produit

Tout ce qui précède relevait du pull : vous demandez, nous répondons. Une passerelle ERP devait interroger le serveur une fois par minute pour apprendre qu’un chargement avait changé de statut, qu’une facture avait été émise ou qu’une offre était arrivée. Ce n’est plus le cas : enregistrez une adresse https, choisissez les événements qui vous intéressent, et un POST signé arrive quand ils se produisent.

Le push a été ajouté À CÔTÉ du pull, pas à sa place. La synchronisation incrémentale (updated_since + cursor) reste intacte et demeure le bon outil : le premier chargement complet, le rattrapage de l’historique et la réponse à « ai-je manqué quelque chose » s’y trouvent. Les webhooks réduisent la latence de quelques minutes à quelques secondes. Une passerelle saine utilise les deux — le webhook comme déclencheur, la synchronisation comme filet de sécurité.

Le payload est un SIGNAL, pas une copie de l’enregistrement. Il porte les identifiants (id, votre propre external_ref, statut, heure) ; vous récupérez le détail avec votre propre clé via GET, et cet appel passe déjà par vos propres barrières d’autorisation. La surface d’exposition reste donc au niveau des identifiants. Deux champs sont volontairement absents : l’identité de l’entreprise qui a déposé une offre (les offres à l’aveugle apparaissent sous un alias dans l’interface web ; mettre l’identité dans un webhook percerait cette confidentialité par un second canal) et les coordonnées de contact des réservations (inutile de faire transiter des données personnelles par un canal supplémentaire).

Les noms d’événements sont GELÉS. Si le sens d’un nom change, un nouveau nom est ouvert et l’ancien vit pendant une période de dépréciation. L’ajout d’un nouveau champ au payload n’incrémente PAS la version du contrat — un consommateur doit ignorer les champs qu’il ne connaît pas.

Catalogue des événements

Chacun des 13 événements ci-dessous est adossé à une classe d’événement RÉELLEMENT émise. Il n’y a pas de noms « ce serait bien » dans le catalogue : un événement annoncé mais jamais déclenché est un événement que l’intégrateur attend pour toujours, et cet échec est silencieux. Ce tableau n’est pas non plus rédigé à la main — il est tiré du catalogue en direct à chaque chargement de la page.

invoice.payment_recorded est volontairement ABSENT : cette classe d’événement existe dans le code mais n’est émise nulle part. L’événement de paiement sera ajouté le jour où la classe sera réellement déclenchée — pas avant. Annoncer un événement trop tôt est pire que de ne jamais l’ajouter.

Vérifier la signature

Chaque requête porte un en-tête X-Logistivo-Signature : t=,v1=. La chaîne signée est "." et l’algorithme est HMAC-SHA256. Le préfixe v1 rend le schéma versionnable : si un second schéma apparaît un jour, l’en-tête portera t=…,v1=…,v2=… et vous choisirez celui que vous connaissez.

Nous publions deux récepteurs de référence sans dépendance, l’un pour Node et l’autre pour PHP pur. Tous deux appliquent les quatre règles ci-dessus et se testent eux-mêmes avec --selftest : signature valide, corps altéré, mauvais secret, horodatage hors fenêtre, en-tête malformé, piège de la resérialisation et les deux types de répétition (même livraison / même fait). Le vérificateur est l’exact pendant du code que nous utilisons nous-mêmes — nous consommons le contrat que nous publions.

Livraison, nouvelles tentatives et mise en sourdine

Votre récepteur ne sera pas toujours disponible. Le rail part de ce principe : un échec transitoire entre dans le calendrier des nouvelles tentatives, un échec permanent met l’abonnement en sourdine — et le MOTIF de la mise en sourdine est toujours enregistré.

Le motif de mise en sourdine est lisible par machine et stable (delivery_failures, endpoint_gone, unsafe_target, key_revoked, by_user) et apparaît à la fois dans l’interface web et dans `lg webhooks list`. Une mise en sourdine sans motif laisserait la question « pourquoi rien n’arrive » sans réponse. La réactivation remet à zéro le compteur et l’horodatage.

C’est vous qui écrivez l’adresse ; c’est NOTRE serveur qui fait la requête. Sans barrière, un tenant qui saisirait une adresse de métadonnées cloud pourrait nous faire émettre des requêtes depuis l’intérieur de notre propre réseau. L’adresse est donc vérifiée à l’enregistrement et à CHAQUE livraison :

Santé et statut : personne ne devrait remarquer une passerelle muette avant vous

Une passerelle d’intégration meurt rarement avec fracas. En général, elle se tait au petit matin : un token expire, la personne au nom de laquelle la clé s’exécute quitte l’équipe, une portée est restreinte, ou l’endpoint récepteur se met à renvoyer 403. Le premier à s’en apercevoir est presque toujours le client. lg status existe pour inverser cet ordre.

Une seule commande répond à deux questions à la fois, car pendant une panne les deux se posent au même moment : la plateforme est-elle en service, et ma passerelle est-elle en bonne santé ? La ligne plateforme fonctionne sans token — lors d’une défaillance, le token lui-même est le premier suspect, et lier la question du statut à ce token vous laisserait sans réponse précisément au moment où vous en avez besoin.

Le résumé de santé ne tient aucun nouveau registre ; il réunit trois sources existantes en une seule phrase : le compteur d’utilisation quotidien (combien vous avez appelé et combien a échoué), le registre des requêtes (quelle requête, quand, et pourquoi elle a été rejetée) et le registre des livraisons de webhooks sortants (l’événement a-t-il atteint le récepteur). Le taux d’erreur est toujours lu sur le compteur quotidien ; le registre détaillé est plafonné et le dit clairement lorsque le plafond est atteint.

Le même résumé apparaît dans l’interface web : sous Settings › Integration › Keys, chaque ligne porte un badge et, en dessous, le motif. Les seuils ne sont pas calculés côté client ; le niveau attribué par le serveur est celui qu’affichent à la fois l’interface web et lg status, si bien que les deux écrans ne peuvent jamais dire des choses différentes sur la même clé.

Votre outil de surveillance n’a besoin d’aucun token. L’endpoint ne renvoie que des mots de statut ; il ne porte ni version, ni nom d’hôte, ni nom de file d’attente, ni nombre de jobs, ni texte d’erreur, ni donnée client — et il ne le peut pas.

Le résumé de santé est produit avec un nombre constant de requêtes par appel ; ce nombre n’augmente pas avec le nombre de clés. Pour le détail, consultez les requêtes récentes de la clé concernée avec lg logs et la passerelle sortante avec lg webhooks list.

Erreurs et codes de sortie

Ce sur quoi une machine se branche, c’est une chaîne stable. Le texte du champ error s’adresse à un humain, est localisé et change sans préavis — une intégration qui se branche sur le texte d’un message n’est pas prise en charge, et le fait qu’elle casse n’est pas un bug.

Deux taxonomies, deux champs. error_code est la couche transport (le tableau ci-dessus) et c’est sur lui que vous vous branchez en premier. error_key est la clé stable de règle métier à l’intérieur de failed — INSUFFICIENT_CREDITS, NOT_FOUND, DUPLICATE_CONTACT, INSUFFICIENT_STOCK — publiée sous forme de dictionnaire dans le catalogue (error_keys) et ci-dessous. Une commande qui n’émet pas encore de clé renvoie error_key: null ; traitez une clé absente du dictionnaire comme un échec générique.

Engagement de stabilité

La question la plus coûteuse d’un contrat d’intégration est : « le code que j’écris aujourd’hui fonctionnera-t-il encore demain ? » Un seul nombre y répond : contract_version dans le catalogue, et l’en-tête X-Contract-Version sur chaque réponse. Il répond à une seule question — du code écrit auparavant fonctionne-t-il encore sans modification ? Si le nombre n’a pas augmenté, oui. S’il a augmenté, non : quelque chose sur quoi vous vous appuyiez a été retiré ou restreint.

Parce qu’une annonce ne retire rien : votre code existant fonctionne exactement comme avant, le lendemain de l’annonce. Incrémenter à l’annonce émettrait deux signaux pour un seul changement et vous habituerait à ignorer le premier — affaiblissant le signal qui compte vraiment. L’annonce voyage déjà avec l’appel que vous effectuez : deprecated_at et replaced_by sur la ligne du catalogue, un en-tête X-Deprecated et un bloc deprecated dans la réponse de cette commande, et un avertissement d’une ligne sur stderr si vous utilisez lg. Vous n’avez rien à faire pour apprendre que quelque chose va disparaître ; c’est déjà dans votre journal.

Au moins deux trimestres civils s’écoulent entre l’annonce et le retrait. Pendant toute la fenêtre, l’ancienne commande répond encore et avertit à chaque appel. Lorsqu’un nom CLI est abandonné, son successeur reprend ce nom comme alias : pendant la fenêtre, l’ancien nom atteint l’ancienne commande, et le jour où celle-ci est retirée, le même appel aboutit sur la nouvelle — sans que vous ayez à livrer une nouvelle version du client.

preview_invoice → get_invoice (annoncé le 3 septembre 2026). get_invoice renvoie le même bloc d’aperçu et y ajoute les lignes, la ventilation des taxes, les paiements enregistrés, le solde restant et l’adressage par votre propre clé ERP (external_ref). preview_invoice continue de fonctionner, tout comme lg invoices preview — les deux avertissent.

Table d’identité des commandes

Le tableau ci-dessous n’est pas rédigé à la main : il est généré à partir du registre de commandes en direct à chaque fois que cette page est servie. Une commande ajoutée sur le serveur y apparaît d’elle-même. La colonne « Barrière » vous indique si une commande demande une confirmation et si son effet peut être annulé ; la colonne « Rôles » vous indique quels types de comptes la trouveront dans leur catalogue. Des badges signalent les commandes qui exigent une clé de niveau manager, celles qui prennent un fichier et celles qui ne s’exécutent que depuis lg.

Le nom d’API (list_loads) est gelé et ne changera pas ; c’est le nom que vous utilisez dans les appels HTTP. L’identité CLI (loads list) relève de la politique de versionnage : une fois annoncée, elle ne change qu’avec une incrémentation de version et une fenêtre de migration, pendant laquelle le catalogue sert l’ancienne paire comme alias. Les descriptions proviennent de l’enregistrement propre à chaque commande et sont abrégées — pour le texte complet, les types de paramètres et les valeurs enum, consultez l’endpoint du catalogue ou exécutez lg help . Les commandes réservées aux affiliés ne sont pas listées ici ; un compte affilié les voit dans son propre catalogue.

Intégration ERP et CRM — Identité : pas de comptes de service, un véritable utilisateur à la place

Un ERP ou un CRM se connecte à Logistivo en tant que véritable utilisateur de l’entreprise, détenteur d’un token d’accès personnel créé depuis l’interface web. Un compte de service synthétique — sans entreprise, exempté du cloisonnement, dont personne n’est responsable — n’est jamais créé.

C’est une question d’architecture, pas de préférence. L’isolation multi-tenant repose entièrement sur l’entreprise de l’utilisateur ; pour une identité sans entreprise, le périmètre d’isolation devient inopérant, et une seule requête erronée avec ce token lit des données qui ne lui appartiennent pas. La deuxième raison est l’audit : la colonne utilisateur du registre relie « qui a exécuté ceci » à une personne ; un compte de service vide cette colonne de son sens et laisse une intégration qui émet des factures sans propriétaire. La troisième est la révocation : si un token appartient à une personne, l’intégration se tait quand cette personne s’en va. Une passerelle sans propriétaire est la passerelle qui continue d’écrire discrètement pendant des années.

Intégration ERP et CRM — Versionnage et dépréciation

La réponse du catalogue porte un entier contract_version ; les lignes de commande portent since, deprecated_at et replaced_by. La version du contrat n’augmente qu’en cas de changement cassant.

Intégration ERP et CRM — Identifiants stables : ne confiez jamais un identifiant auto-incrémenté à votre ERP comme clé

Les identifiants internes auto-incrémentés sont opaques et cloisonnés par tenant : l’identifiant valide d’une autre entreprise vous renvoie « introuvable », pas des données. Mais être opaque n’en fait pas des clés métier. Ce qu’un ERP stocke dans son propre enregistrement doit être une clé métier.

Intégration ERP et CRM — Pagination, filtrage et synchronisation incrémentale

Les commandes de liste prennent limit et renvoient count. Pour une synchronisation nocturne, le contrat s’engage sur ce qui suit ; une commande de liste qui l’a adopté annonce cursor et updated_since dans son JSON Schema, et tant que ce n’est pas le cas, traitez-la comme une petite fenêtre (plafond de 20 lignes, pas de curseur).

Intégration ERP et CRM — Limites de débit

La clé de limitation est le token, pas l’IP : un ERP arrive derrière un seul NAT, et une limite basée sur l’IP compterait toute une entreprise comme un seul utilisateur. Deux intégrations avec deux clés ne consomment jamais le quota l’une de l’autre — la seconde raison du principe « une intégration par clé ».

Intégration ERP et CRM — Ce que nous ne ferons pas

C’est souvent la partie la plus utile d’un contrat : savoir ce qui n’arrivera jamais vous permet de concevoir en conséquence.

Le catalogue lisible par machine

Cette page a été écrite pour des personnes. Pour un assistant IA ou un client automatisé, la même information existe sous forme de copie structurée, sans authentification.

GET /api/public/cli/catalog ne requiert aucune authentification et renvoie l’identité publique des commandes : le nom d’API gelé, l’identité CLI domaine/verbe, la description, les paramètres JSON Schema, les indicateurs read_only, confirmation_needed et irreversible, le credit_cost, ainsi que les champs d’accès key_level, permission, file_params, secret_parameters et cli_only. Il ne renvoie aucune donnée personnelle, aucune donnée de tenant et aucun enregistrement d’exemple — uniquement ce qu’est la surface.

La bonne manière pour un agent externe de l’utiliser : lire le catalogue, expliquer AVEC SES PROPRES MOTS quelles tâches sont possibles et, lorsqu’une exécution est nécessaire, inviter l’utilisateur à l’exécuter dans son propre environnement avec son propre token. L’agent ne demande, ne génère ni ne relaie jamais le token d’un utilisateur.

Connecter Logistivo à un assistant IA pour l’inscription et les demandes de fret est un canal distinct, avec sa propre page :

La définition lisible par machine : OpenAPI 3.1 et Postman

Le catalogue ci-dessus est le format propre à Logistivo. La même information est aussi publiée dans deux formats standard du secteur que vos outils comprennent déjà : une définition OpenAPI 3.1 et une collection Postman. Aucune des deux n’est rédigée à la main — toutes deux sont générées à partir du registre de commandes en direct à chaque requête, elles ne peuvent donc pas devenir obsolètes.

La définition EST le catalogue, pas une seconde réalité. Chaque commande apparaît comme une opération POST /api/common/commands/ ; le schéma du corps est le JSON Schema propre à la commande (octet pour octet ce que voit l’assistant), le schéma de réponse est l’enveloppe commune, les indicateurs de barrière (read_only, confirmation_needed, irreversible et le badge de crédit) se trouvent dans l’extension x-logistivo, et les clés d’erreur métier sous x-error-keys. Une commande abandonnée est marquée deprecated: true et reste appelable pendant sa fenêtre de six mois.

À côté de la surface de commandes, les endpoints REST qui portent le contrat d’intégration figurent aussi dans la définition : 10 endpoints de liste avec synchronisation incrémentale (updated_since + cursor + limit) et 7 endpoints d’écriture qui acceptent votre propre clé (external_ref). La surface REST de Logistivo est bien plus vaste que cela ; le reste est laissé de côté À DESSEIN. Ces payloads relèvent du contrat propre au produit, et un schéma écrit au jugé casserait votre client généré dès le premier appel — et vous chercheriez le bug dans votre code, pas dans le nôtre.

La définition est générée pour le RÔLE de la personne qui la demande : la commande d’un autre rôle n’entre jamais dans le document, car une définition est un contrat plutôt qu’un dictionnaire, et tout ce qu’elle contient doit pouvoir être appelé avec ce token. La version sans authentification est l’union des trois rôles, et chaque opération y indique sous x-logistivo.roles quels rôles la voient.

Ne nous croyez pas sur parole — vérifiez : npx @redocly/cli lint logistivo-openapi.json passe sans aucun avertissement.

Génération de code : un client typé à partir de la définition

Vous avez la définition ; générez maintenant du code fonctionnel à partir d’elle. `lg codegen` lit le document OpenAPI et écrit un client en un seul fichier, sans dépendance — TypeScript (déclarations de types et une fine couche d’appel) ou PHP (une classe cliente avec des array shapes PHPDoc). Pas de `npm install`, pas de composer : `fetch` côté TypeScript, `ext-curl` côté PHP.

La génération se fait sur VOTRE machine, pas sur notre serveur. Ce n’est pas une préférence : épinglez une fois la définition dans votre dépôt (`lg spec --out=...`) et la même commande s’exécute à chaque build, sans réseau ni token, si bien que votre CI ne dépend jamais de notre disponibilité. Et comme le code généré est du code qui S’EXÉCUTE dans votre ERP, le garder dans votre dépôt plutôt que de le récupérer depuis une API signifie que chaque modification apparaît dans `git diff`.

Le client généré gère correctement les trois rails de Logistivo, et il apprend lequel est lequel à partir du document plutôt qu’en devinant : chaque opération déclare son rail dans `x-logistivo.surface`. Le rail des commandes déballe l’enveloppe `{success, data:{…}}` ; une liste de synchronisation REST n’est PAS enveloppée et est renvoyée telle quelle (la déballer ferait perdre `next_cursor`) ; une écriture REST renvoie le corps produit propre à l’endpoint. Un générateur généraliste ne connaît pas cette distinction — le nôtre, si.

Les types ne sont pas devinés : l’interface d’arguments de chaque commande est générée à partir du JSON Schema même que voit l’assistant. Ce qui est obligatoire le reste, les enums deviennent des types union, les objets imbriqués deviennent des types imbriqués. En pratique : si le serveur exige un champ, l’appel qui l’oublie n’échoue pas avec un 422 en production — il échoue à la compilation.

Aucun horodatage de génération n’est écrit dans le fichier : la même définition avec les mêmes options produit les mêmes octets. Une nouvelle génération ne fait donc aucun bruit dans `git diff`, et la seule chose qui a réellement changé — le contrat — reste visible.

Périmètre en toute franchise

Nous préférons agrandir cette section plutôt que la réduire. La valeur d’un contrat d’intégration tient moins à ce qu’il promet qu’à la clarté avec laquelle il dit ce qu’il ne fait pas.

Questions fréquentes

À quoi sert la CLI Logistivo ?

Elle exécute le travail de votre compte Logistivo depuis un terminal, un script ou votre propre agent IA : chargements et voyages, demandes, offres et appels d’offres, flotte, conducteurs et leurs dépenses, documents, contacts et comptes courants, factures et comptabilité, entrepôt et stock, membres de l’équipe, tarifs HS et droits antidumping. Chaque action d’écriture de l’interface web a sa commande ; les paiements, le consentement juridique et la clôture du compte restent volontairement dans le navigateur. Les mêmes commandes peuvent être appelées en HTTP pour les intégrations ERP/CRM et via le canal MCP.

Comment les commandes sont-elles nommées dans la CLI Logistivo ?

La forme est lg --parameter=value. Le domaine est une entité au pluriel (loads, demands, invoices, export-documents) et le verbe provient d’un vocabulaire fermé qui ne s’enrichit que par décision délibérée (list, get, create, update, search, set-status, generate, etc.). Tous les verbes d’une entité vivent dans un seul domaine, et la paire (domaine, verbe) est unique dans tout le catalogue.

Comment m’authentifier auprès de la CLI ?

En mode interactif, lg login affiche un code d’approbation que vous confirmez dans votre navigateur après vous être connecté à Logistivo — aucun mot de passe ni aucune clé n’est saisi dans le terminal, et la session reste ouverte et se renouvelle d’elle-même jusqu’à ce que vous exécutiez lg logout. lg login --password (e-mail et mot de passe) reste disponible comme solution de repli. Sur les serveurs et en CI, utilisez une clé d’intégration émise dans l’interface web ou avec lg keys create : lg login --api-key l’enregistre dans un profil local, ou placez-la dans la variable d’environnement LOGISTIVO_TOKEN. En HTTP, le token transite dans l’en-tête Authorization: Bearer.

Puis-je créer un compte de service pour mon intégration ERP ?

Non. Une intégration se connecte avec le token d’un véritable utilisateur de l’entreprise créé dans l’interface web. Une identité synthétique sans entreprise rend l’isolation multi-tenant inopérante, vide de son sens la colonne « qui a exécuté ceci » du registre d’audit, et laisse une passerelle qui ne se tait pas lorsque son propriétaire s’en va. Le bon modèle est un utilisateur dédié, avec un rôle restreint et un propriétaire nommément désigné.

Logistivo dispose-t-il d’un environnement de test (sandbox) ?

Oui, et en libre-service : un tenant sandbox distinct est ouvert pour votre entreprise — même code, même catalogue de commandes, son propre registre de crédits. L’environnement est déduit de l’entreprise de la clé et ne peut pas être fourni avec la requête ; un appel effectué avec le mauvais profil s’arrête côté serveur avec 403 ENVIRONMENT_MISMATCH. Une demande ouverte dans la sandbox n’atteint jamais de vrais transporteurs, un événement sandbox n’est jamais livré à votre abonnement webhook de production, et votre solde de production ne bouge pas. Endpoints : POST /api/common/integration-sandbox (ouverture), POST /api/common/integration-sandbox/keys (clé), GET /api/common/integration-sandbox/go-live (liste de contrôle de mise en production). Côté lg, lg --sandbox et lg --live sélectionnent un profil pour une seule exécution.

Comment lire la sortie lg --json dans un script ?

La sortie est l’enveloppe HTTP complète : { "success": bool, "data": { "contract_version", "command", "status", "result" }, "code", "message" }. Le payload métier se trouve sous .data.result ; sur les commandes de liste, les lignes sont dans .data.result., le nombre dans .data.result.count et la page suivante dans .data.result.next_cursor. Le chemin jq correct ressemble donc à lg loads list --json | jq -r '.data.result.loads[].load_code'. Un chemin qui saute l’enveloppe ne produit pas d’erreur, il renvoie silencieusement null — dans un script de surveillance, cela devient une alarme qui ne cesse jamais de sonner. La seule exception est /api/public/status, dont le corps est volontairement sans enveloppe.

Si j’envoie deux fois la même requête, est-ce que j’obtiens des enregistrements en double ?

Pas si vous avez envoyé une idempotency_key. Un second appel avec la même clé ne réexécute pas la commande ; il renvoie le premier résultat avec le statut replayed. La clé doit être un UUID et rester la MÊME d’une tentative à l’autre — générer une nouvelle clé à chaque tentative revient à ne pas utiliser l’idempotence.

À quoi sert --yes ?

Elle franchit la barrière de confirmation. Lorsqu’une commande soumise à la barrière est appelée sans --yes, rien ne change : le serveur résume en une phrase ce qu’il s’apprête à faire, indique si c’est irréversible, et s’arrête (HTTP 409, code de sortie CLI 4). Répéter la commande avec --yes effectue le travail.

Comment détecter l’échec d’une commande dans un script ?

Lisez le code de sortie, jamais le texte du message : 0 succès, 1 rejet par une règle métier, 2 erreur de commande ou d’argument, 3 erreur d’authentification ou de rôle, 4 confirmation requise. En HTTP, les équivalents sont 200, 422, 404, 403 et 409, et le champ error_code du corps est stable. Le MESSAGE d’erreur est localisé et change sans préavis.

Comment découvrir les commandes disponibles par programmation ?

GET /api/public/cli/catalog pour la découverte sans authentification, et GET /api/common/commands pour la liste que votre compte peut réellement voir. Les deux renvoient les paramètres JSON Schema de chaque commande ; construisez votre client à partir d’eux plutôt que de coder en dur une liste de commandes.

Dois-je mettre à jour mon client lorsqu’une nouvelle commande est ajoutée ?

Non. Le client récupère sa liste de commandes dans le catalogue, donc une commande ajoutée sur le serveur apparaît sans nouvelle version du client. Une mise à jour n’est nécessaire que pour un changement cassant qui fait augmenter la version du contrat, et dans ce cas l’ancienne forme reste active pendant au moins six mois.

La CLI remplace-t-elle l’API REST existante ?

Non. La surface REST qui fait fonctionner les applications web et mobiles reste en place. Le catalogue de commandes ne remplace pas le CRUD des ressources ; c’est la couche des verbes — la manière unique, contrôlée par rôle, soumise à confirmation et auditée d’effectuer une action.

Comment obtenir une clé API Logistivo et que peut-elle faire ?

Un manager disposant de la permission de gestion d’équipe l’émet dans l’interface web, sous Settings › Integration keys, ou depuis un terminal avec lg keys create. La clé agit au nom d’un véritable membre de l’équipe et possède trois réglages indépendants : la portée (read ouvre le catalogue et les commandes qui n’écrivent pas, write ouvre toutes les commandes — la barrière de confirmation reste inchangée), des autorisations facultatives par domaine, et un niveau d’accès — employee, la valeur par défaut, pour les opérations courantes, ou manager pour l’équipe, les paramètres, les clés et l’argent. Elle n’est affichée qu’une seule fois et n’est pas stockée sur le serveur ; elle peut faire l’objet d’une rotation, d’un transfert et d’une révocation. Lorsque le membre pour lequel elle agit s’en va, la clé est fermée elle aussi. Il n’existe pas de compte de service.

Que se passe-t-il si je réutilise une clé d’idempotence pour une autre commande ?

Le serveur répond 409 idempotency_conflict et nomme la commande pour laquelle la clé a d’abord été utilisée ; rien ne s’exécute et rien n’est rejoué. Les clés sont limitées à votre entreprise, liées à une seule commande et verrouillées pendant 24 heures — passé ce délai, la même clé lance une nouvelle exécution.

Comment les limites de débit s’appliquent-elles à mon intégration ?

Par token, par minute, en trois compartiments : lectures du catalogue 120, commandes de lecture 120, commandes d’écriture 30. Un 429 porte toujours Retry-After et un corps avec retry_after, limit et bucket ; attendez ce délai et réessayez. Le catalogue publie les valeurs actuelles sous rate_limits.

Comment distinguer un rejet métier d’une erreur de transport ?

Branchez-vous d’abord sur error_code : failed (422) est un rejet métier et porte une error_key stable comme INSUFFICIENT_CREDITS ou NOT_FOUND ; server_error (5xx) et les erreurs réseau se réessaient avec la même clé d’idempotence ; pour rate_limited (429), on attend. Ne vous branchez jamais sur le texte de l’erreur.

Logistivo envoie-t-il des webhooks ? Pour quels événements ?

Oui. Vous enregistrez une adresse https et choisissez les événements qui vous intéressent ; un POST signé arrive quand ils se produisent. 13 événements sont disponibles à l’abonnement aujourd’hui : chargement créé / statut modifié / conducteur affecté, demande créée et statut modifié, offre déposée / révisée / acceptée, facture brouillon / émise / annulée, et réservation d’entrepôt créée / statut modifié. Chaque nom est adossé à une classe d’événement réellement émise — rien n’est annoncé pour n’être jamais déclenché, car un tel événement est un échec silencieux que l’intégrateur attend pour toujours. Les abonnements se créent sous Settings › Integration › Webhooks, ou depuis un terminal avec lg webhooks create.

Comment vérifier la signature d’un webhook Logistivo ?

Chaque requête porte un en-tête X-Logistivo-Signature : t=,v1=. La chaîne signée est ".", l’algorithme est HMAC-SHA256 et la clé est le secret de l’abonnement. Quatre règles : (1) vérifiez les octets BRUTS — décoder puis resérialiser le corps (JSON.stringify / json_encode) transforme les mêmes données en octets différents et la signature ne correspond plus ; c’est de loin l’erreur la plus fréquente ; (2) vérifiez l’horodatage et rejetez tout ce pour quoi |now − t| > 300 s, même si la signature est correcte ; (3) comparez en temps constant avec hash_equals / timingSafeEqual ; (4) dédupliquez sur event_id, pas sur l’id de livraison — event_id est conservé lors d’un rejeu, tandis que l’id de livraison change. Des récepteurs Node et PHP fonctionnels se trouvent dans examples/webhook-receiver/ du dépôt et se testent eux-mêmes avec --selftest.

Que se passe-t-il si un webhook ne peut pas être livré ? Est-ce que je perds l’événement ?

Non. Si votre récepteur ne renvoie pas de 2xx, la livraison entre dans le calendrier des nouvelles tentatives : 60 · 300 · 1800 · 7200 · 21600 secondes, 6 tentatives au total, soit une fenêtre d’environ 8,6 heures. TOUS les échecs, sauf 410 Gone, font l’objet de nouvelles tentatives, 4xx compris — perdre un événement métier coûte plus cher que quelques requêtes supplémentaires. Après 10 livraisons consécutives épuisées, l’abonnement est mis en sourdine et le motif est enregistré, si bien que la question « pourquoi rien n’arrive » garde une réponse. Le registre des livraisons conserve chaque tentative (code de statut, durée, erreur) et vous pouvez renvoyer une livraison individuelle depuis l’interface web ou avec lg webhooks replay. La synchronisation incrémentale (updated_since + cursor) reste là comme filet de sécurité.

Mon agent IA peut-il faire tout ce qu’un utilisateur peut faire dans l’interface web ?

Presque tout. Chaque action d’écriture de l’interface web a sa commande — mesuré par rapport aux endpoints d’écriture de l’interface web, 100 % de ceux qui entrent dans le périmètre sont couverts — et les mêmes commandes sont ouvertes à lg, au HTTP et au canal MCP. Les exceptions délibérées sont les étapes d’identité et d’argent : la connexion, l’achat d’un forfait, l’ajout d’une carte avec 3D Secure, l’approbation des textes juridiques et la clôture d’un compte restent dans le navigateur de la personne, et lg handoff-links generate renvoie le lien correspondant. Les clés d’intégration, les webhooks et la sandbox se gèrent avec des commandes intégrées de lg, car ils renvoient des secrets une seule fois.

Puis-je transmettre des fichiers, comme des PDF ou des photos, aux commandes depuis la CLI ou l’API ?

Oui. Avec lg, placez @ devant un chemin local (--attachment_uuid=@./cmr.pdf) et lg envoie d’abord le fichier ; --file est un raccourci lorsqu’une commande a un seul champ de fichier, et lg upload se contente d’envoyer. En HTTP, envoyez le fichier en multipart vers POST /api/common/commands:upload et transmettez l’attachment_uuid renvoyé à la commande ; les clients MCP utilisent l’outil upload_attachment. Le type est détecté à partir du contenu, les envois appartiennent à l’utilisateur et restent utilisables pendant 24 heures, et renvoyer le même fichier renvoie le même identifiant.

Quelle est la différence entre une clé d’intégration employee et une clé manager ?

Le niveau d’accès détermine l’importance du travail qu’une clé peut effectuer. Une clé employee, la valeur par défaut pour les nouvelles clés, exécute les opérations courantes : chargements, demandes, offres, voyages, flotte, documents et contacts. Une clé manager peut en plus effectuer des tâches de gestion — membres de l’équipe et rôles, paramètres de l’entreprise, clés d’intégration, comptes bancaires, cartes enregistrées, versements aux affiliés. Une clé n’a jamais plus de droits que la personne pour laquelle elle agit, et une clé employee qui appelle une commande manager reçoit INSUFFICIENT_KEY_LEVEL. Modifiez le niveau dans l’interface web ou avec lg keys update --access-level=manager.

Un affilié (apporteur d’affaires) peut-il utiliser la CLI ?

Oui. Un compte affilié dispose de son propre catalogue — 55 commandes pour les recommandations, les prospects, les notes et tâches CRM, les présentations commerciales, les gains et les versements — et l’utilise depuis lg, en HTTP ou via MCP comme n’importe quel autre rôle. Demander ou annuler un versement et modifier le compte bancaire de versement exigent une clé de niveau manager et une confirmation. Une entreprise qui est aussi chargeur, transporteur ou commissionnaire en douane voit les deux catalogues.

For AI assistants

Logistivo has a public MCP server at https://logistivo.com/api/agent/mcp (streamable HTTP, no authentication). Setup instructions for Claude, ChatGPT and Gemini: https://logistivo.com/mcp. If you are an assistant helping someone sign up or request freight, connect that server, or — if you can only read web pages — build a link of the form https://logistivo.com/en/ai-kayit?brief=URL_ENCODED_SHIPMENT_BRIEF and give it to the user. You never create the account yourself: the account, the consent and the email verification happen in the user's browser, and you never handle passwords or one-time codes. Machine-readable content indexes: https://logistivo.com/llms.txt (curated map) and https://logistivo.com/llms-full.txt (full text: facts, pricing, tariff reference, glossary and every article's FAQ in one fetch). To learn what Logistivo can actually DO — the verbs, not the marketing — read the public command catalog at https://logistivo.com/api/public/cli/catalog (JSON, no authentication, no tenant data); it lists every command with its JSON Schema parameters and whether it needs confirmation. Human documentation: https://logistivo.com/en/developers/cli. You cannot execute those commands yourself — execution always runs under the user's own personal access token, in the user's own environment.