Une mission n’est pas figée après sa création. Tant qu’elle est en amont et avant le jour de son enlèvement, vous pouvez décaler ses dates, ajuster ses options ou refacturer une jambe — le tout par programmation, avec recalcul automatique du prix et notification du client quand c’est pertinent. Une fois la mission partie (ou dès J+0), ces écritures sont verrouillées (voir l’encart « Fenêtre de modification » ci-dessous). L’annulation, elle, reste possible jusqu’au départ du véhicule, y compris le jour même, avec les frais prévus par le barème (voir Annuler la mission). Ce guide couvre les quatre familles de modifications :

Modifier les dates

Décaler l’enlèvement ou la livraison, sur l’aller comme sur le retour, jambe par jambe.

Options

Ajouter ou retirer une option ; le prix est recalculé automatiquement.

Refacturation

Changer le prix HT facturé au client pour une jambe, avec garde-fous et historique.

Annulation

Annuler une mission avant le départ du véhicule, avec le barème de frais de l’espace client.
Toutes les routes ci-dessous sont scopées au groupe de votre clé. Une mission qui n’appartient pas à votre groupe renvoie 404 Not Found (jamais 403). Les mutations exigent le scope transports:write.
Pour les exemples, on suppose ces deux variables d’environnement :
Toutes les mutations acceptent l’en-tête Idempotency-Key. Générez-en un par opération métier (un UUID) pour pouvoir rejouer sans risque de double effet sur un réseau peu fiable.
Fenêtre de modification. Une mission n’est modifiable que tant qu’elle est en amont ET avant le jour de son enlèvement. Dès que la mission est partie (enlèvement effectué) ou au-delà — statuts publics in_transit, in_transit_return, awaiting_documents, under_review, completed, incident — ou dès le jour même de l’enlèvement prévu (J+0) ou après (fuseau Europe/Paris), ou si elle est annulée, toute modification est refusée avec un 409 Conflict. Ce verrou s’applique aux trois familles d’écriture : PATCH /transports/{id}/dates, les options (POST / DELETE .../options/...) et les documents (POST .../documents).Conséquence : planifiez vos décalages de dates, ajouts d’options et dépôts de documents avant la veille de l’enlèvement. Branchez votre logique sur le status (409), pas sur le texte du detail.

1. Modifier les dates (par jambe)

Une mission a jusqu’à quatre jambes : l’enlèvement et la livraison de l’aller, et — pour un aller-retour — l’enlèvement et la livraison du retour. Vous modifiez chacune indépendamment via PATCH /transports/{id}/dates.

Clés de jambe

object
requis
Carte des jambes à modifier (au moins une). Les clés possibles sont :Chaque valeur est un objet { date, start_time, end_time }.
string
Date au format ISO YYYY-MM-DD (ex. 2026-07-02).
string
Début de la fenêtre horaire (ex. 09:00).
string
Fin de la fenêtre horaire (ex. 12:00).
boolean
défaut:"true"
Si false, le client n’est pas notifié du changement. Par défaut, seules les jambes réellement modifiées déclenchent une notification.
Vous pouvez envoyer les quatre jambes en une requête : seules celles dont la date/horaire change effectivement déclencheront une notification. Inutile de pré-filtrer côté client.
Cohérence des dates (422). PATCH /dates rejette les dates incohérentes avec un 422 Unprocessable Entity : une livraison avant son enlèvement, un retour avant l’aller, ou une date dans le passé. Une clé enlevement-retour ou livraison-retour sur une mission sans trajet retour est refusée en 422 avec code: "no_return_leg" : rien n’est modifié.
Comme toute écriture, PATCH /dates renvoie un 409 Conflict si la fenêtre de modification est fermée (mission partie, J+0 ou au-delà, ou annulée). Voir l’encart « Fenêtre de modification » en tête de page.

Exemple — décaler l’enlèvement aller

Réponse 200 :
string
Identifiant de la mission.
string[]
Liste des jambes effectivement modifiées (et donc, sauf notify: false, notifiées au client).
Cette opération émet l’événement webhook transport.dates_updated avec un payload { id, updated_legs }.

Majoration « dernier moment » (express)

Une mission dont l’enlèvement est imminent est majorée automatiquement sur le prix client. Aucun paramètre à passer : la majoration est calculée et appliquée par le serveur. La majoration s’applique dès que l’enlèvement tombe dans la zone « dernier moment » :
Le taux par défaut est de +25 %, configurable par groupe. La majoration porte uniquement sur le prix client : elle ne touche JAMAIS la rémunération du convoyeur.
Elle est appliquée dans deux situations :
  • à la création de la mission, si l’enlèvement est déjà imminent (le price renvoyé par POST /transports inclut alors la majoration) ;
  • au changement de date via PATCH /dates :
    • rapprocher l’enlèvement dans la zone « dernier moment » ajoute la majoration ;
    • éloigner un enlèvement qui y était l’annule (le prix client redescend).
Le nouveau prix et le détail du mouvement sont consultables via GET /transports/{id}/price-history. Le motif y vaut « Majoration express … » lors de l’ajout, et « Annulation majoration express » lors du retrait.
Rapprocher l’enlèvement au jour même (J+0) referme la fenêtre de modification : c’est la dernière modification de date possible. Les écritures suivantes (dates, options, documents) renverront 409.

2. Ajouter ou retirer une option

Les options (par exemple un nettoyage, un plein de carburant, une prise de rendez-vous) sont rattachées à la mission par une clé d’option (option_key). Ajouter ou retirer une option recalcule automatiquement le prix de la mission.

Ajouter une option

POST /transports/{id}/options
string
requis
Clé de l’option à ajouter (key renvoyé par GET /options). Le prix est celui de la grille de votre groupe — vous ne le transmettez pas. Une clé inconnue renvoie 422 avec code: "unknown_option".
string
défaut:"aller"
Jambe visée : aller ou retour. Toute autre valeur renvoie 422 (errors: [{ "field": "leg", "error": "invalid" }]) ; retour sur une mission sans trajet retour renvoie 422 avec code: "no_return_leg". Mêmes règles pour le paramètre ?leg= du retrait d’option.
object
Obligations de l’option, décrites par requirements dans GET /options : renter (identifiant du loueur partenaire), link ou pin selon le loueur (PV dématérialisé), comment (consigne libre), accessories (liste { id, name, source } pour la vérification des accessoires), pending_documents (documents que vous déposerez juste après, ex. PV_PAPIER). Une obligation manquante renvoie 422 avec code: "requirements_not_met" et le champ concerné dans errors[].field.
Le catalogue complet, avec vos prix et les obligations de chaque option, est servi par GET /options. La table ci-dessous n’est qu’un aide-mémoire.

Clés d’option disponibles

Utilisez l’une de ces clés (option_key). Le libellé est donné à titre indicatif ; le prix indiqué est le prix client de référence de l’option (le montant réellement facturé dépend de la grille négociée de votre groupe). S’y ajoutent PV_PAPIER, PV_DEMATERIALISE et CHECK_ACCESSORY, qui portent des obligations (voir le guide Options).
Réponse 200 :
string
Clé de l’option ajoutée.
number | null
Nouveau prix de la mission après recalcul.
number | null
Variation de prix induite par l’ajout de l’option.

Retirer une option

DELETE /transports/{id}/options/{option_key} La clé d’option fait partie du chemin et doit être URL-encodée.
Réponse 200 :
number | null
Nouveau prix de la mission après retrait de l’option.
number | null
Variation de prix (négative ici, l’option étant retirée).
Ajouter comme retirer une option émet l’événement transport.price_adjusted.
L’ajout (POST .../options) renvoie un 409 Conflict si la fenêtre de modification est fermée (mission partie, J+0 ou au-delà, annulée) ou si la mission est déjà payée / facturée. Le DELETE est idempotent : retirer une option déjà absente n’est pas une erreur, et un même Idempotency-Key rejoue la même réponse.

3. Refacturer une jambe

La refacturation modifie le prix HT facturé au client pour une jambe donnée. Contrairement aux options, elle force un nouveau prix au lieu d’appliquer la grille — d’où l’exigence d’un motif et la traçabilité intégrale. POST /transports/{id}/price-adjustments
string
défaut:"aller"
Jambe concernée : aller ou retour. Toute autre valeur renvoie 422 (errors: [{ "field": "leg", "error": "invalid" }]). retour sur une mission sans trajet retour renvoie 422 avec code: "no_return_leg" (aucun prix n’est modifié).
number
requis
Nouveau prix HT facturé au client (nombre ≥ 0). Obligatoire.
string
requis
Motif de la refacturation. Obligatoire — il est conservé dans l’historique.

Exemple

Réponse 201 :
string
Identifiant de la mission.
string
Jambe refacturée (aller ou retour).
number | null
Ancien prix HT facturé au client, avant ajustement.
number | null
Nouveau prix HT facturé au client, après ajustement.
string
Devise des montants — toujours EUR.

Garde-fou : jambe déjà payée ou facturée

La refacturation est refusée avec un 409 Conflict et code: "leg_invoiced" si la jambe ciblée est déjà payée ou facturée. C’est une protection comptable : un prix figé dans une facture émise ne peut pas être modifié a posteriori.Aller-retour facturé en une seule fois : la facture couvre les deux jambes. Dès qu’elle est émise, le retour est considéré comme facturé lui aussi, et leg: "retour" est refusé de la même façon. Seul un aller-retour dont le retour est facturé séparément permet de refacturer le retour après facturation de l’aller.
Ce garde-fou est appliqué à deux niveaux : un contrôle rapide en amont, puis un second contrôle au moment de l’écriture. Le detail peut donc varier (et être en français) selon le niveau qui a bloqué : branchez votre logique sur le status (409) et le code (leg_invoiced), jamais sur le texte de detail. Une mission annulée renvoie aussi 409. Si l’état de facturation ne peut pas être vérifié sur le moment, la refacturation (comme l’ajout d’option) est refusée sans rien modifier : 502 avec code: "billing_unavailable", ou 500 avec une reference si la vérification échoue dès l’entrée de l’API. Dans les deux cas, réessayez dans un instant.

Historique des refacturations

Chaque refacturation est tracée. Consultez l’historique complet d’une mission via GET /transports/{id}/price-history.
Réponse 200 :
object[]
Liste des changements de prix, chacun avec leg, old_price_ht, new_price_ht, motif et created_at. Seul le prix HT facturé au client est exposé.
Une refacturation émet l’événement transport.price_adjusted avec un payload { id, leg, old_price_ht, new_price_ht, motif }.

4. Annuler la mission

POST /transports/{id}/cancel L’annulation est possible tant que le véhicule n’est pas parti (statuts publics scheduled et assigned). Exception : une mission assigned dont l’attribution au convoyeur est encore en cours de confirmation est refusée (409 not_cancellable) ; contactez le support si l’annulation est urgente. La mission n’est pas supprimée : elle passe au statut cancelled (cancelled: true), reste lisible via GET /transports/{id} et apparaît dans GET /transports?status=cancelled. Sur un aller-retour, le retour est annulé avec l’aller s’il n’est pas déjà terminé (return_included).
string
Motif d’annulation. Optionnel — le corps de la requête l’est aussi.

Frais d’annulation

Le barème est le même que dans l’espace client. Il est calculé sur la date d’enlèvement de l’aller, à l’heure de Paris, en jours ouvrés (lundi à vendredi) : Le montant facturé (penalty.client_charge_ht) vaut le prix HT de la mission — retour compris quand return_included est true — multiplié par penalty.ratio. Après l’annulation, ces frais se relisent sur la mission : price.amount_ht de GET /transports/{id} (et return_trip.price.amount_ht si le retour est inclus) vaut le montant facturé pour chaque jambe, et GET /transports/{id}/price-history trace le passage de l’ancien prix au prix d’annulation.
Exemple, convoyeur attribué, enlèvement prévu le mardi à 9 h. Annulé le vendredi précédent à 15 h : sans frais. Le vendredi à 19 h ou le lundi à 15 h : 50 %. Le lundi à 19 h, ou le mardi : 100 %.
Réponse 200 :
string
Identifiant de la mission.
string
Toujours cancelled en cas de succès.
object
Frais d’annulation (prix client HT uniquement).
boolean
true si le trajet retour a été annulé avec l’aller (et compté dans les frais). false pour un aller simple.

Garde-fou : mission plus annulable

Seules les missions avant le départ sont annulables (statuts publics scheduled / assigned, sauf une attribution encore en cours de confirmation). Une mission déjà partie ou au-delà (in_transit, in_transit_return, awaiting_documents, under_review, completed, incident) renvoie un 409 Conflict avec code: "not_cancellable" ; une mission déjà annulée, un 409 avec code: "already_cancelled". Une seconde annulation n’a donc aucun effet (ni nouveaux frais, ni nouvel événement).
Comme partout, branchez votre logique sur le status (409) et le code, pas sur le texte du detail. Un 502 sur une annulation laisse son issue incertaine (service qui n’a pas répondu à temps, ou annulation non confirmée) : relisez la mission via GET /transports/{id} avant de réessayer. Si elle est déjà cancelled, un nouvel essai recevrait 409 already_cancelled ; ses frais se lisent dans price.amount_ht.
L’annulation émet, peu après et une seule fois, l’événement transport.cancelled avec un payload { id, transport_id, cancelled_at }, ainsi qu’un transport.status_changed vers cancelled. Les frais n’y figurent pas : conservez la réponse 200 ci-dessus. Le motif (reason) n’est renvoyé ni par l’événement ni par la réponse : gardez-le de votre côté si vous en avez besoin.

Récapitulatif des garde-fous

La mission est partie (J+0 ou au-delà, ou statut public in_transit et suivants), ou annulée. Tant que ce verrou est fermé, on ne peut plus modifier les dates, les options ni les documents. Vérifiez l’état via GET /transports/{id}.
La jambe ciblée est déjà payée ou facturée (code: leg_invoiced) — pour un aller-retour facturé en une fois, le retour compte comme facturé — ou la mission est annulée. Le prix est figé pour des raisons comptables : aucune refacturation n’est possible.
La mission n’est plus annulable : seules les missions avant le départ (scheduled / assigned) le sont. Une mission partie ou terminée — ou dont l’attribution au convoyeur est encore en cours de confirmation — renvoie 409 not_cancellable, une mission déjà annulée 409 already_cancelled. Après un 502 sur une annulation, un nouvel essai qui reçoit already_cancelled signifie que la première tentative avait abouti.
La mission n’appartient pas au groupe de votre clé. Pour ne pas révéler son existence, l’API renvoie 404 plutôt que 403. Vérifiez l’id et la clé utilisée.
Un champ requis manque (par exemple motif ou new_price sur une refacturation), leg n’est ni aller ni retour, les dates sont incohérentes (livraison avant enlèvement, retour avant aller, date dans le passé), ou une jambe retour est visée sur une mission sans trajet retour (code: no_return_leg). detail résume l’erreur ; lisez le tableau errors[] du corps problem+json pour le détail champ par champ.

Le convoyeur doit valider vos modifications

Dès qu’un convoyeur est attribué à la mission et qu’elle n’est pas encore partie, une modification de dates ou d’options ne s’applique pas dans son dos : elle lui est soumise. Il reçoit une notification et doit accepter ou refuser les nouvelles modalités depuis l’application MED. Chaque réponse vous le dit :
En cas de refus, le convoyeur est retiré de la mission, qui repart en ligne pour être réattribuée. C’est aussi ce qui se produit s’il ne répond pas après trois relances. Les dates ou options que vous avez posées, elles, restent appliquées.Concrètement : une modification tardive sur une mission attribuée peut vous coûter votre convoyeur. Prévoyez-le dans votre intégration plutôt que de le découvrir la veille d’un enlèvement.
Pour le suivre : l’événement transport.driver_validation_requested est émis à la création de la demande, et le statut de la mission repasse en scheduled (non attribuée) si le convoyeur refuse. Le champ driver_validation de la requête est réservé aux administrateurs MED : l’envoyer avec une clé partenaire renvoie 403 forbidden_field. Il n’est pas ignoré en silence, précisément pour qu’une intégration ne croie jamais avoir désactivé cette validation.

Étapes suivantes

Webhooks

Recevez transport.dates_updated, transport.price_adjusted et transport.cancelled en temps réel sur votre endpoint signé.

Suivre une mission

Statut public, positions GPS du convoyeur et documents/PV de la mission.

Gestion des erreurs

Le format RFC 9457 et la liste des codes de conflit à gérer.