Skip to content

API Reference

Cette page ne duplique pas la référence complète des endpoints — elle vous oriente vers la documentation générée automatiquement depuis le schéma OpenAPI réel de l'API, et donne le contexte nécessaire pour la lire efficacement.

Où trouver la référence complète

  • Swagger UI : /docs sur l'environnement sandbox — exploration interactive, "Try it out" inclus.
  • Schéma brut : /openapi.json — utile pour générer un client, ou l'importer dans votre propre outil (Postman, Insomnia...).

/docs, /redoc et /openapi.json ne sont exposés qu'en dehors de la production, pour des raisons de sécurité (surface d'attaque inutile une fois l'intégration terminée). Utilisez le sandbox pour explorer le schéma.

Groupes de ressources

Tout endpoint métier vit sous le préfixe /v1. Les endpoints d'infrastructure (/healthz, /readyz, /version) n'en font délibérément pas partie.

Groupe Préfixe Contenu
Accounts /v1/accounts Inscription, activation, connexion, clés API, clients OAuth, sous-comptes, recherche de compte par email
OAuth /v1/oauth Émission de token via Client Credentials
Freight Documents /v1/freightdocuments CRUD du document, transitions de statut, rôles, délégation, access codes
Access Codes /v1/access-codes Échange d'un carrierAccessCode contre un token (endpoint public, sans authentification préalable)
Directory /v1/accounts/me Partners/Locations, Vehicles, Drivers, Packaging Methods — voir Annuaire

Particularité à noter : l'Annuaire partage son préfixe avec Accounts (/v1/accounts/me/...). Dans Swagger, vous le trouverez donc listé sous ce même chemin, mais avec son propre tag directory — ce n'est pas une erreur de rangement.

Accounts — inscription (POST /v1/accounts/register)

Champs obligatoires : email, password, company_name, terms_accepted (doit valoir true — sinon account.terms_not_accepted, voir Format d'erreur). Le contenu réel des conditions d'utilisation n'est pas encore publié ; terms_accepted n'enregistre aujourd'hui que l'acceptation d'un identifiant de version, pas un texte définitif.

Champs optionnels, tous deux purement informatifs — aucun n'influence account_kind, les permissions, ou tout autre comportement technique :

Champ Type Détail
vat_number string Vérification de format uniquement (préfixe de 2-3 lettres suivi de 2 à 20 caractères alphanumériques) — jamais un format exact par pays, jamais de vérification contre un registre externe (VIES, Zefix). Un format structurellement invalide renvoie account.invalid_vat_number.
company_types liste de strings Valeurs possibles : SHIPPER, CARRIER, FREIGHT_FORWARDER, OTHER — plusieurs valeurs simultanées autorisées. Liste fixe ; toute valeur hors de cet ensemble est rejetée par la validation standard du schéma.

account_kind n'est plus un champ de ce payload — chaque compte est créé SITE inconditionnellement, indépendamment de ce que fait l'appelant (voir Codes d'erreur pour le reste de la taxonomie account.*).

Freight Documents — endpoints principaux

Méthode Chemin Description
POST /v1/freightdocuments Création — l'appelant devient automatiquement SUBMITTER
GET /v1/freightdocuments Liste (voir pagination/filtrage ci-dessous)
GET /v1/freightdocuments/{id} Détail — nécessite la permission VIEW
GET /v1/freightdocuments/ext/{externalIdentifier} Recherche par votre propre identifiant externe
PUT /v1/freightdocuments/{id} Remplacement complet — version obligatoire (verrouillage optimiste)
PATCH /v1/freightdocuments/{id} Mise à jour partielle (JSON Merge Patch, RFC 7396) — version obligatoire
POST /v1/freightdocuments/{id}/issue Transition DRAFTISSUED
POST /v1/freightdocuments/{id}/roles Ajout d'un rôle/partie
POST /v1/freightdocuments/{id}/roles/{roleId}/delegate Délégation d'un rôle vers un tiers par email
GET /v1/freightdocuments/{id}/roles/{roleId}/delegations Chaîne de délégation d'un rôle
POST /v1/freightdocuments/{id}/roles/{roleId}/delegations/{delegationId}/revoke Révocation d'une délégation et de sa sous-chaîne
POST /v1/freightdocuments/{id}/roles/{roleId}/access-codes Émission d'un carrierAccessCode
GET /v1/freightdocuments/{id}/roles/{roleId}/access-codes Liste des access codes déjà émis sur ce rôle (actif/révoqué/expiré)
POST /v1/freightdocuments/{id}/roles/{roleId}/access-codes/{accessCodeId}/revoke Révocation d'un access code
POST /v1/freightdocuments/{id}/attachments Création d'une pièce jointe — retourne une URL d'upload pré-signée
GET /v1/freightdocuments/{id}/attachments Liste des pièces jointes confirmées, filtrée selon le scellement
GET /v1/freightdocuments/{id}/attachments/{attachmentId}/download URL de téléchargement pré-signée, courte durée
DELETE /v1/freightdocuments/{id}/attachments/{attachmentId} Suppression — uploader ou SUBMITTER, document encore DRAFT
GET /v1/freightdocuments/{id}/pdf Génération du PDF du document (voir Pièces jointes & PDF)
POST /v1/freightdocuments/{id}/pdf/share-link Émission d'un lien de partage PDF sans authentification
POST /v1/freightdocuments/{id}/subscriptions Création d'une souscription webhook — nécessite VIEW, compte principal uniquement (voir Webhooks)
GET /v1/freightdocuments/{id}/subscriptions Liste des souscriptions de votre propre compte sur ce document
DELETE /v1/freightdocuments/{id}/subscriptions/{subscriptionId} Révocation — uniquement par le compte créateur

Chaque rôle dans roles (hors SUBMITTER, dérivé automatiquement de l'appelant authentifié) doit porter au moins l'un des deux : account_id (la partie a déjà un compte) ou email (elle n'en a pas encore — le rôle s'y rattachera automatiquement à son activation, et pas avant même si un compte portant cet email existe déjà, voir Rôles et permissions). Les deux sont individuellement optionnels dans le schéma, mais en fournir aucun des deux échoue avec freight_document.invalid_role_assignment (voir Format d'erreur) — sauf sur PLACEOFTAKINGOVER et PLACEOFDELIVERY, qui acceptent de n'en porter aucun. Un rôle de lieu créé ainsi ne pourra jamais être rattaché à un compte ni signer, définitivement : voir Rôles et permissions.

!!! warning "Changement cassant — identité fiscale obligatoire"

Depuis le 2026-09-10, chaque rôle représentant une **entité juridique** (`CONSIGNOR`, `CONSIGNEE`, `CARRIER`, `SUBSEQUENTCARRIER`, `SUBCONTRACTOR`) doit porter une identité fiscale, sans quoi la création est rejetée avec `freight_document.missing_tax_identification` :

- **`party_snapshot.vat_number` est toujours requis.** Format contrôlé seulement (préfixe pays de 2-3 lettres puis 2 à 20 caractères alphanumériques), jamais vérifié contre un registre réel ; mal formé, il est rejeté avec `freight_document.invalid_vat_number`.
- **`party_snapshot.nif` est requis uniquement si `party_snapshot.address.country` vaut `ES`.** Il est validé **strictement**, caractère de contrôle compris (DNI, NIE ou CIF) ; invalide, il est rejeté avec `freight_document.invalid_nif`. Une partie non espagnole n'a pas de NIF et ne s'en voit jamais demander.
- **`party_snapshot.address` est également devenue requise sur ces cinq rôles**, rejetée avec `freight_document.address_required`. C'était un champ optionnel depuis l'origine de l'API : c'est donc une **seconde rupture de compatibilité**, sur le même endpoint et dans la même version. Elle est exigée parce que le pays de l'adresse est ce qui décide si un `nif` est requis — sans adresse, l'obligation entière se contournait. Vérifiée avant le `vat_number`, pour ne pas vous renvoyer deux fois.
- Les deux rôles de lieu (`PLACEOFTAKINGOVER`, `PLACEOFDELIVERY`) ne sont **jamais** concernés, ni par l'identité fiscale ni par cette obligation d'adresse : un lieu physique n'a pas d'identité fiscale.
- Un rôle créé depuis `partner_id` tient les deux valeurs de la fiche : **cette fiche doit donc les porter**, sinon la création est rejetée et la description de l'erreur nomme la fiche à compléter. Les fiches créées avant cette date ne les ont pas — complétez-les avec `PATCH /v1/accounts/me/partners/{id}`.

C'est une **règle métier Fincargo pour le marché espagnol**, pas une exigence de la Convention CMR (son article 6 ne demande que nom et adresse). Pour trouver l'`account_id` d'une partie qui a déjà un compte sans lui demander son UUID, `GET /v1/accounts/lookup?email=...` le résout à partir de son email (404 `account.not_found_by_email` si aucun compte ne correspond).

Voir le champ PUT/PATCH : le PUT remplace le document mais ne touche jamais aux rôles (roles) ni aux champs système (status, hostingType...) — utilisez les endpoints dédiés pour ceux-là. Le PATCH ne porte que sur un sous-ensemble de champs métier (lignes de marchandise, références, incoterms, dates planifiées, champs spécifiques pays) et applique une vérification de permission dédiée par champ.

goods_lines — cases CMR 6 à 12

Chaque document a besoin d'au moins une ligne de marchandise (freight_document.goods_line_required sinon), que ce soit à la création, sur un PUT (remplacement complet), ou sur un PATCH qui touche ce champ — jamais un document sans aucune marchandise déclarée. Chaque ligne porte 7 champs, correspondant aux cases 6 à 12 du CMR papier : marks_and_numbers (case 6, optionnel), package_count (case 7, requis, > 0), packaging_method ou packaging_method_id (case 8, requis — exactement l'un des deux), nature_of_goods (case 9, requis), statistical_number (case 10, optionnel, aucune validation de format — varie par pays/régime douanier), gross_weight_kg (case 11, requis, > 0), volume_m3 (case 12, optionnel). Un 8e champ optionnel, sscc, porte le SSCC GS1 (Serial Shipping Container Code) de la ligne — exactement 18 chiffres, vérifié uniquement sur la forme (pas de clé de contrôle), rejeté avec freight_document.invalid_sscc sinon. Généré par expédition/palette, pas une propriété d'entité d'annuaire : pas d'équivalent ..._id, pas de gel — une simple valeur saisie ou fournie par un TMS.

packaging_method_id référence une entrée de votre annuaire Packaging Methods (voir Annuaire) — son label est copié au moment de la création/PATCH, jamais une référence vivante ensuite, même principe que vehicle_id/partner_id. Fournir packaging_method et packaging_method_id en même temps, ou aucun des deux, échoue avec freight_document.invalid_role_assignment.

Rôles requis pour l'émission (issue)

POST .../issue (transition DRAFTISSUED) exige que le document porte au moins un rôle CARRIER, un rôle PLACEOFTAKINGOVER et un rôle PLACEOFDELIVERY — sinon freight_document.no_carrier, freight_document.no_collection_signer ou freight_document.no_delivery_signer (voir Format d'erreur).

Point d'attention pour les intégrations TMS : la plupart des modèles de données TMS ne connaissent que CONSIGNOR/CONSIGNEE/CARRIER — pas la distinction, propre à la Convention CMR, entre la partie contractuelle et le lieu physique de collecte/livraison. Or CONSIGNOR et CONSIGNEE ne portent jamais la permission de signer (voir Rôles et permissions) : sans un rôle PLACEOFTAKINGOVER/PLACEOFDELIVERY dédié, la signature COLLECTION/DELIVERY correspondante est structurellement impossible, même une fois le document émis. Si votre lieu de collecte/livraison réel est la même adresse que votre CONSIGNOR/CONSIGNEE, envoyez simplement un rôle PLACEOFTAKINGOVER/PLACEOFDELIVERY supplémentaire avec la même identité — ce n'est pas une donnée redondante, c'est le seul rôle qui portera effectivement la permission de signer.

Exemple de payload de création avec deux lignes, l'une en saisie manuelle et l'autre via l'annuaire :

{
  "type": "WAYBILL_ESP",
  "roles": [
    {
      "role_type": "CARRIER",
      "email": "ops@lotrans.example",
      "party_snapshot": {
        "name": "Lotrans",
        "address": { "street": "Calle Mayor 1", "postal_code": "28013", "city": "Madrid", "country": "ES" }
      }
    }
  ],
  "goods_lines": [
    {
      "marks_and_numbers": "PAL-001",
      "package_count": 20,
      "packaging_method": "Palettes",
      "nature_of_goods": "Grain de café",
      "gross_weight_kg": 2000,
      "volume_m3": 3.5
    },
    {
      "package_count": 5,
      "packaging_method_id": "3fa85f64-5717-4562-b3fc-2c963f66afa7",
      "nature_of_goods": "Pièces détachées automobile",
      "statistical_number": "8708.99",
      "gross_weight_kg": 340
    }
  ]
}

ownPermissions

Chaque réponse GET/PUT/PATCH sur un Freight Document inclut un champ ownPermissions — la liste des permissions que vous, l'appelant authentifié, possédez sur ce document précis. Ce champ est recalculé à chaque requête, jamais mis en cache : il reflète toujours l'état courant de vos rôles/délégations sur ce document. Si vous accédez via un carrierAccessCode plutôt qu'un compte complet, ownPermissions est automatiquement plafonné à la consultation et aux commentaires, quel que soit le rôle sous-jacent.

carrierAccessCode

Un mécanisme d'accès minimal pour un acteur sans compte du tout — un cran en dessous d'une délégation complète. Le code est réutilisable jusqu'à expiration ou révocation (ce n'est pas un token à usage unique), et n'est visible en clair qu'au moment de sa création — il est haché en base et ne peut plus être récupéré ensuite.

  • POST /v1/freightdocuments/{id}/roles/{roleId}/access-codes — émission (nécessite la permission DELEGATE sur ce rôle). Réservé aux rôles CARRIER/SUBSEQUENTCARRIER/SUBCONTRACTORCONSIGNOR/CONSIGNEE/PLACEOFTAKINGOVER/PLACEOFDELIVERY/SUBMITTER ne portent jamais DELEGATE, même pour leur propre rôle : un code ne peut donc jamais être émis sur l'un de ces rôles, quel que soit l'appelant. Réponse : id, code (visible une seule fois), prefix, url (lien de rédemption complet, prêt à partager), expires_at, created_at.
  • GET /v1/freightdocuments/{id}/roles/{roleId}/access-codes — liste des codes déjà émis sur ce rôle, avec leur statut (active/revoked/expired), même permission que l'émission.
  • POST /v1/access-codes/redeem — échange du code contre un token de courte durée, endpoint public, plafonné aux permissions consultation + commentaire

Envoi par SMS (optionnel) : le corps de la requête d'émission accepte un champ optionnel send_to_phone (numéro au format E.164, ex. +33612345678). S'il est fourni, l'URL de rédemption est envoyée par SMS. L'échec de l'envoi ne bloque jamais la création du code — la réponse porte alors sms_sent: false et sms_failure_reason, l'URL reste utilisable et retournée normalement. Un send_to_phone mal formé (pas de + initial, etc.) est rejeté avec freight_document.access_code_invalid_phone_number (voir Format d'erreur) avant toute tentative d'envoi.

Pagination et filtrage

L'API n'a pas une convention unique de pagination — deux schémas coexistent délibérément :

Annuaire (partners, vehicles, drivers, packaging-methods) : pagination simple.

  • limit (1-200, défaut 50), offset (défaut 0)
  • Réponse : { ..., limit, offset, total }

Freight Documents (GET /v1/freightdocuments) : schéma plus riche.

  • filters — paramètre répétable, grammaire champ.opérateur:valeur, combinés en ET. Exemple : ?filters=status.eq:ISSUED&filters=referenceValue.like:PO-123
  • sort — un champ, -champ pour un tri descendant (ex : sort=-created_at). Seuls created_at, updated_at, status sont triables.
  • limit — toujours ramené dans l'intervalle [25, 50], jamais rejeté ; la réponse indique la valeur effective.
  • first — décalage (équivalent d'offset, mais nommé différemment de l'Annuaire)
  • Par défaut, les documents CANCELLED sont exclus des résultats, sauf filtrage explicite sur status.

Un filtre non reconnu (champ ou opérateur) renvoie une erreur explicite plutôt que d'être silencieusement ignoré.

updated_since — rattrapage après un webhook manqué

GET /v1/freightdocuments?updated_since=<ISO 8601> — paramètre distinct de filters (il ne suit pas la grammaire champ.opérateur:valeur). Renvoie uniquement les documents dont updated_at est postérieur ou égal à la date donnée. Volontairement simple : cet endpoint ne renvoie que la liste des documents modifiés, pas ce qui a changé sur chacun — récupérez ensuite le détail complet avec un GET /v1/freightdocuments/{id} normal sur chaque id retourné. Ce n'est pas un journal d'événements/changelog.

Pensé pour un TMS qui interroge périodiquement l'API en complément (ou en filet de sécurité) des webhooks — si une livraison de webhook a été manquée, un poll sur updated_since=<date du dernier poll réussi> retrouve les documents concernés.

updated_at est mis à jour par : tout changement de statut (issue, ou une signature COLLECTION/DELIVERY), tout PUT/PATCH réussi (même sans changement effectif de valeur), toute signature HANDOVER/ACCEPTANCE/COLLECTION_ACCEPTANCE, tout nouveau commentaire, toute pièce jointe une fois confirmée (AVAILABLE — pas au moment de la simple déclaration d'upload), et tout nouvel événement (POST .../events). Une pièce jointe jamais confirmée (upload abandonné) ne déclenche donc pas de mise à jour.

Format d'erreur

Toute erreur suit la même forme, quel que soit l'endpoint :

{
  "errors": [
    { "code": "auth.invalid_credentials", "description": "Invalid credentials." }
  ]
}

Voir la taxonomie complète des codes d'erreur.