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, refacturer une jambe ou l’annuler — 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). 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 qui n’est pas encore terminée.
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 :
export BASE="https://api.myexpressdriver.com/v1"
export MED_API_KEY="med_live_xxxxxxxxxxxxxxxxxxxx"
export TID="-O9xAbCdEf"   # id de la mission (offer_id)
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

legs
object
requis
Carte des jambes à modifier (au moins une). Les clés possibles sont :
CléJambe concernée
enlevement-allerEnlèvement, trajet aller
livraison-allerLivraison, trajet aller
enlevement-retourEnlèvement, trajet retour
livraison-retourLivraison, trajet retour
Chaque valeur est un objet { date, start_time, end_time }.
legs.<clé>.date
string
Date au format ISO YYYY-MM-DD (ex. 2026-07-02).
legs.<clé>.start_time
string
Début de la fenêtre horaire (ex. 09:00).
legs.<clé>.end_time
string
Fin de la fenêtre horaire (ex. 12:00).
notify
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é.
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

curl -s -X PATCH "$BASE/transports/$TID/dates" \
  -H "X-Api-Key: $MED_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "legs": {
      "enlevement-aller": {
        "date": "2026-07-02",
        "start_time": "09:00",
        "end_time": "12:00"
      }
    },
    "notify": true
  }'
Réponse 200 :
{
  "id": "-O9xAbCdEf",
  "updated_legs": ["enlevement-aller"]
}
id
string
Identifiant de la mission.
updated_legs
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 » :
CasExemple
Enlèvement aujourd’hui (J+0)enlèvement le jour de l’appel
Enlèvement demain (J+1)enlèvement le lendemain
Enlèvement le prochain jour ouvréun vendredi pour un lundi (le week-end ne compte pas)
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
option_key
string
requis
Clé de l’option à ajouter. Le prix de l’option est dérivé du catalogue de la grille de votre groupe — vous ne le transmettez pas. Une clé inconnue renvoie une erreur Unknown option.

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).
Clé (option_key)LibelléPrix client indicatif
Plaque_WgaragePlaque W Garage50 €
CPICertificat provisoire d’immatriculation45 €
Lavage_exterieurLavage Extérieur19 €
Lavage_interieurLavage Intérieur15 €
Plein_carburantPlein carburant10 €
Recharge_electriqueRecharge électrique30 €
Pression_pneusPression pneus10 €
Recharge_lave_glaceRecharge lave glace10 €
Mise_en_mainMise en main20 €
BoosterBooster30 €
curl -s -X POST "$BASE/transports/$TID/options" \
  -H "X-Api-Key: $MED_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "option_key": "Lavage_interieur"
  }'
Réponse 200 :
{
  "option": "Lavage_interieur",
  "new_price": 195.5,
  "delta": 15.0
}
option
string
Clé de l’option ajoutée.
new_price
number | null
Nouveau prix de la mission après recalcul.
delta
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.
curl -s -X DELETE "$BASE/transports/$TID/options/Lavage_interieur" \
  -H "X-Api-Key: $MED_API_KEY"
Réponse 200 :
{
  "new_price": 180.5,
  "delta": -15.0
}
new_price
number | null
Nouveau prix de la mission après retrait de l’option.
delta
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
leg
string
défaut:"aller"
Jambe concernée : aller ou retour.
new_price
number
requis
Nouveau prix HT facturé au client. Obligatoire.
motif
string
requis
Motif de la refacturation. Obligatoire — il est conservé dans l’historique.

Exemple

curl -s -X POST "$BASE/transports/$TID/price-adjustments" \
  -H "X-Api-Key: $MED_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "leg": "aller",
    "new_price": 210.00,
    "motif": "Distance revue à la hausse"
  }'
Réponse 201 :
{
  "id": "-O9xAbCdEf",
  "leg": "aller",
  "old_price_ht": 180.5,
  "new_price_ht": 210.0,
  "currency": "EUR"
}
id
string
Identifiant de la mission.
leg
string
Jambe refacturée (aller ou retour).
old_price_ht
number | null
Ancien prix HT facturé au client, avant ajustement.
new_price_ht
number | null
Nouveau prix HT facturé au client, après ajustement.
currency
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 (format application/problem+json) si la jambe ciblée est déjà payée ou facturée, ou si la mission est terminée et déjà payée / facturée. C’est une protection comptable : un prix figé dans une facture émise ne peut pas être modifié a posteriori.
{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "detail": "Leg already paid or invoiced."
}
Ce garde-fou est appliqué à deux niveaux : un contrôle rapide en amont, puis un backstop côté moteur de transport qui relit la source de vérité. Dans ce second cas, le detail peut être en français (ex. « Mission terminée et déjà payée »). Branchez donc votre logique sur le status (409), jamais sur le texte de detail qui peut varier selon le niveau qui a bloqué.

Historique des refacturations

Chaque refacturation est tracée. Consultez l’historique complet d’une mission via GET /transports/{id}/price-history.
curl -s "$BASE/transports/$TID/price-history" \
  -H "X-Api-Key: $MED_API_KEY"
Réponse 200 :
{
  "data": [
    {
      "leg": "aller",
      "old_price_ht": 180.5,
      "new_price_ht": 210.0,
      "motif": "Distance revue à la hausse",
      "created_at": "2026-06-17T09:12:00Z"
    }
  ]
}
data
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
reason
string
Motif d’annulation. Optionnel — le corps de la requête l’est aussi.
curl -s -X POST "$BASE/transports/$TID/cancel" \
  -H "X-Api-Key: $MED_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "reason": "Demande client" }'
Réponse 200 :
{
  "id": "-O9xAbCdEf",
  "status": "cancelled"
}
id
string
Identifiant de la mission.
status
string
Toujours cancelled en cas de succès.

Garde-fou : mission plus annulable

Seules les missions en amont sont annulables, c’est-à-dire avant le départ (statuts publics scheduled / assigned). Toute mission déjà partie ou au-delà (in_transit, in_transit_return, awaiting_documents, under_review, completed, incident) — ou déjà annulée — renvoie un 409 Conflict.
{
  "type": "about:blank",
  "title": "Conflict",
  "status": 409,
  "detail": "Transport already completed."
}
Comme partout, branchez votre logique sur le status (409), pas sur le texte du detail.
L’annulation émet l’événement transport.cancelled avec un payload { id, reason }.

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, ou la mission est terminée et déjà payée / facturée. Le prix est figé pour des raisons comptables : aucune refacturation n’est possible.
La mission n’est plus annulable : seules les missions en amont avant le départ (scheduled / assigned) le sont. Une mission partie, terminée ou déjà annulée renvoie 409.
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 sur une refacturation, ou aucun des deux prix), ou les dates sont incohérentes (livraison avant enlèvement, retour avant aller, date dans le passé). Lisez le tableau errors[] du corps problem+json pour le détail champ par champ.

É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.