Endpoints · Création
Créer un bordereau
L'endpoint central : corps de requête, réponse, statuts et idempotence.
Crée un bordereau — l'endpoint central. Scope write.
| Champ | Requis | Notes |
|---|---|---|
deliveryAddress.recipientName | Requis | |
deliveryAddress.street | Requis | |
deliveryAddress.city | Requis | |
deliveryAddress.postalCode | Requis | |
deliveryAddress.phoneNumber | Requis | Non vide — le format n'est pas vérifié. Envoyez +33612345678 |
deliveryAddress.email | Requis | E-mail valide — sert à notifier le destinataire du suivi. Absent des webhooks |
deliveryAddress.country | Optionnel | Défaut : "France" |
deliveryAddress.coordinates.latitude | Optionnel | Entre -90 et 90. Fournies, elles évitent le géocodage : envoyez-les si vous les avez |
deliveryAddress.coordinates.longitude | Optionnel | Entre -180 et 180. À envoyer avec la latitude |
parcelDetails.weight | Requis | Nombre > 0, en kg |
parcelDetails.nature | Requis | Description du contenu |
pickupAddress | Optionnel | Défaut : l'adresse d'enlèvement du compte |
externalReference.orderId | Optionnel | Votre identifiant de commande |
externalReference.shopDomain | Optionnel | |
metadata | Optionnel | Objet libre — absent de la réponse et des webhooks |
scheduledDate | Optionnel | ISO 8601 — EXIGE timeSlot avec lui |
timeSlot | Optionnel | Libellé ou id de créneau — EXIGE scheduledDate avec lui |
isCollection | Optionnel | Booléen strict. Marque une course de récupération (on va chercher chez le client final). Défaut : false |
collectionContact.phoneNumber | Si récupération | Requis dès que isCollection vaut true — le SMS d'enlèvement part sur ce numéro |
collectionContact.email | Si récupération | Requis dès que isCollection vaut true — reçoit le QR d'enlèvement en image |
collectionContact.name | Optionnel | Nom de la personne chez qui on récupère, utilisé dans les messages |
groupReference | Optionnel | 1 à 64 caractères. Référence commune à plusieurs bordereaux qui vont ensemble |
notes | Optionnel |
externalReference.platform est forcé à "api" côté serveur : votre valeur est ignorée.
Récupérer chez le client : isCollection
Un bordereau décrit par défaut une livraison : de chez vous vers votre client. Certains métiers ont besoin du trajet inverse — un cordeur de raquettes, un retoucheur, un réparateur récupère l'objet chez le client, le traite en atelier, puis le renvoie. Cela fait deux courses : une récupération, puis une livraison.
Passez isCollection: true sur la première pour la marquer comme telle. Sur la seconde, ne le passez pas : c'est une livraison ordinaire.
Le champ n'accepte qu'un vrai booléen. "true" entre guillemets est refusé en 400, tout comme 1 : une chaîne non vide est toujours vraie en JavaScript, donc "false" passerait pour un oui. Un refus explicite vaut mieux qu'une course qualifiée à l'envers.
isCollection est renvoyé par POST /shipping et par les lectures. Il est en revanche absent des webhooks.
Une récupération exige les coordonnées de la personne chez qui on passe
Sur une récupération, deliveryAddress désigne votre atelier — c'est là que le bien est rapporté. La personne à prévenir n'est donc pas celle de l'adresse de livraison : renseignez collectionContact.
collectionContact.phoneNumber et collectionContact.email sont obligatoires dès que isCollection vaut true. Sans eux la requête est refusée en 400 : une course partirait vers quelqu'un qui ne l'attend pas.
Au moment où un livreur est assigné, cette personne reçoit un SMS et un e-mail l'informant du passage, avec le créneau si vous en avez programmé un. Le QR d'enlèvement est joint en image dans l'e-mail et accessible par lien dans le SMS — votre client n'a donc rien à imprimer.
Relier deux courses : groupReference
Une récupération et sa livraison de retour sont deux bordereaux distincts, créés par deux appels. Passez la même groupReference sur les deux pour les rattacher — la valeur est la vôtre, choisissez ce qui vous parle : un numéro de commande, un numéro de dossier, un identifiant de réparation.
Vous les retrouvez ensuite en un appel : GET /shipping?groupReference=VOTRE-REFERENCE. Le champ est renvoyé par la création et par les lectures.
La portée est votre compte : deux marchands peuvent employer la même chaîne sans se gêner. Rien ne limite le nombre de bordereaux qui la partagent — si un dossier en compte trois, les trois se retrouvent ensemble.
Exemple : créer une récupération
Le bien part de chez le client (pickupAddress) vers votre atelier (deliveryAddress). La livraison du retour, une fois le travail fait, est une seconde course, ordinaire celle-là.
curl -X POST "https://api.arelpro.com/pro/api/v1/shipping" \
-H "Authorization: Bearer sk_live_VOTRE_CLE" \
-H "X-Client-Id: VOTRE_CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{
"isCollection": true,
"groupReference": "CMD-2001",
"collectionContact": {
"name": "Jean Dupont",
"phoneNumber": "+33612345678",
"email": "jean.dupont@example.com"
},
"pickupAddress": {
"street": "123 Rue de la République",
"city": "Paris",
"postalCode": "75001"
},
"deliveryAddress": {
"recipientName": "Mon atelier",
"street": "8 rue des Artisans",
"city": "Paris",
"postalCode": "75011",
"phoneNumber": "+33100000000",
"email": "atelier@example.com"
},
"parcelDetails": { "weight": 0.4, "nature": "Raquette de tennis" },
"externalReference": { "orderId": "CMD-2001" }
}'Envoyez les coordonnées si vous les avez
Si deliveryAddress.coordinates est absent, Arel géocode l'adresse pour situer la livraison. En transmettant la latitude et la longitude que votre boutique connaît déjà, vous supprimez cette étape : le bordereau est créé plus vite et la position utilisée pour le dispatch est exactement la vôtre, pas une interprétation de l'adresse écrite.
Les deux valeurs doivent être présentes et cohérentes. Une paire hors bornes est rejetée en 400 ; 0,0 est ignoré et déclenche le géocodage, car c'est presque toujours la trace d'un géocodage raté en amont, pas une position réelle.
Le format du téléphone n'est pas validé
deliveryAddress.phoneNumber est seulement vérifié non vide. Son format n'est pas contrôlé : 06 12 34 56 78 crée un bordereau en 201, exactement comme +33612345678.
Ce numéro sert à joindre le destinataire et figure sur le bordereau remis au livreur. Transmettez-le au format international — sans espaces ni ponctuation, indicatif pays en tête (+33612345678).
scheduledDate et timeSlot vont par paire
Le tableau les liste « optionnels » un par un, mais ils ne sont pas indépendants : fournissez les deux, ou aucun. L'un sans l'autre est refusé en 400, avec message seul et aucun slug : « scheduledDate et timeSlot doivent être fournis ensemble ».
Les autres refus de créneau, en 400 sans slug également : « Créneau horaire invalide » (libellé non reconnu et ne correspondant à aucun créneau du compte) et « Le créneau programmé doit être dans le futur ». Un créneau accepté s'écrit 14h-16h, 14:00-16:00, ou l'id / le libellé d'un créneau de votre compte (voir timeSlots dans GET /account).
Envoi programmé : le SMS part à l'assignation
Un bordereau express déclenche le SMS au destinataire dès la création : il contient le lien vers son code de remise et son suivi.
Un bordereau créé avec scheduledDate + timeSlot n'a encore ni livreur ni heure ferme : son SMS est émis à l'assignation du livreur. Le destinataire reçoit le même message, simplement plus tard.
L'émission est garantie une seule fois : une réassignation, après un abandon par exemple, ne génère pas de second SMS.
deliveryCode n'est jamais renvoyé par l'API, quel que soit le type d'envoi : c'est la preuve de livraison, réservée au destinataire et au livreur. GET /shipping/:id, GET /shipping, GET /shipping/tracking/:ref, POST /shipping, PUT /shipping/:id/cancel et les webhooks l'excluent tous.
# La cle est generee UNE FOIS, dans une variable — pas inline dans le curl.
# Relancez la commande curl telle quelle et vous rejouez la MEME cle : Arel
# rend le bordereau deja cree (200). Remettez $(uuidgen) directement dans
# l'en-tete et chaque relance creerait un SECOND bordereau, facture.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST "https://api.arelpro.com/pro/api/v1/shipping" \
-H "Authorization: Bearer sk_live_VOTRE_CLE" \
-H "X-Client-Id: VOTRE_CLIENT_ID" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"deliveryAddress": {
"recipientName": "Jean Dupont",
"street": "123 Rue de la République",
"city": "Paris",
"postalCode": "75001",
"phoneNumber": "+33612345678",
"email": "jean.dupont@example.com"
},
"parcelDetails": { "weight": 2.5, "nature": "Vêtements" },
"externalReference": { "orderId": "CMD-1001" }
}'Réponse :
{
"success": true,
"shipping": {
"_id": "665f...",
"referenceNumber": "AREL-20260716-00042",
"status": "created",
"price": 9,
"parcelDetails": { "weight": 2.5, "nature": "Vêtements" },
"notes": null,
"isCollection": false,
"pickupAddress": { },
"deliveryAddress": { },
"scheduledDelivery": { "date": null, "timeSlot": null, "datetime": null },
"externalReference": {
"platform": "api",
"orderId": "CMD-1001",
"shopDomain": null
},
"estimatedDeliveryDate": "2026-07-16T10:00:00.000Z",
"pdfUrl": "/pdfs/bordereau_AREL-20260716-00042_9f2c41d7b83e05a6c1de74baf39027ec.pdf",
"createdAt": "2026-07-16T10:00:00.000Z",
"updatedAt": "2026-07-16T10:00:00.000Z"
}
}La réponse est une projection stable : exactement les champs documentés, ni plus ni moins. deliveryCode (la preuve de livraison remise au livreur), professional et metadata n'y figurent pas. Cette discrétion vaut pour cette réponse, pas pour tout l'API : GET /shipping/:id renvoie le document complet et vous y reverrez metadata (dont la clé d'idempotence que vous aviez envoyée) et professional. deliveryCode, lui, est retiré de toutes les réponses de l'API — lectures comme écritures, PUT /shipping/:id/cancel compris.
deliverer est absent de cette projection — et le restera : à la création, aucun livreur n'est encore assigné. Récupérez-le via GET /shipping/:id ou l'événement shipping.assigned.
estimatedDeliveryDate est toujours renseigné ici : fin du créneau pour un envoi programmé, date de création pour un express (livraison le jour même). Le champ est typé date | null par prudence, mais un bordereau que vous venez de créer n'aura pas null.
pdfUrl est déjà rempli à la création : le PDF est généré dans la foulée. Mais c'est un chemin relatif (/pdfs/bordereau_….pdf), sans nom d'hôte. Plutôt que de lui préfixer un hôte deviné, appelez POST /shipping/:id/download-link : il vous rend la même ressource en URL absolue, déjà pointée sur le bon hôte.
Statuts possibles
Ce sont les statuts du champ status. Ils ne se confondent pas avec la liste des événements webhook, qui est plus courte. À la création, status vaut created ou programmed. programmed signifie qu'aucun livreur ne sera cherché immédiatement, pour deux raisons distinctes :
- vous avez fourni un créneau (
scheduledDate+timeSlot) ; - ou votre compte est configuré en « demande spéciale » (
isSpecialRequest, réglage Arel) : le dispatch est alors reporté à l'heure d'enlèvement convenue pour votre compte, même sans créneau dans votre requête.
Recevoir programmed alors que vous n'avez rien programmé n'est pas une anomalie. Ne traitez pas ce statut comme une erreur.
Idempotence — X-Idempotency-Key
Indispensable si votre réseau peut vous faire retenter un appel dont vous n'avez pas vu la réponse.
Cette protection ne couvre que POST /shipping
Tout ce qui suit — le rejeu à 200, le 409, le drapeau idempotent — vaut pour POST /shipping uniquement. POST /shipping/batch ne lit pas l'en-tête : un lot rejoué y crée des doublons facturés.
Utilisez un UUIDv4, pas un identifiant de commande
Une clé naturelle ("CMD-1001", un numéro de commande, un id de panier) sera réutilisée à votre insu — deuxième colis d'une même commande, renvoi après retour, re-livraison — et vous récupérerez alors silencieusement l'ancien bordereau au lieu du nouveau. Générez une clé par tentative d'expédition et stockez-la à côté de votre commande pour pouvoir la rejouer à l'identique.
Générez la clé une seule fois, hors de la fonction d'appel
Un crypto.randomUUID() ou un uuid.uuid4() écrit à l'intérieur de la fonction qui poste le bordereau est réévalué à chaque appel : au réessai, la clé est différente, l'idempotence ne s'applique pas, et Arel crée un second bordereau facturé.
Séquence correcte : (1) générez un UUIDv4 quand vous décidez d'expédier ; (2) persistez-le à côté de la commande avant l'appel HTTP ; (3) passez-le en paramètre à votre fonction d'envoi ; (4) au réessai, relisez la valeur stockée — ne la régénérez jamais.
Persistez avant l'appel, et non après : si le processus meurt pendant la requête, la clé doit déjà être en base — c'est précisément le scénario où le bordereau a pu être créé sans que vous ayez vu la réponse.
| Cas | HTTP | Corps |
|---|---|---|
| Première requête | 201 | { success, shipping } |
| Rejeu — même clé, même compte | 200 | { success, idempotent: true, shipping } |
| Clé déjà utilisée par un autre marchand | 409 | IDEMPOTENCY_KEY_CONFLICT |
Votre code doit accepter 200 et 201 comme un succès. C'est la seule différence entre « je viens de le créer » et « il existait déjà » ; le champ shipping a la même forme dans les deux cas. Le drapeau idempotent: true n'est présent que sur le rejeu — utilisez-le pour éviter de re-notifier votre client.
Le 409 signifie que cette clé est déjà prise par un autre bordereau que celui que vous pourriez rejouer. La réponse ne divulgue jamais le bordereau en question. Traitez-le comme « régénérez une clé et recommencez » — avec un UUIDv4, il ne se produit pas. Le rejeu (200) n'est cherché que parmi vos propres bordereaux.