OTEP Download Home

OTEP — Open Tracking Event Protocol

Status: Draft v0.1 · An open, vendor-neutral specification · License: open (see §10)

OTEP is an open, vendor-neutral protocol for the lifecycle of any trackable subject. It defines one event model and one status vocabulary so that tracking events from any source — own-fleet delivery, third-party couriers, carrier labels and beyond — can be exchanged, understood and projected to international standards without re-integrating for every party.

This document is the specification: the structure, the fields, the status tables, the state machine, the external-standard mappings, and the conformance rules for building a compliant implementation. It is implementation-agnostic — it describes the protocol, not any particular vendor's internals. RFC-2119 keywords (MUST / SHOULD / MAY) are normative.

1. What OTEP solves

Tracking is fragmented: every carrier names fields and status codes differently, every delivery channel reports in its own shape, and connecting to global standards means re-integrating again and again. OTEP gives producers and consumers a single common language: a producer emits OTEP events once, and every OTEP consumer understands them and can project them to the standard it needs.

2. Design principles

  1. Event-sourced. The event timeline is the source of truth; "current status" is always a projection — the status of the latest event.
  2. What / When / Where / Why. Every event is shaped around these four dimensions — the same dimensions shared by GS1 EPCIS, IATA ONE Record and UN/CEFACT — so those standards are output projections of an OTEP event rather than parallel models.
  3. No-list sources are not a blocker. A source that exposes only a current status (no history) is handled by synthesizing one event per observed change (§5).
  4. Additive. OTEP is exposed alongside any existing tracking API; adopting it never requires a breaking change to what consumers already use.

3. The OTEP timeline

A timeline is an envelope carrying the tracked subject and an ordered list of events.

{
  "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 */ ]
}

The OTEP event

{
  // 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"
  }
}

Every field except subject, occurred_at, status_code and source is optional — partial sources fill what they have. Node/hub scans set location to the scanning facility. High-frequency GPS telemetry is NOT an OTEP event; an event is emitted on a status change or a node scan.

4. Vocabulary

4.1 Status codes

20 canonical lifecycle codes. phase is always derivable from the code.

code phase terminal POD meaning
information_submitted pre_shipment order info received
booking_confirmed pre_shipment carrier/booking acknowledged
awaiting_pickup pre_shipment ready for collection
out_for_pickup pickup en route to pick up
picked_up pickup collected from shipper
pickup_failed exception pickup attempt failed
pickup_rescheduled exception pickup will retry
received inbound received at facility
arrival_scan inbound arrival scan at node
in_transit transit moving
package_outbound transit departed facility
removed_from_route exception taken off route
route_cancelled exception route cancelled
out_for_delivery out_for_delivery on the vehicle
delivered delivered delivered to consignee
delivery_failed exception delivery attempt failed
delivery_rescheduled exception will retry / redeliver
return_to_sender return returning to origin
rejected_by_recipient return recipient refused
cancelled return order cancelled

4.2 Phases

pre_shipment · preparing · pickup · inbound · in_custody · transit · out_for_delivery · delivered · exception · return. (preparing and in_custody are reserved for non-parcel profiles — §8.)

4.3 Source types & time types

source.type: self_delivery · third_party_delivery · carrier_label. time_type: actual · estimated · scheduled (default actual).

4.4 Incident reasons

When an event is in the exception phase it SHOULD carry an incident_reason from this normalized vocabulary:

5. State machine & status-only sources

Phases advance forward; exceptions interrupt and resolve back. Terminal states (delivered, return_to_sender, rejected_by_recipient, cancelled) close the subject.

pre_shipment → preparing → pickup → inbound → in_custody → transit → out_for_delivery → delivered ✓
                                       └──────────── exception ──────────┘
                                       └──────────── return / cancelled ✓

Rules:

6. External-standard mappings

OTEP's four dimensions line up field-for-field with the major standards, so each is an output projection of an OTEP event.

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 values (EPCIS CBV bizStep/disposition, ONE Record eventCode, UN/CEFACT code) are published in the machine-readable codebook. Beyond these international standards, an OTEP timeline can also be projected to OpenTelemetry traces, OGC SensorThings observations, and common commerce platforms (AfterShip, Shopify, Amazon, Walmart, BigCommerce, Magento, WooCommerce, Etsy).

Confidence: EPCIS CBV values are stable standard URNs. ONE Record and UN/CEFACT code values are best-fit for last-mile and should be validated against the official code lists before external use. "Delivered to consignee" has no exact CBV bizStep — the closest fit (receiving + received) is used, or a user-vocabulary extension URN.

7. The OTEP API

OTEP is consumed over a small read-only HTTP surface; all endpoints are public.

Verb Path Returns
GET /api/v1/otep/trackings/{tracking_number} the timeline for a tracking number
GET /api/v1/otep/trackings/{tracking_number}/events events only
POST /api/v1/otep/trackings/batch many tracking numbers in one call
POST /api/v1/otep/validate conformance check for a posted timeline (§9)

A GraphQL query exposing the same timeline is also available.

Content negotiation

The same timeline is serialized into whichever representation you request, via a ?format= query parameter or an Accept profile:

Request Representation
?format=otep (default) native OTEP timeline
?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 observations
?format=aftership | shopify | amazon | walmart | bigcommerce | magento | woocommerce | etsy the platform's tracking/fulfillment shape

Events that cannot be assigned a code in a projection are skipped and counted (never silently dropped).

8. Domain profiles

OTEP is a general protocol, not a parcel one. The protocol layer (event envelope, phase spine, state machine) is universal; concrete status codes belong to a profile declared on the timeline via profile. The codes in §4 are the parcel profile. Other domains — moving, food delivery, storage and beyond — add their own code sets under their profile, namespaced otep:<profile>:<code>, each mapping up to the same phase spine. Adding a profile is an extension, not a protocol change.

9. Conformance specification (normative)

A producer or consumer is OTEP-conformant when every event it emits or accepts satisfies these tables and rules.

9.1 Timeline envelope

Field Type Req. Constraints
otep_version string MUST semver, e.g. 0.1
profile string MUST a registered profile
subject object MUST §9.2
current_status string|null SHOULD a status code (§4)
current_phase string|null SHOULD MUST equal the phase of current_status if both present
delivered boolean SHOULD true iff current_status = delivered
events array MUST event objects (§9.3), orderable by occurred_at

9.2 subject

At least ONE of tracking_number / order_id / package_id MUST be present.

Field Type Constraints
tracking_number string non-empty
order_id / package_id integer|null
external_tracking_number string|null
gs1_sscc string|null 18 digits
piece_id string|null

9.3 Event object

# Field Type Req. Constraints
1 occurred_at string MUST ISO-8601 with 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¹ a code in §4.1
5 phase string|null SHOULD MUST equal the phase of status_code
6 incident_reason string|null SHOULD² a reason in §4.4
7 description string|null MAY human-readable
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? }

¹ A coded event MUST carry a status code from §4.1. A raw scan you cannot yet classify MAY set status_code = null, but MUST preserve the native code in source.external_event_code and MUST be counted, never dropped. ² An exception-phase event SHOULD carry an incident_reason. ³ Events with status_code ∈ {delivered, picked_up} SHOULD carry a pod.

9.4 location

name (string) · code (string) · gln (GS1 GLN, 13 digits) · lat / lng (WGS-84) · country (ISO 3166-1 alpha-2). All optional.

9.5 source

Field Type Req. Constraints
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⁴ your raw status code
raw object|null MAY original payload

⁴ MUST be present when status_code is null, so the native code is never lost.

9.6 Rules

  1. Time. occurred_at MUST parse as ISO-8601. Normalize numeric epochs and .NET /Date(ms)/ on ingest; do not emit those forms.
  2. Ordering / dedup. Consumers MUST sort by occurred_at, tolerate out-of-order arrival, and dedupe on (subject, status_code, occurred_at).
  3. State machine. After a terminal status, do not emit further events except a documented RMA / re-open.
  4. No silent loss. Events that cannot be coded MUST be counted, never dropped.
  5. Derivation. phase, current_status, current_phase, delivered are projections — if present they MUST be consistent with the event timeline.

9.7 Conformance levels & validation

Verify your output by POSTing a timeline to POST /api/v1/otep/validate. Treat any errors as blocking; address warnings. A machine-readable JSON Schema and the complete codebook (every status code, phase and external mapping) are published for offline validation.

10. Openness & governance

OTEP is an open specification, free for any party to implement.

11. Vendor interoperability — bring your own standard

OTEP welcomes other vendors to bring their own tracking-event standard so OTEP can interoperate with it, in either direction:

Even if you cannot adopt OTEP directly, you are welcome to add a single normalized otep_status to your own API responses and to share your tracking event codes for crosswalk mapping. Propose mappings against this spec (rather than forking) so implementers converge; new formats plug into the same ?format= content negotiation and never break an existing consumer.