---
name: otep-development
description: Build an OTEP-conformant tracking integration — emit, consume, validate or project Open Tracking Event Protocol timelines. Use when implementing tracking-event interoperability against the OTEP spec, mapping a carrier/platform status vocabulary onto OTEP, projecting an OTEP timeline to another format, or verifying that a payload conforms.
---

# OTEP Development

OTEP (Open Tracking Event Protocol) is an open, vendor-neutral protocol that unifies tracking
events from any source — own-fleet, third-party couriers, carrier labels and beyond — into one
event timeline with one status vocabulary, and projects to GS1 EPCIS, IATA ONE Record,
UN/CEFACT, OpenTelemetry, OGC SensorThings and major commerce platforms.

**Authoritative references (read these first):**
- Specification (online): `/api/otep-guide/spec` — the §9 section is the normative conformance spec.
- JSON Schema: `/api/otep-guide/schema.json`
- Codebook (every status code, phase and external mapping): `/api/otep-guide/codebook` / `/api/otep-guide/codebook.json`
- Live validator: `POST /api/v1/otep/validate` with a timeline body.
- Reference data: `GET /api/v1/otep/trackings/{tracking_number}` (add `?format=` to project).

## The OTEP event (what you MUST produce)

A timeline = `{ otep_version, profile, subject, current_status?, events[] }`. Each event answers
**What / When / Where / Why**:

```jsonc
{
  "occurred_at": "2026-06-10T09:30:00-04:00",   // When — ISO-8601 with offset (REQUIRED)
  "status_code": "out_for_delivery",             // Why  — an OTEP status code
  "location": { "name": "Toronto Hub" },         // Where
  "source": { "type": "self_delivery" }          // provenance (REQUIRED, type enum)
}
```

The 20 status codes, their phases, terminal flags and external mappings are in the codebook
and §4 of the spec. `phase` is always derivable from `status_code` — never emit a phase that
disagrees.

## Conformance rules (spec §9 — do not skip)

1. `occurred_at` MUST be ISO-8601 with offset. Normalize epoch / `.NET /Date(ms)/` on ingest;
   never emit them.
2. `status_code` MUST be one of the OTEP codes, or `null` for a raw scan you can't classify —
   and then the native code MUST be preserved in `source.external_event_code`.
3. Never silently drop events. Unmapped events MUST be counted.
4. Consumers MUST sort by `occurred_at`, tolerate out-of-order arrival, and dedupe on
   (subject, status_code, occurred_at).
5. After a terminal status (delivered / return_to_sender / rejected_by_recipient / cancelled),
   do not emit further events except a documented RMA/re-open.
6. A status-only source MUST synthesize one event per observed change, not omit history.

**Always verify your output** by POSTing it to `/api/v1/otep/validate` (or validating against the
JSON Schema). Treat any `errors` as blocking; address `warnings`.

## Mapping a carrier/platform onto OTEP

Translate your raw status codes onto the OTEP status codes. Keep your raw code in
`source.external_event_code`. For exceptions, set `incident_reason` from the OTEP incident-reason
vocabulary (spec §4.4). Where your descriptions are readable, a keyword match on the description
covers most scans; add precise per-code overrides where your codes are terse.

## Projecting OTEP to another format

A projection is a pure transformation: an OTEP timeline in, your structure out, reusing the OTEP
status vocabulary and (where relevant) the external mappings in the codebook. Skip events with no
OTEP code and COUNT them — never drop silently. New formats plug into the same `?format=` content
negotiation and never break existing consumers.

## Bringing your own standard (vendor interoperability)

OTEP welcomes other vendors' tracking-event standards. Contribute either a projection
(OTEP → your format) or a status crosswalk (your codes → OTEP). Propose against the spec rather
than forking; use an `x-` prefix for experimental codes until registered. Even if you can't adopt
OTEP fully, you can add a single normalized `otep_status` to your own responses and share your
tracking event codes for crosswalk mapping.

## Checklist before shipping

- [ ] Every event has `occurred_at` (ISO-8601) + `source.type`.
- [ ] Every `status_code` is a valid OTEP code (or null with `external_event_code`).
- [ ] `phase` (if present) matches the status code's phase.
- [ ] Exceptions carry `incident_reason`; delivered/picked_up carry `pod`.
- [ ] No dropped events; unmapped are counted.
- [ ] Payload passes `POST /api/v1/otep/validate` with zero errors.
