Übersetzung zur besseren Verständlichkeit — maßgeblich ist die englische Fassung.
Status: Entwurf v0.1 · Eine offene, herstellerneutrale Spezifikation · Lizenz: offen (siehe §10)
OTEP ist ein offenes, herstellerneutrales Protokoll für den Lebenszyklus jedes verfolgbaren Objekts. Es definiert ein Ereignismodell und ein Statusvokabular, sodass Tracking-Ereignisse aus jeder Quelle — eigene Flottenzustellung, Drittanbieter-Kuriere, Frachtführer-Labels und darüber hinaus — ausgetauscht, verstanden und auf internationale Standards projiziert werden können, ohne für jede Partei neu integrieren zu müssen.
Dieses Dokument ist die Spezifikation: die Struktur, die Felder, die Statustabellen, der Zustands- automat, die Zuordnungen zu externen Standards und die Konformitätsregeln für den Aufbau einer konformen Implementierung. Es ist implementierungsunabhängig — es beschreibt das Protokoll, nicht die internen Abläufe eines bestimmten Herstellers. RFC-2119-Schlüsselwörter (MUST / SHOULD / MAY) sind normativ.
Tracking ist fragmentiert: jeder Frachtführer benennt Felder und Statuscodes anders, jeder Zustellkanal meldet in seiner eigenen Form, und die Anbindung an globale Standards bedeutet immer wieder eine erneute Integration. OTEP gibt Produzenten und Konsumenten eine einzige gemeinsame Sprache: ein Produzent gibt OTEP-Ereignisse einmal aus, und jeder OTEP-Konsument versteht sie und kann sie auf den von ihm benötigten Standard projizieren.
Eine Zeitleiste ist ein Umschlag, der das verfolgte Objekt und eine geordnete Liste von Ereignissen trägt.
{
"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"
}
}
Jedes Feld außer subject, occurred_at, status_code und source ist optional —
partielle Quellen füllen aus, was sie haben. Node-/Hub-Scans setzen location auf die scannende Einrichtung.
Hochfrequente GPS-Telemetrie ist KEIN OTEP-Ereignis; ein Ereignis wird bei einer Statusänderung oder
einem Node-Scan ausgegeben.
20 kanonische Lebenszyklus-Codes. phase ist immer aus dem Code ableitbar.
| code | phase | terminal | POD | Bedeutung |
|---|---|---|---|---|
information_submitted |
pre_shipment | Bestellinformationen empfangen | ||
booking_confirmed |
pre_shipment | Frachtführer/Buchung bestätigt | ||
awaiting_pickup |
pre_shipment | abholbereit | ||
out_for_pickup |
pickup | unterwegs zur Abholung | ||
picked_up |
pickup | ✓ | beim Versender abgeholt | |
pickup_failed |
exception | Abholversuch fehlgeschlagen | ||
pickup_rescheduled |
exception | Abholung wird erneut versucht | ||
received |
inbound | in der Einrichtung empfangen | ||
arrival_scan |
inbound | Ankunftsscan am Node | ||
in_transit |
transit | in Bewegung | ||
package_outbound |
transit | Einrichtung verlassen | ||
removed_from_route |
exception | von der Route genommen | ||
route_cancelled |
exception | Route storniert | ||
out_for_delivery |
out_for_delivery | auf dem Fahrzeug | ||
delivered |
delivered | ✓ | ✓ | an Empfänger zugestellt |
delivery_failed |
exception | Zustellversuch fehlgeschlagen | ||
delivery_rescheduled |
exception | wird erneut versucht / erneut zugestellt | ||
return_to_sender |
return | ✓ | Rücksendung an Ursprung | |
rejected_by_recipient |
return | ✓ | Empfänger verweigert | |
cancelled |
return | ✓ | Bestellung storniert |
pre_shipment · preparing · pickup · inbound · in_custody · transit ·
out_for_delivery · delivered · exception · return. (preparing und in_custody
sind für Nicht-Paket-Profile reserviert — §8.)
source.type: self_delivery · third_party_delivery · carrier_label.
time_type: actual · estimated · scheduled (Standard actual).
Wenn sich ein Ereignis in der Phase exception befindet, SHOULD es einen incident_reason aus diesem
normalisierten Vokabular tragen:
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, unknownPhasen schreiten vorwärts; Ausnahmen unterbrechen und lösen sich wieder auf. Terminalzustände (delivered,
return_to_sender, rejected_by_recipient, cancelled) schließen das Objekt ab.
pre_shipment → preparing → pickup → inbound → in_custody → transit → out_for_delivery → delivered ✓
└──────────── exception ──────────┘
└──────────── return / cancelled ✓
Regeln:
occurred_at ordnen und MUST ein Eintreffen in falscher Reihenfolge tolerieren.Die vier Dimensionen von OTEP entsprechen Feld für Feld den wichtigsten Standards, sodass jeder eine Ausgabeprojektion eines OTEP-Ereignisses ist.
| 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 |
Werte pro Code (EPCIS CBV bizStep/disposition, ONE Record eventCode, UN/CEFACT-Code)
werden im maschinenlesbaren Codebook veröffentlicht. Über diese internationalen Standards hinaus kann eine OTEP-
Zeitleiste auch auf OpenTelemetry-Traces, OGC SensorThings-Beobachtungen und
gängige Handelsplattformen (AfterShip, Shopify, Amazon, Walmart, BigCommerce, Magento,
WooCommerce, Etsy) projiziert werden.
Vertrauensgrad: EPCIS CBV-Werte sind stabile Standard-URNs. ONE Record- und UN/CEFACT-Codewerte sind eine bestmögliche Annäherung für die letzte Meile und sollten vor der externen Verwendung gegen die offiziellen Codelisten validiert werden. „Delivered to consignee“ hat keinen exakten CBV-
bizStep— die nächstliegende Annäherung (receiving+received) wird verwendet, oder eine Erweiterungs-URN aus dem Benutzervokabular.
OTEP wird über eine kleine, nur lesende HTTP-Schnittstelle konsumiert; alle Endpunkte sind öffentlich.
| Verb | Path | Liefert |
|---|---|---|
| GET | /api/v1/otep/trackings/{tracking_number} |
die Zeitleiste für eine Sendungsnummer |
| GET | /api/v1/otep/trackings/{tracking_number}/events |
nur Ereignisse |
| POST | /api/v1/otep/trackings/batch |
viele Sendungsnummern in einem Aufruf |
| POST | /api/v1/otep/validate |
Konformitätsprüfung für eine übermittelte Zeitleiste (§9) |
Eine GraphQL-Abfrage, die dieselbe Zeitleiste bereitstellt, ist ebenfalls verfügbar.
Dieselbe Zeitleiste wird in die von Ihnen angeforderte Darstellung serialisiert, über einen ?format=-
Query-Parameter oder ein Accept-Profil:
| Anfrage | Darstellung |
|---|---|
?format=otep (default) |
native OTEP-Zeitleiste |
?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-Beobachtungen |
?format=aftership | shopify | amazon | walmart | bigcommerce | magento | woocommerce | etsy |
die Tracking-/Fulfillment-Form der Plattform |
Ereignisse, denen in einer Projektion kein Code zugewiesen werden kann, werden übersprungen und gezählt (niemals stillschweigend verworfen).
OTEP ist ein allgemeines Protokoll, kein Paketprotokoll. Die Protokollebene (Ereignisumschlag, Phasen-
Rückgrat, Zustandsautomat) ist universell; konkrete Statuscodes gehören zu einem Profil, das auf
der Zeitleiste über profile deklariert wird. Die Codes in §4 sind das parcel-Profil. Andere Domänen —
Umzug, Essenslieferung, Lagerung und darüber hinaus — fügen unter ihrem Profil eigene Codesätze hinzu,
mit dem Namensraum otep:<profile>:<code>, die jeweils auf dasselbe Phasen-Rückgrat abbilden. Das Hinzufügen eines Profils
ist eine Erweiterung, keine Protokolländerung.
Ein Produzent oder Konsument ist OTEP-konform, wenn jedes von ihm ausgegebene oder akzeptierte Ereignis diese Tabellen und Regeln erfüllt.
| Feld | Typ | Erf. | Einschränkungen |
|---|---|---|---|
otep_version |
string | MUST | semver, z. B. 0.1 |
profile |
string | MUST | ein registriertes Profil |
subject |
object | MUST | §9.2 |
current_status |
string|null | SHOULD | ein Statuscode (§4) |
current_phase |
string|null | SHOULD | MUST gleich der Phase von current_status sein, wenn beide vorhanden |
delivered |
boolean | SHOULD | true genau dann, wenn current_status = delivered |
events |
array | MUST | Ereignisobjekte (§9.3), nach occurred_at sortierbar |
subjectMindestens EINES von tracking_number / order_id / package_id MUST vorhanden sein.
| Feld | Typ | Einschränkungen |
|---|---|---|
tracking_number |
string | nicht leer |
order_id / package_id |
integer|null | |
external_tracking_number |
string|null | |
gs1_sscc |
string|null | 18 Ziffern |
piece_id |
string|null |
| # | Feld | Typ | Erf. | Einschränkungen |
|---|---|---|---|---|
| 1 | occurred_at |
string | MUST | ISO-8601 mit Offset |
| 2 | recorded_at |
string|null | SHOULD | ISO-8601 |
| 3 | time_type |
string | MAY (default actual) |
actual | estimated | scheduled |
| 4 | status_code |
string|null | MUST¹ | ein Code in §4.1 |
| 5 | phase |
string|null | SHOULD | MUST gleich der Phase von status_code sein |
| 6 | incident_reason |
string|null | SHOULD² | ein Grund in §4.4 |
| 7 | description |
string|null | MAY | menschenlesbar |
| 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? } |
¹ Ein codiertes Ereignis MUST einen Statuscode aus §4.1 tragen. Ein Rohscan, den Sie noch nicht klassifizieren können, MAY
status_code = null setzen, MUST aber den nativen Code in source.external_event_code bewahren und MUST
gezählt werden, niemals verworfen.
² Ein Ereignis der Phase exception SHOULD einen incident_reason tragen.
³ Ereignisse mit status_code ∈ {delivered, picked_up} SHOULD ein pod tragen.
locationname (string) · code (string) · gln (GS1 GLN, 13 Ziffern) · lat / lng (WGS-84) ·
country (ISO 3166-1 alpha-2). Alle optional.
source| Feld | Typ | Erf. | Einschränkungen |
|---|---|---|---|
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⁴ | Ihr Roh-Statuscode |
raw |
object|null | MAY | ursprüngliche Nutzlast |
⁴ MUST vorhanden sein, wenn status_code null ist, damit der native Code niemals verloren geht.
occurred_at MUST als ISO-8601 parsbar sein. Normalisieren Sie numerische Epochs und .NET /Date(ms)/
bei der Aufnahme; geben Sie diese Formen nicht aus.occurred_at sortieren, ein Eintreffen in falscher Reihenfolge tolerieren
und auf (subject, status_code, occurred_at) deduplizieren.phase, current_status, current_phase, delivered sind Projektionen — wenn
vorhanden, MUST sie mit der Ereignis-Zeitleiste konsistent sein.Überprüfen Sie Ihre Ausgabe, indem Sie eine Zeitleiste an POST /api/v1/otep/validate senden. Behandeln Sie alle
errors als blockierend; beheben Sie warnings. Ein maschinenlesbares JSON Schema und das vollständige
Codebook (jeder Statuscode, jede Phase und jede externe Zuordnung) werden für die Offline-Validierung veröffentlicht.
OTEP ist eine offene Spezifikation, frei für jede Partei zur Implementierung.
otep:<profile>:<code> adressiert, Phasen als
otep:phase:<name>. Sobald ein Bezeichner in einer veröffentlichten Version publiziert ist, ist seine Bedeutung unveränderlich.otep_version).x--Präfix verwenden
(otep:parcel:x-my_code), bis sie registriert sind.OTEP heißt andere Hersteller willkommen, ihren eigenen Tracking-Ereignis-Standard mitzubringen, damit OTEP damit interoperieren kann, in beide Richtungen:
source.external_event_code bewahrt; nicht zugeordnete Codes werden gezählt, niemals verworfen.Selbst wenn Sie OTEP nicht direkt übernehmen können, sind Sie willkommen, einen einzigen normalisierten otep_status
zu Ihren eigenen API-Antworten hinzuzufügen und Ihre Tracking-Ereignis-Codes für die Querverbindungs-Zuordnung zu teilen. Schlagen Sie
Zuordnungen gegen diese Spezifikation vor (statt zu forken), damit Implementierer konvergieren; neue Formate fügen sich in
dieselbe ?format=-Inhaltsaushandlung ein und brechen niemals einen bestehenden Konsumenten.