Tournevia

Référence API

Authentification, écritures fiables, calculs asynchrones et contrat détaillé de chaque opération.

Choisir le bon point d’entrée

Toutes les opérations applicatives utilisent /api/v1 sur la même origine que l’interface. Le contrat OpenAPI JSON fournit les types, champs, limites et codes de réponse de la version documentée. Une installation partagée (HTTPS) ne sert pas elle-même /api/docs ni /api/openapi.json : utilisez cette copie. Les noms JSON et les valeurs d’énumération restent en anglais, quelle que soit la langue de l’interface.

Cette API utilise des comptes nominatifs et une session ; aucun système de clés développeur ou d’accès SaaS multi-entreprise n’est fourni. L’entreprise et les permissions proviennent du serveur, jamais d’un identifiant d’entreprise choisi par le client.

Session et protection des écritures

  1. Connectez-vous avec POST /api/v1/session depuis l’origine configurée, avec X-VRP-Request: 1. Cette première requête n’a pas encore de jeton CSRF de session.
  2. Le navigateur conserve le cookie de session HttpOnly. La réponse fournit un csrf_token.
  3. Pour une écriture, envoyez X-VRP-Request: 1 et X-CSRF-Token, avec le cookie et l’origine attendue.
  4. Fermez la session avec DELETE /api/v1/session.

Le mode partagé exige HTTPS et le cookie __Host-vrp_session. Le mode local authentifié utilise un cookie distinct. Une session expirée renvoie 401 ; une origine ou un jeton CSRF incorrect renvoie 403.

La préférence personnelle s’enregistre avec PUT /api/v1/preferences et un corps {"language":"fr"} ou {"language":"en"}. Elle ne modifie pas le rôle et ne permet pas de choisir un autre utilisateur.

L’utilisateur connecté change son mot de passe avec PUT /api/v1/session/password, current_password et new_password (15 à 128 caractères) ; ses autres sessions prennent fin et les mots de passe actuels erronés comptent dans les limites de connexion. Un administrateur définit le mot de passe d’un autre utilisateur avec POST /api/v1/users/{key}/password et new_password, ce qui met fin à toutes les sessions de cet utilisateur.

Une session dure au plus huit heures et se termine après 30 minutes sans requête ; les requêtes envoyées avec X-VRP-Background: 1 ne comptent pas comme activité. Les échecs de connexion sont limités à 5 par identifiant et par client, et à 50 par identifiant, sur 15 minutes (429 LOGIN_THROTTLED). Derrière le proxy HTTPS fourni, les connexions sont aussi limitées par adresse (429 SIGNIN_RATE_LIMITED).

Lire et modifier les données

Les livraisons se filtrent par service_date ; la flotte appartient à l’espace et ne dépend pas de cette date. Les listes sont classées par identifiant ; utilisez limit (1 à 250, 50 par défaut) et offset pour les parcourir. status filtre sur toute la journée ou toute la flotte — active, inactive, review, missing ou ready pour les livraisons ; active, inactive ou off_day avec une service_date pour les véhicules — et q recherche dans les identifiants (et les adresses de livraison) sans tenir compte des majuscules. GET /api/v1/orders/summary et GET /api/v1/vehicles/summary renvoient les compteurs correspondants, y compris pour une recherche.

Un véhicule enregistré a des horaires hebdomadaires dans hours : pour chaque jour, de monday à sunday, un start et un end en heure locale HH:MM, ou null pour un jour non travaillé. Jusqu’à 100 exceptions datées remplacent ces horaires à leur date : un jour de repos (hours: null) ou d’autres horaires. Le travail de nuit n’est pas pris en charge. Un calcul copie chaque véhicule tel qu’il travaille le jour planifié.

GET /api/v1/geocode?q=… (3 à 200 caractères) renvoie les points candidats pour une adresse, du meilleur au moins bon, classés autour du dépôt principal, chacun avec label, latitude, longitude, score (de 0 à 1), kind (housenumber, street, locality ou municipality), postcode et city. Ils viennent du géocodeur propre à l’installation (GEOCODER_URL) ou, si l’installation n’en a pas et qu’un administrateur l’a activé, du service public de l’IGN. Ce sont des propositions : enregistrez-en une comme emplacement, puis vérifiez l’entrée. Sans recherche d’adresses, la réponse est 404 GEOCODER_DISABLED ; si le service ne répond pas, 503 GEOCODER_UNAVAILABLE. GET /api/v1/configuration renvoie dans address_lookup l’attribution des données et dans address_lookup_source leur destination (installation ou public) lorsque la recherche est disponible, null sinon. Dans l’aperçu d’un import CSV de livraisons, address_match signale les lignes dont les coordonnées ont été proposées à partir de l’adresse, location_source (FILE, ADDRESS, CUSTOMER, SAVED ou NONE) indique l’origine de chaque point, warnings liste les points à vérifier sans bloquer l’import, et address_lookup (UNAVAILABLE ou INCOMPLETE) indique pourquoi certaines lignes n’ont pas été recherchées.

Un import refuse une ligne dont l’identifiant existe déjà avec d’autres valeurs, sauf avec update_existing=true : l’aperçu la marque alors UPDATE et nomme les champs modifiés dans changes, et la confirmation l’applique à la version actuelle de l’enregistrement. Pour une livraison existante, une ligne sans coordonnées conserve le point enregistré et sa vérification. Un fichier dont la ligne d’en-têtes attendue est séparée par des points-virgules, comme l’enregistrent les tableurs réglés sur les conventions françaises, reçoit une réponse 422 avec une seule erreur, ligne 1, champ headers, qui le dit au lieu de lister les en-têtes attendus.

POST /api/v1/orders/activation annule ("active": false) ou rétablit (true) plusieurs livraisons ; POST /api/v1/orders/delete les supprime définitivement et renvoie leur nombre dans deleted. Les deux reçoivent orders, une liste de {"id", "version"} à la version vue par l’utilisateur (412 sinon). Une livraison annulée reste dans la liste et n’est pas planifiée. La suppression est refusée tant qu’un calcul en attente ou en cours utilise l’une des livraisons (409 JOB_ACTIVE) ; les plans enregistrés gardent leur copie et demandent ensuite un nouveau calcul.

Les enregistrements possèdent un entier version. Lors d’une modification, transmettez la version attendue dans l’en-tête If-Match, par exemple "3" ; sans cet en-tête, la réponse est 428 VERSION_REQUIRED. En cas de conflit, relisez l’enregistrement et faites décider l’utilisateur ; ne remplacez pas silencieusement les changements d’un collègue.

Les quantités API sont exprimées dans les unités indiquées par les champs : kilogrammes, dm³, millimètres, secondes et centimes d’euro. L’interface convertit certains champs en mètres, m³, minutes ou euros ; ne recopiez pas une valeur affichée sans cette conversion. Les coordonnées sont latitude/longitude, sans altitude. Les équipements sont une liste de codes : skills pour un véhicule, required_skills pour une livraison, un client et un arrêt de feuille de route. L’interface nomme TAIL_LIFT (Hayon élévateur) et REFRIGERATED (Caisse frigorifique) et affiche tout autre code tel quel ; les enregistrements, les fichiers, l’API et l’export CSV conservent les codes.

Dépôts et lieux

GET /api/v1/depot et PUT /api/v1/depot lisent et modifient le dépôt principal. GET /api/v1/depots liste tous les dépôts et lieux de l’entreprise, le dépôt principal en premier. Un lieu (kind PLACE), comme le domicile d’un conducteur, sert seulement de départ ou d’arrivée à un véhicule. Un administrateur ajoute un dépôt ou un lieu avec POST /api/v1/depots (son point arrive non vérifié), le modifie avec PUT /api/v1/depots/{key}, en fait le dépôt principal avec POST /api/v1/depots/{key}/main et le supprime avec DELETE /api/v1/depots/{key}, à chaque fois avec If-Match. Le code d’un dépôt ne change pas tant qu’un véhicule ou une livraison le nomme ; le dépôt principal et un dépôt ou lieu encore nommé ne se suppriment pas.

Un véhicule indique où il commence sa journée (start, code d’un dépôt ou d’un lieu ; null pour le dépôt principal) et où il la termine (end ; null pour son point de départ). Une livraison peut indiquer le dépôt d’où elle part (ships_from, code d’un dépôt, jamais d’un lieu) : seul un véhicule qui part de ce dépôt peut la transporter, une règle que la validation indépendante vérifie aussi.

Clients

GET /api/v1/customers liste les clients enregistrés par code ; status (active, inactive, review ou missing) et q (code, nom ou adresse) filtrent toute la liste, et GET /api/v1/customers/summary renvoie les compteurs. POST /api/v1/customers crée un client et PUT /api/v1/customers/{key} le modifie avec If-Match ; les livraisons déjà enregistrées gardent leur propre copie de son entrée. Une livraison qui nomme un client (customer, son code) sans point propre prend l’entrée du client avec sa vérification. POST /api/v1/orders/{key}/customer, avec code, name et la version de la livraison dans If-Match, enregistre une livraison comme nouveau client, en conservant son entrée vérifiée, et l’y relie.

Paramètres de l’entreprise et exemples

Configuration initiale. GET /api/v1/setup indique si la configuration est nécessaire et autorisée, et nomme dans address_lookup le géocodeur propre à l’installation s’il existe. Tant que l’installation est vide, GET /api/v1/setup/geocode?q=… cherche l’adresse du dépôt avec le code privé dans l’en-tête X-Setup-Token ; public=true utilise le service de l’IGN lorsque l’installation n’a pas de géocodeur. POST /api/v1/setup crée en une transaction l’entreprise, son dépôt principal (point vérifié obligatoire) et le premier administrateur, puis ouvre sa session. Le corps contient setup_token, company_name, depot, display_name, username et password, et facultativement public_address_lookup et language (fr ou en, en par défaut), la langue de l’interface du premier administrateur. Le champ workspace_kind n’existe plus.

Recherche d’adresses. Un administrateur active ou désactive le service public de l’IGN pour l’entreprise avec PUT /api/v1/workspace/address-lookup et {"public_service": true} ou false. Chaque adresse recherchée est alors envoyée à l’IGN. Une installation dotée de son propre géocodeur utilise toujours celui-ci.

Marge de sécurité. Un administrateur la définit avec PUT /api/v1/workspace/safety-margin et {"minutes": 15}, un nombre entier de 0 à 120 ; elle vaut 15 par défaut, et GET /api/v1/configuration la renvoie dans safety_margin_minutes. Les nouveaux calculs prévoient le début de chaque livraison au moins ce temps avant la fin de son créneau, jamais avant son ouverture ; la validation contrôle toujours les vrais créneaux. Une livraison qui ne peut être atteinte que dans les dernières minutes de son créneau peut rester non affectée. Chaque plan conserve la marge avec laquelle il a été calculé dans input_data.safety_margin_seconds : 0 pour un plan calculé avant l’existence de ce réglage.

Numéros de livraison. GET /api/v1/workspace/numbering renvoie le format des numéros de livraison (format, start), le numéro que recevrait une livraison du jour (next) et un format qui prolongerait les identifiants existants (suggestion). Un administrateur le définit ou l’efface avec PUT /api/v1/workspace/numbering. Un format combine du texte, les parties de date {YYYY}, {YY}, {MM} et {DD} de la date de livraison et un seul numéro {N} ou {N:4} (sur quatre chiffres), par exemple LIV-{YYYY}-{N:4}. GET /api/v1/workspace/numbering/preview?format=…&start=… montre le numéro qu’il donnerait avant son enregistrement. GET /api/v1/orders/next-identifier?service_date=… propose le prochain numéro, et POST /api/v1/orders?assign_identifier=true attribue le numéro définitif à l’enregistrement, sous le verrou de l’entreprise : deux utilisateurs ne reçoivent jamais le même. Sans format, cette option renvoie 409 NUMBERING_OFF. Avec un format, une ligne CSV de livraison dont external_id est vide reçoit le numéro suivant ; l’aperçu la marque numbered.

Exemples. POST /api/v1/examples avec {"service_date": "YYYY-MM-DD"} (administrateur) ajoute dix livraisons fictives à Paris pour ce jour, ainsi qu’un dépôt d’exemple et deux véhicules d’exemple qui en partent s’ils n’existent pas encore. Le serveur marque ces enregistrements example: true ; un client ne peut pas définir ce champ. Un calcul prend uniquement des exemples ou uniquement les enregistrements de l’entreprise (422 EXAMPLES_MIXED), et seuls des exemples nomment le dépôt d’exemple (EXAMPLE_PLACE). Un plan d’exemples peut être marqué Prêt : ses feuilles de route portent example: true et chaque planning_notice de son CSV commence par « Données d’exemple, pas pour la route. » (« Example data, not for driving. » en anglais). POST /api/v1/examples/remove (administrateur) supprime tous les exemples, avec les plans et calculs des véhicules d’exemple, et renvoie leur nombre dans removed ; il est refusé pendant qu’un calcul attend ou s’exécute (409 JOB_ACTIVE). GET /api/v1/configuration indique dans examples si l’entreprise en a. La configuration initiale ne crée plus d’espace d’exemple séparé.

Suivre un calcul

Soumettre une optimisation crée un travail asynchrone. Conservez son identifiant et consultez son état jusqu’à SUCCEEDED ou FAILED. Un succès indique qu’un résultat est enregistré ; il ne signifie pas qu’il est approuvé. POST /api/v1/jobs/{key}/cancel annule un travail en attente ou en cours. Une entreprise peut avoir au plus cinq travaux en attente ou en cours ; un envoi supplémentaire renvoie 429 QUEUE_FULL avec Retry-After. Les véhicules sélectionnés qui ne travaillent pas le jour planifié (jour de repos, ou pas d’horaires ce jour de la semaine) sont refusés avant la création du travail (422 VEHICLE_OFF_DAY, avec leurs identifiants dans identifiers). Sont aussi refusés : une sélection qui mélange exemples et enregistrements de l’entreprise (422 EXAMPLES_MIXED) ; un dépôt ou lieu de départ ou d’arrivée dont l’entrée n’est pas vérifiée (422 PLACE_NOT_REVIEWED, avec leurs codes) ; une sélection dont les lieux, dépôts compris, sont plus éloignés que MATRIX_MAX_KM (422 AREA_TOO_WIDE, avec les deux lieux les plus éloignés — depot, code d’un autre dépôt ou lieu, ou identifiants de livraison — dans identifiers).

Les opérations qui demandent une Idempotency-Key doivent recevoir la même clé et le même corps lors d’une nouvelle tentative du même envoi. Pour un nouvel envoi, utilisez une nouvelle clé. Ne resoumettez pas automatiquement une optimisation chaque fois qu’un rafraîchissement échoue.

Après une modification de l’ordre des arrêts, attendez le recalcul et la validation indépendante. L’approbation et l’export vérifient à nouveau la version et l’admissibilité du résultat.

Traiter les réponses d’erreur

RéponseAction du client
400Vérifier le format de la requête et les guillemets du numéro de version.
428Fournir l’en-tête If-Match ou Idempotency-Key exigé.
401Demander une nouvelle connexion.
403Vérifier origine, jeton CSRF et droits.
404Vérifier l’identifiant dans l’entreprise courante.
409 ou 412Relire l’état et résoudre le conflit avant un nouvel envoi.
413Réduire la requête : imports CSV et corps de requête sont limités à 1 Mio (UPLOAD_TOO_LARGE, ou REQUEST_TOO_LARGE du proxy).
415Envoyer un import CSV en text/csv (CSV_REQUIRED).
422Afficher les champs invalides et conserver la saisie.
422 AREA_TOO_WIDESélectionner des livraisons plus proches ; identifiers nomme les deux lieux les plus éloignés.
429 LOGIN_THROTTLED ou SIGNIN_RATE_LIMITEDAttendre avant de se reconnecter.
429 QUEUE_FULLAnnuler un calcul ou attendre qu’un calcul se termine.
503Vérifier le service routier (ACCESS_CHECK_FAILED) ou le géocodeur (GEOCODER_UNAVAILABLE), ou attendre que l’application réponde (SERVICE_UNAVAILABLE du proxy).
autre 5xx ou réponse illisibleConserver les données et vérifier si l’envoi a été enregistré avant de le répéter.

Les diagnostics techniques ne doivent pas contenir de mot de passe, cookie ou code privé dans vos journaux.

Les erreurs métier contiennent code et detail, ainsi que identifiers lorsque le message concerne des enregistrements nommés. Les erreurs de champ utilisent un tableau detail avec loc, msg, type, et un modèle anglais fixe template avec ses values, pour qu’un client puisse traduire le message. Certains refus du middleware ne contiennent que detail. Traitez le statut et le code, pas le texte du message. Messages et solutions liste les codes expliqués par l’interface.

Exemple : enregistrer la langue du compte connecté

Ce code s’exécute dans le navigateur, sur la même origine que l’application, après connexion. Il lit la session courante sans inscrire de jeton en dur. Une intégration doit gérer l’expiration de session et exclure les identifiants des journaux.

const sessionResponse = await fetch('/api/v1/session');
if (!sessionResponse.ok) throw new Error('Reconnectez-vous');
const session = await sessionResponse.json();
const response = await fetch('/api/v1/preferences', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'X-VRP-Request': '1',
    'X-CSRF-Token': session.csrf_token,
  },
  body: JSON.stringify({language: 'fr'}),
});
if (!response.ok) throw new Error('Langue non enregistrée');
const identity = await response.json(); // identity.language === "fr"

Enchaîner les requêtes d’un calcul

  1. Lisez GET /api/v1/orders?service_date=YYYY-MM-DD, GET /api/v1/vehicles et GET /api/v1/depot.
  2. Soumettez POST /api/v1/optimization-jobs avec le modèle exact ci-dessous et une clé d’idempotence.
  3. Consultez GET /api/v1/jobs/{key}. Conservez cette même tâche après une coupure temporaire de connexion ; annulez-la avec POST /api/v1/jobs/{key}/cancel si elle n’est plus utile.
  4. En cas de succès, ouvrez le plan retourné avec GET /api/v1/plans/{key}.
  5. Vérifiez validation et livraisons non affectées avant POST /api/v1/plans/{key}/finalize. L’acceptation d’un résultat partiel doit être explicite. Si un autre plan Prêt du jour utilise certains des mêmes véhicules ou livraisons, la requête renvoie 409 READY_CONFLICT, sauf si elle définit replace_conflicting_ready.
  6. Téléchargez GET /api/v1/plans/{key}/export.csv seulement pour le résultat Prêt courant et admissible, avec sa version entre guillemets dans If-Match. layout=standard (par défaut) produit un fichier UTF-8 séparé par des virgules ; layout=spreadsheet ajoute une marque d’ordre des octets, des points-virgules et une virgule décimale dans les coordonnées. Les colonnes sont décrites dans Formats CSV ; start_place et end_place donnent les codes des lieux de départ et d’arrivée de chaque véhicule.
  7. Lisez les feuilles de route des conducteurs avec GET /api/v1/plans/{key}/sheets, aux mêmes conditions que l’export et avec le même If-Match. La réponse contient une feuille par véhicule ayant des livraisons (?vehicle= pour un seul véhicule) : lieu et heure de départ et d’arrivée, distance, temps de conduite et chargement, puis chaque livraison dans l’ordre de passage avec son numéro, son client, son adresse, son entrée vérifiée, sa fenêtre, ses heures d’arrivée, de début de service et de départ, sa charge, ses équipements et ses instructions. example: true signale un plan d’exemples.

GET /api/v1/system-status indique l’état du worker, du service routier et de la file d’attente pour un utilisateur connecté. GET /api/v1/metrics sert uniquement à la supervision : il exige Authorization: Bearer avec le MONITOR_TOKEN de l’installation et n’existe que si ce réglage est défini.

Trouver une opération

Choisissez un domaine, puis une opération. Seuls ses paramètres et modèles s’affichent. Ouvrez une réponse pour lire ses champs ; le contrat complet reste téléchargeable.

Connecter un utilisateur, mémoriser sa langue et gérer les comptes autorisés. La création et la gestion des autres comptes sont réservées à un administrateur.

GET/api/v1/session

Réponses

200Successful Response
SessionRecord · application/json
ChampType et règle
identityObligatoireIdentity
Voir les champs
ChampType et règle
organization_idObligatoirestring
user_idObligatoirestring | null
usernameObligatoirestring
display_nameObligatoirestring
roleObligatoireadmin | planner
languageFacultatifen | fr
csrf_tokenObligatoirestring | null
expires_atObligatoirestring | null
401Unauthorized
SourceProblem · application/json
ChampType et règle
codeObligatoirestring
detailObligatoirestring
identifiersFacultatifstring[] | null
403Forbidden
SourceProblem · application/json
ChampType et règle
codeObligatoirestring
detailObligatoirestring
identifiersFacultatifstring[] | null
404Not Found
SourceProblem · application/json
ChampType et règle
codeObligatoirestring
detailObligatoirestring
identifiersFacultatifstring[] | null
409Conflict
SourceProblem · application/json
ChampType et règle
codeObligatoirestring
detailObligatoirestring
identifiersFacultatifstring[] | null
429Too Many Requests
SourceProblem · application/json
ChampType et règle
codeObligatoirestring
detailObligatoirestring
identifiersFacultatifstring[] | null

Cette vue lit le contrat de la version documentée. Les clés JSON restent identiques dans les deux langues. Télécharger OpenAPI

Sur cette page