Vertaling voor het gemak — de Engelse versie is gezaghebbend.
Status: Draft v0.1 · Een open, leveranciersneutrale specificatie · Licentie: open (zie §10)
OTEP is een open, leveranciersneutraal protocol voor de levenscyclus van elk traceerbaar subject. Het definieert één gebeurtenismodel en één statusvocabulaire, zodat trackinggebeurtenissen uit elke bron — bezorging met eigen vloot, externe koeriers, vervoerderslabels en daarbuiten — kunnen worden uitgewisseld, begrepen en geprojecteerd naar internationale standaarden zonder voor elke partij opnieuw te hoeven integreren.
Dit document is de specificatie: de structuur, de velden, de statustabellen, de state machine, de mappings naar externe standaarden en de conformiteitsregels voor het bouwen van een conforme implementatie. Het is implementatie-onafhankelijk — het beschrijft het protocol, niet de interne werking van een bepaalde leverancier. RFC-2119-sleutelwoorden (MUST / SHOULD / MAY) zijn normatief.
Tracking is gefragmenteerd: elke vervoerder benoemt velden en statuscodes anders, elk bezorgkanaal rapporteert in zijn eigen vorm, en aansluiten op wereldwijde standaarden betekent keer op keer opnieuw integreren. OTEP geeft producenten en consumenten één gemeenschappelijke taal: een producent zendt OTEP-gebeurtenissen één keer uit, en elke OTEP-consument begrijpt ze en kan ze projecteren naar de standaard die hij nodig heeft.
Een tijdlijn is een envelop die het getraceerde subject en een geordende lijst van gebeurtenissen draagt.
{
"otep_version": "0.1",
"profile": "parcel", // domeinprofiel (§8)
"subject": {
"tracking_number": "SR123...", // ≥1 identifier vereist
"order_id": 12345, // optioneel
"package_id": 67890, // optioneel
"external_tracking_number": "1Z...",// optioneel
"gs1_sscc": "00...", // optioneel — schakelt EPCIS epcList in
"piece_id": "..." // optioneel — schakelt ONE Record-koppeling in
},
"current_status": "delivered", // projectie van de laatste gebeurtenis
"delivered": true,
"events": [ /* OTEP-gebeurtenissen, §4 */ ]
}
{
// WHAT — eenmalig gedragen op tijdlijnniveau (subject hierboven)
// WHEN
"occurred_at": "2026-06-10T09:30:00-04:00", // gebeurtenismoment, ISO-8601 met offset
"recorded_at": "2026-06-10T09:45:23-04:00", // moment van opname (optioneel)
"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", // een OTEP-statuscode (§4)
"phase": "out_for_delivery", // afgeleid van status_code
"incident_reason": null, // een OTEP incident reason (§4) bij uitzondering
// 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, // jouw ruwe statuscode (bewaar deze)
"raw": { /* originele payload */ }
},
// PROOF
"pod": {
"photos": [ { "url": "...", "content_base64": null } ],
"signature": [ { "url": "...", "content_base64": null } ],
"recipient": "John Smith"
}
}
Elk veld behalve subject, occurred_at, status_code en source is optioneel —
gedeeltelijke bronnen vullen aan wat ze hebben. Node-/hub-scans stellen location in op de scannende faciliteit.
Hoogfrequente GPS-telemetrie is GEEN OTEP-gebeurtenis; een gebeurtenis wordt uitgezonden bij een statuswijziging of
een node-scan.
20 canonieke levenscycluscodes. phase is altijd afleidbaar uit de code.
| code | phase | terminaal | POD | betekenis |
|---|---|---|---|---|
information_submitted |
pre_shipment | orderinformatie ontvangen | ||
booking_confirmed |
pre_shipment | vervoerder/boeking bevestigd | ||
awaiting_pickup |
pre_shipment | klaar voor ophalen | ||
out_for_pickup |
pickup | onderweg om op te halen | ||
picked_up |
pickup | ✓ | opgehaald bij verzender | |
pickup_failed |
exception | ophaalpoging mislukt | ||
pickup_rescheduled |
exception | ophalen wordt opnieuw geprobeerd | ||
received |
inbound | ontvangen bij faciliteit | ||
arrival_scan |
inbound | aankomstscan bij node | ||
in_transit |
transit | onderweg | ||
package_outbound |
transit | faciliteit verlaten | ||
removed_from_route |
exception | van route gehaald | ||
route_cancelled |
exception | route geannuleerd | ||
out_for_delivery |
out_for_delivery | op het voertuig | ||
delivered |
delivered | ✓ | ✓ | bezorgd bij geadresseerde |
delivery_failed |
exception | bezorgpoging mislukt | ||
delivery_rescheduled |
exception | wordt opnieuw geprobeerd / opnieuw bezorgd | ||
return_to_sender |
return | ✓ | terug naar afzender | |
rejected_by_recipient |
return | ✓ | ontvanger heeft geweigerd | |
cancelled |
return | ✓ | order geannuleerd |
pre_shipment · preparing · pickup · inbound · in_custody · transit ·
out_for_delivery · delivered · exception · return. (preparing en in_custody
zijn gereserveerd voor niet-pakketprofielen — §8.)
source.type: self_delivery · third_party_delivery · carrier_label.
time_type: actual · estimated · scheduled (standaard actual).
Wanneer een gebeurtenis zich in de exception-fase bevindt, SHOULD deze een incident_reason dragen uit dit
genormaliseerde vocabulaire:
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, unknownFasen schuiven voorwaarts op; uitzonderingen onderbreken en lossen weer op. Terminale toestanden (delivered,
return_to_sender, rejected_by_recipient, cancelled) sluiten het subject af.
pre_shipment → preparing → pickup → inbound → in_custody → transit → out_for_delivery → delivered ✓
└──────────── exception ──────────┘
└──────────── return / cancelled ✓
Regels:
occurred_at en MUST aankomst in willekeurige volgorde tolereren.De vier dimensies van OTEP komen veld voor veld overeen met de belangrijkste standaarden, zodat elk een uitvoerprojectie van een OTEP-gebeurtenis is.
| 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 | — |
| subject (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 |
Per-code-waarden (EPCIS CBV bizStep/disposition, ONE Record eventCode, UN/CEFACT-code)
worden gepubliceerd in het machineleesbare codeboek. Naast deze internationale standaarden kan een OTEP-
tijdlijn ook worden geprojecteerd naar OpenTelemetry-traces, OGC SensorThings-observaties en
gangbare commerceplatforms (AfterShip, Shopify, Amazon, Walmart, BigCommerce, Magento,
WooCommerce, Etsy).
Vertrouwen: EPCIS CBV-waarden zijn stabiele standaard-URN's. ONE Record- en UN/CEFACT-codewaarden zijn best-fit voor de last mile en moeten worden gevalideerd tegen de officiële codelijsten vóór extern gebruik. "Delivered to consignee" heeft geen exacte CBV
bizStep— de dichtstbijzijnde passende (receiving+received) wordt gebruikt, of een uitbreidings-URN uit een gebruikersvocabulaire.
OTEP wordt geconsumeerd via een klein, alleen-lezen HTTP-oppervlak; alle endpoints zijn openbaar.
| Werkwoord | Pad | Retourneert |
|---|---|---|
| GET | /api/v1/otep/trackings/{tracking_number} |
de tijdlijn voor een trackingnummer |
| GET | /api/v1/otep/trackings/{tracking_number}/events |
alleen gebeurtenissen |
| POST | /api/v1/otep/trackings/batch |
meerdere trackingnummers in één aanroep |
| POST | /api/v1/otep/validate |
conformiteitscontrole voor een geposte tijdlijn (§9) |
Een GraphQL-query die dezelfde tijdlijn blootlegt is eveneens beschikbaar.
Dezelfde tijdlijn wordt geserialiseerd naar de representatie die je aanvraagt, via een ?format=-
queryparameter of een Accept-profiel:
| Verzoek | Representatie |
|---|---|
?format=otep (standaard) |
native OTEP-tijdlijn |
?format=epcis |
GS1 EPCIS 2.0 (JSON-LD ObjectEvents) |
?format=onerecord |
IATA ONE Record (LogisticsEvents) |
?format=uncefact |
UN/CEFACT transport status |
?format=otlp |
OpenTelemetry-traces |
?format=sensorthings |
OGC SensorThings-observaties |
?format=aftership | shopify | amazon | walmart | bigcommerce | magento | woocommerce | etsy |
de tracking-/fulfillmentvorm van het platform |
Gebeurtenissen waaraan in een projectie geen code kan worden toegewezen, worden overgeslagen en geteld (nooit stilzwijgend weggegooid).
OTEP is een algemeen protocol, geen pakketprotocol. De protocollaag (gebeurtenisenvelop, faseruggengraat,
state machine) is universeel; concrete statuscodes behoren tot een profiel dat op
de tijdlijn wordt gedeclareerd via profile. De codes in §4 vormen het parcel-profiel. Andere domeinen —
verhuizen, voedselbezorging, opslag en daarbuiten — voegen hun eigen codeverzamelingen toe onder hun profiel,
genaamruimted als otep:<profile>:<code>, elk gemapt op dezelfde faseruggengraat. Een profiel toevoegen
is een uitbreiding, geen protocolwijziging.
Een producent of consument is OTEP-conform wanneer elke gebeurtenis die hij uitzendt of accepteert voldoet aan deze tabellen en regels.
| Veld | Type | Vereist | Beperkingen |
|---|---|---|---|
otep_version |
string | MUST | semver, bijv. 0.1 |
profile |
string | MUST | een geregistreerd profiel |
subject |
object | MUST | §9.2 |
current_status |
string|null | SHOULD | een statuscode (§4) |
current_phase |
string|null | SHOULD | MUST gelijk zijn aan de fase van current_status als beide aanwezig |
delivered |
boolean | SHOULD | true alleen als current_status = delivered |
events |
array | MUST | gebeurtenisobjecten (§9.3), ordenbaar op occurred_at |
subjectTen minste ÉÉN van tracking_number / order_id / package_id MUST aanwezig zijn.
| Veld | Type | Beperkingen |
|---|---|---|
tracking_number |
string | niet-leeg |
order_id / package_id |
integer|null | |
external_tracking_number |
string|null | |
gs1_sscc |
string|null | 18 cijfers |
piece_id |
string|null |
| # | Veld | Type | Vereist | Beperkingen |
|---|---|---|---|---|
| 1 | occurred_at |
string | MUST | ISO-8601 met offset |
| 2 | recorded_at |
string|null | SHOULD | ISO-8601 |
| 3 | time_type |
string | MAY (standaard actual) |
actual | estimated | scheduled |
| 4 | status_code |
string|null | MUST¹ | een code in §4.1 |
| 5 | phase |
string|null | SHOULD | MUST gelijk zijn aan de fase van status_code |
| 6 | incident_reason |
string|null | SHOULD² | een reden in §4.4 |
| 7 | description |
string|null | MAY | leesbaar voor mensen |
| 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? } |
¹ Een gecodeerde gebeurtenis MUST een statuscode uit §4.1 dragen. Een ruwe scan die je nog niet kunt classificeren MAY
status_code = null instellen, maar MUST de native code bewaren in source.external_event_code en MUST
worden geteld, nooit weggegooid.
² Een gebeurtenis in de exception-fase SHOULD een incident_reason dragen.
³ Gebeurtenissen met status_code ∈ {delivered, picked_up} SHOULD een pod dragen.
locationname (string) · code (string) · gln (GS1 GLN, 13 cijfers) · lat / lng (WGS-84) ·
country (ISO 3166-1 alpha-2). Alle optioneel.
source| Veld | Type | Vereist | Beperkingen |
|---|---|---|---|
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⁴ | jouw ruwe statuscode |
raw |
object|null | MAY | originele payload |
⁴ MUST aanwezig zijn wanneer status_code null is, zodat de native code nooit verloren gaat.
occurred_at MUST parsen als ISO-8601. Normaliseer numerieke epochs en .NET /Date(ms)/
bij ingest; zend die vormen niet uit.occurred_at, aankomst in willekeurige volgorde tolereren,
en ontdubbelen op (subject, status_code, occurred_at).phase, current_status, current_phase, delivered zijn projecties — indien
aanwezig MUST ze consistent zijn met de gebeurtenistijdlijn.Verifieer je uitvoer door een tijdlijn te POSTen naar POST /api/v1/otep/validate. Behandel elke
errors als blokkerend; verhelp warnings. Een machineleesbaar JSON Schema en het volledige
codeboek (elke statuscode, fase en externe mapping) worden gepubliceerd voor offline validatie.
OTEP is een open specificatie, vrij voor elke partij om te implementeren.
otep:<profile>:<code>, fasen als
otep:phase:<name>. Eenmaal gepubliceerd in een uitgebrachte versie is de betekenis van een identifier onveranderlijk.otep_version).x--prefix gebruiken
(otep:parcel:x-my_code) totdat ze geregistreerd zijn.OTEP nodigt andere leveranciers uit om hun eigen trackinggebeurtenisstandaard mee te brengen, zodat OTEP ermee kan interopereren, in beide richtingen:
source.external_event_code; niet-gemapte codes worden geteld, nooit weggegooid.Zelfs als je OTEP niet rechtstreeks kunt adopteren, ben je welkom om een enkele genormaliseerde otep_status
toe te voegen aan je eigen API-responses en om je trackinggebeurteniscodes te delen voor crosswalk-mapping. Stel
mappings voor tegen deze spec (in plaats van te forken) zodat implementeerders convergeren; nieuwe formaten pluggen in
dezelfde ?format= content negotiation en breken nooit een bestaande consument.