Traduction de courtoisie — la version anglaise fait autorité.
Statut : Brouillon v0.1 · Une spécification ouverte et indépendante de tout fournisseur · Licence : ouverte (voir §10)
OTEP est un protocole ouvert et indépendant de tout fournisseur pour le cycle de vie de tout sujet traçable. Il définit un seul modèle d'événement et un seul vocabulaire de statut afin que les événements de suivi provenant de n'importe quelle source — livraison par flotte propre, transporteurs tiers, étiquettes de transporteur et au-delà — puissent être échangés, compris et projetés vers les standards internationaux sans réintégration pour chaque partie.
Ce document est la spécification : la structure, les champs, les tables de statut, la machine à états, les correspondances avec les standards externes et les règles de conformité pour construire une implémentation conforme. Il est indépendant de toute implémentation — il décrit le protocole, et non les rouages internes d'un fournisseur particulier. Les mots-clés RFC-2119 (MUST / SHOULD / MAY) sont normatifs.
Le suivi est fragmenté : chaque transporteur nomme les champs et les codes de statut différemment, chaque canal de livraison rapporte selon sa propre forme, et la connexion aux standards mondiaux signifie réintégrer encore et encore. OTEP donne aux producteurs et aux consommateurs un langage commun unique : un producteur émet des événements OTEP une fois, et chaque consommateur OTEP les comprend et peut les projeter vers le standard dont il a besoin.
Une chronologie est une enveloppe portant le sujet suivi et une liste ordonnée d'événements.
{
"otep_version": "0.1",
"profile": "parcel", // domain profile (§8)
"subject": {
"tracking_number": "SR123...", // ≥1 identifier required
"order_id": 12345, // optional
"package_id": 67890, // optional
"external_tracking_number": "1Z...",// optional
"gs1_sscc": "00...", // optional — enables EPCIS epcList
"piece_id": "..." // optional — enables ONE Record linkage
},
"current_status": "delivered", // projection of the latest event
"delivered": true,
"events": [ /* OTEP events, §4 */ ]
}
{
// WHAT — carried once at the timeline level (subject above)
// WHEN
"occurred_at": "2026-06-10T09:30:00-04:00", // event instant, ISO-8601 with offset
"recorded_at": "2026-06-10T09:45:23-04:00", // ingestion instant (optional)
"time_type": "actual", // actual | estimated | scheduled
// WHERE
"location": {
"name": "Toronto Hub",
"code": "YYZ-2",
"gln": "0614141000005", // GS1 GLN → EPCIS SGLN
"lat": 43.6777, "lng": -79.6248,
"country": "CA" // ISO 3166-1 alpha-2
},
// WHY / WHAT HAPPENED
"status_code": "out_for_delivery", // an OTEP status code (§4)
"phase": "out_for_delivery", // derived from status_code
"incident_reason": null, // an OTEP incident reason (§4) when exception
// WHO
"actor": { "type": "driver", "name": "Jane D.", "phone": "+1..." },
// PROVENANCE
"source": {
"type": "self_delivery", // self_delivery | third_party_delivery | carrier_label
"provider_id": null,
"carrier_code": null,
"external_event_code": null, // your raw status code (preserve it)
"raw": { /* original payload */ }
},
// PROOF
"pod": {
"photos": [ { "url": "...", "content_base64": null } ],
"signature": [ { "url": "...", "content_base64": null } ],
"recipient": "John Smith"
}
}
Chaque champ sauf subject, occurred_at, status_code et source est optionnel —
les sources partielles remplissent ce qu'elles ont. Les scans de nœud/hub définissent location sur l'installation de scan.
La télémétrie GPS à haute fréquence n'est PAS un événement OTEP ; un événement est émis lors d'un changement de statut ou
d'un scan de nœud.
20 codes canoniques de cycle de vie. phase est toujours dérivable du code.
| code | phase | terminal | POD | signification |
|---|---|---|---|---|
information_submitted |
pre_shipment | informations de commande reçues | ||
booking_confirmed |
pre_shipment | transporteur/réservation confirmé | ||
awaiting_pickup |
pre_shipment | prêt pour la collecte | ||
out_for_pickup |
pickup | en route pour la collecte | ||
picked_up |
pickup | ✓ | collecté chez l'expéditeur | |
pickup_failed |
exception | tentative de collecte échouée | ||
pickup_rescheduled |
exception | la collecte sera retentée | ||
received |
inbound | reçu à l'installation | ||
arrival_scan |
inbound | scan d'arrivée au nœud | ||
in_transit |
transit | en mouvement | ||
package_outbound |
transit | a quitté l'installation | ||
removed_from_route |
exception | retiré de la tournée | ||
route_cancelled |
exception | tournée annulée | ||
out_for_delivery |
out_for_delivery | dans le véhicule | ||
delivered |
delivered | ✓ | ✓ | livré au destinataire |
delivery_failed |
exception | tentative de livraison échouée | ||
delivery_rescheduled |
exception | sera retentée / relivrée | ||
return_to_sender |
return | ✓ | retour à l'origine | |
rejected_by_recipient |
return | ✓ | destinataire a refusé | |
cancelled |
return | ✓ | commande annulée |
pre_shipment · preparing · pickup · inbound · in_custody · transit ·
out_for_delivery · delivered · exception · return. (preparing et in_custody
sont réservés aux profils non-colis — §8.)
source.type : self_delivery · third_party_delivery · carrier_label.
time_type : actual · estimated · scheduled (par défaut actual).
Lorsqu'un événement est dans la phase exception, il SHOULD porter un incident_reason issu de ce
vocabulaire normalisé :
carrier_damaged_parcel, carrier_sorting_error, carrier_address_not_found,
carrier_parcel_lost, carrier_not_enough_time, carrier_vehicle_issue,
carrier_capacity_exceeded, carrier_mechanical_delayretailer_cancelled, retailer_incorrect_data, retailer_not_ready,
retailer_incorrect_parcel, retailer_incorrect_dimensions, retailer_packaging_issueconsignee_refused, consignee_business_closed, consignee_not_available,
consignee_not_home, consignee_cancelled, consignee_verification_failed,
consignee_incorrect_address, consignee_access_restricted, consignee_safe_place_unavailablecustoms_delay, customs_documentation, customs_duties_unpaid,
customs_prohibited, customs_inspectionweather_delay, natural_disaster, force_majeureparcel_being_researched, security_issue, regulatory_hold, unknownLes phases avancent vers l'avant ; les exceptions interrompent et se résolvent en retour. Les états terminaux (delivered,
return_to_sender, rejected_by_recipient, cancelled) clôturent le sujet.
pre_shipment → preparing → pickup → inbound → in_custody → transit → out_for_delivery → delivered ✓
└──────────── exception ──────────┘
└──────────── return / cancelled ✓
Règles :
occurred_at et MUST tolérer une arrivée dans le désordre.Les quatre dimensions d'OTEP s'alignent champ par champ avec les principaux standards, de sorte que chacun est une projection de sortie d'un événement OTEP.
| OTEP | GS1 EPCIS 2.0 | IATA ONE Record | UN/CEFACT |
|---|---|---|---|
occurred_at (+offset) |
eventTime + eventTimeZoneOffset |
eventDate + eventTimeType=Actual |
Event Date/Time |
recorded_at |
recordTime |
recordedAt | — |
| sujet (sscc/piece) | epcList (SSCC URN) |
linkedObject → Piece/Shipment |
Consignment |
location (gln/lat/lng) |
readPoint / bizLocation (SGLN) |
recordedAtLocation → Location |
Location |
status_code |
bizStep + disposition |
eventCode |
Transport status code |
actor |
sourceList / extension | Actor / Party | Party |
incident_reason |
disposition / ErrorDeclaration | event remark | Status reason code |
Les valeurs par code (EPCIS CBV bizStep/disposition, ONE Record eventCode, code UN/CEFACT)
sont publiées dans le codebook lisible par machine. Au-delà de ces standards internationaux, une
chronologie OTEP peut aussi être projetée vers des traces OpenTelemetry, des observations OGC SensorThings et
des plateformes de commerce courantes (AfterShip, Shopify, Amazon, Walmart, BigCommerce, Magento,
WooCommerce, Etsy).
Confiance : les valeurs EPCIS CBV sont des URN standard stables. Les valeurs de code ONE Record et UN/CEFACT sont les plus adaptées pour le dernier kilomètre et devraient être validées contre les listes de codes officielles avant tout usage externe. « Delivered to consignee » n'a pas de bizStep CBV exact — la correspondance la plus proche (
receiving+received) est utilisée, ou une URN d'extension de vocabulaire utilisateur.
OTEP est consommé via une petite surface HTTP en lecture seule ; tous les points de terminaison sont publics.
| Verbe | Chemin | Retourne |
|---|---|---|
| GET | /api/v1/otep/trackings/{tracking_number} |
la chronologie pour un numéro de suivi |
| GET | /api/v1/otep/trackings/{tracking_number}/events |
les événements uniquement |
| POST | /api/v1/otep/trackings/batch |
plusieurs numéros de suivi en un seul appel |
| POST | /api/v1/otep/validate |
vérification de conformité pour une chronologie postée (§9) |
Une requête GraphQL exposant la même chronologie est également disponible.
La même chronologie est sérialisée dans la représentation que vous demandez, via un paramètre de requête
?format= ou un profil Accept :
| Requête | Représentation |
|---|---|
?format=otep (par défaut) |
chronologie OTEP native |
?format=epcis |
GS1 EPCIS 2.0 (ObjectEvents JSON-LD) |
?format=onerecord |
IATA ONE Record (LogisticsEvents) |
?format=uncefact |
statut de transport UN/CEFACT |
?format=otlp |
traces OpenTelemetry |
?format=sensorthings |
observations OGC SensorThings |
?format=aftership | shopify | amazon | walmart | bigcommerce | magento | woocommerce | etsy |
la forme de suivi/exécution de la plateforme |
Les événements qui ne peuvent pas se voir attribuer un code dans une projection sont ignorés et comptabilisés (jamais abandonnés silencieusement).
OTEP est un protocole général, pas un protocole de colis. La couche protocole (enveloppe d'événement, colonne
vertébrale des phases, machine à états) est universelle ; les codes de statut concrets appartiennent à un profil déclaré sur
la chronologie via profile. Les codes du §4 sont le profil parcel. D'autres domaines —
déménagement, livraison de nourriture, stockage et au-delà — ajoutent leurs propres jeux de codes sous leur profil,
avec l'espace de noms otep:<profile>:<code>, chacun se rattachant à la même colonne vertébrale des phases. Ajouter un profil
est une extension, pas un changement de protocole.
Un producteur ou un consommateur est conforme à OTEP lorsque chaque événement qu'il émet ou accepte satisfait ces tables et règles.
| Champ | Type | Req. | Contraintes |
|---|---|---|---|
otep_version |
string | MUST | semver, p. ex. 0.1 |
profile |
string | MUST | un profil enregistré |
subject |
object | MUST | §9.2 |
current_status |
string|null | SHOULD | un code de statut (§4) |
current_phase |
string|null | SHOULD | MUST être égal à la phase de current_status si les deux sont présents |
delivered |
boolean | SHOULD | true ssi current_status = delivered |
events |
array | MUST | objets événement (§9.3), ordonnables par occurred_at |
subjectAu moins UN de tracking_number / order_id / package_id MUST être présent.
| Champ | Type | Contraintes |
|---|---|---|
tracking_number |
string | non vide |
order_id / package_id |
integer|null | |
external_tracking_number |
string|null | |
gs1_sscc |
string|null | 18 chiffres |
piece_id |
string|null |
| # | Champ | Type | Req. | Contraintes |
|---|---|---|---|---|
| 1 | occurred_at |
string | MUST | ISO-8601 avec offset |
| 2 | recorded_at |
string|null | SHOULD | ISO-8601 |
| 3 | time_type |
string | MAY (par défaut actual) |
actual | estimated | scheduled |
| 4 | status_code |
string|null | MUST¹ | un code du §4.1 |
| 5 | phase |
string|null | SHOULD | MUST être égal à la phase de status_code |
| 6 | incident_reason |
string|null | SHOULD² | un motif du §4.4 |
| 7 | description |
string|null | MAY | lisible par l'humain |
| 8 | location |
object|null | MAY | §9.4 |
| 9 | actor |
object|null | MAY | { type, name?, phone? } ; type ∈ {driver, operator, carrier, system} |
| 10 | source |
object | MUST | §9.5 |
| 11 | pod |
object|null | MAY³ | { photos[], signature[], recipient? } |
¹ Un événement codé MUST porter un code de statut du §4.1. Un scan brut que vous ne pouvez pas encore classer MAY définir
status_code = null, mais MUST préserver le code natif dans source.external_event_code et MUST
être comptabilisé, jamais abandonné.
² Un événement de phase exception SHOULD porter un incident_reason.
³ Les événements avec status_code ∈ {delivered, picked_up} SHOULD porter un pod.
locationname (string) · code (string) · gln (GS1 GLN, 13 chiffres) · lat / lng (WGS-84) ·
country (ISO 3166-1 alpha-2). Tous optionnels.
source| Champ | Type | Req. | Contraintes |
|---|---|---|---|
type |
string | MUST | self_delivery | third_party_delivery | carrier_label |
provider_id |
integer|null | MAY | |
carrier_code |
string|null | MAY | |
external_event_code |
string|null | SHOULD⁴ | votre code de statut brut |
raw |
object|null | MAY | charge utile d'origine |
⁴ MUST être présent quand status_code est null, afin que le code natif ne soit jamais perdu.
occurred_at MUST s'analyser comme ISO-8601. Normalisez les époques numériques et .NET /Date(ms)/
à l'ingestion ; n'émettez pas ces formes.occurred_at, tolérer une arrivée dans le désordre,
et dédupliquer sur (subject, status_code, occurred_at).phase, current_status, current_phase, delivered sont des projections — s'ils
sont présents, ils MUST être cohérents avec la chronologie des événements.Vérifiez votre sortie en POSTant une chronologie à POST /api/v1/otep/validate. Traitez toute
errors comme bloquante ; corrigez les warnings. Un JSON Schema lisible par machine et le codebook
complet (chaque code de statut, phase et correspondance externe) sont publiés pour une validation hors ligne.
OTEP est une spécification ouverte, libre d'implémentation pour toute partie.
otep:<profile>:<code>, les phases sous la forme
otep:phase:<name>. Une fois publiée dans une version diffusée, la signification d'un identifiant est immuable.otep_version).x-
(otep:parcel:x-my_code) jusqu'à leur enregistrement.OTEP invite les autres fournisseurs à apporter leur propre standard d'événements de suivi afin qu'OTEP puisse interopérer avec lui, dans les deux directions :
source.external_event_code ; les codes non mis en correspondance sont comptabilisés, jamais abandonnés.Même si vous ne pouvez pas adopter OTEP directement, vous êtes invité à ajouter un seul otep_status normalisé
à vos propres réponses d'API et à partager vos codes d'événements de suivi pour la mise en correspondance. Proposez
les correspondances par rapport à cette spécification (plutôt que de forker) pour que les implémenteurs convergent ; les nouveaux formats se branchent dans
la même négociation de contenu ?format= et ne cassent jamais un consommateur existant.