Traducción por conveniencia — la versión en inglés es la autoritativa.
Estado: Borrador v0.1 · Una especificación abierta y neutral respecto al proveedor · Licencia: abierta (ver §10)
OTEP es un protocolo abierto y neutral respecto al proveedor para el ciclo de vida de cualquier sujeto rastreable. Define un único modelo de eventos y un único vocabulario de estados para que los eventos de seguimiento de cualquier fuente — entrega con flota propia, mensajeros de terceros, etiquetas de transportistas y más allá — puedan ser intercambiados, comprendidos y proyectados a estándares internacionales sin necesidad de reintegrar para cada parte.
Este documento es la especificación: la estructura, los campos, las tablas de estados, la máquina de estados, las correspondencias con estándares externos y las reglas de conformidad para construir una implementación conforme. Es independiente de la implementación: describe el protocolo, no los detalles internos de ningún proveedor en particular. Las palabras clave de RFC-2119 (MUST / SHOULD / MAY) son normativas.
El seguimiento está fragmentado: cada transportista nombra los campos y códigos de estado de forma diferente, cada canal de entrega reporta con su propia forma, y conectar con estándares globales significa reintegrar una y otra vez. OTEP brinda a productores y consumidores un único lenguaje común: un productor emite eventos OTEP una sola vez, y cada consumidor OTEP los comprende y puede proyectarlos al estándar que necesite.
Una línea temporal es un sobre que transporta el sujeto rastreado y una lista ordenada de eventos.
{
"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"
}
}
Todos los campos excepto subject, occurred_at, status_code y source son opcionales —
las fuentes parciales rellenan lo que tienen. Los escaneos de nodos/centros establecen location en la instalación de escaneo.
La telemetría GPS de alta frecuencia NO es un evento OTEP; un evento se emite ante un cambio de estado o
un escaneo de nodo.
20 códigos canónicos de ciclo de vida. phase siempre se puede derivar del código.
| code | phase | terminal | POD | significado |
|---|---|---|---|---|
information_submitted |
pre_shipment | información del pedido recibida | ||
booking_confirmed |
pre_shipment | reserva/transportista confirmado | ||
awaiting_pickup |
pre_shipment | listo para recolección | ||
out_for_pickup |
pickup | en ruta para recoger | ||
picked_up |
pickup | ✓ | recogido del remitente | |
pickup_failed |
exception | intento de recolección fallido | ||
pickup_rescheduled |
exception | la recolección se reintentará | ||
received |
inbound | recibido en instalación | ||
arrival_scan |
inbound | escaneo de llegada en nodo | ||
in_transit |
transit | en movimiento | ||
package_outbound |
transit | salió de la instalación | ||
removed_from_route |
exception | retirado de la ruta | ||
route_cancelled |
exception | ruta cancelada | ||
out_for_delivery |
out_for_delivery | en el vehículo | ||
delivered |
delivered | ✓ | ✓ | entregado al destinatario |
delivery_failed |
exception | intento de entrega fallido | ||
delivery_rescheduled |
exception | se reintentará / reentregará | ||
return_to_sender |
return | ✓ | regresando al origen | |
rejected_by_recipient |
return | ✓ | el destinatario rechazó | |
cancelled |
return | ✓ | pedido cancelado |
pre_shipment · preparing · pickup · inbound · in_custody · transit ·
out_for_delivery · delivered · exception · return. (preparing y in_custody
están reservados para perfiles que no son de paquetería — §8.)
source.type: self_delivery · third_party_delivery · carrier_label.
time_type: actual · estimated · scheduled (por defecto actual).
Cuando un evento está en la fase exception SHOULD llevar un incident_reason de este
vocabulario normalizado:
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, unknownLas fases avanzan hacia adelante; las excepciones interrumpen y se resuelven hacia atrás. Los estados terminales (delivered,
return_to_sender, rejected_by_recipient, cancelled) cierran el sujeto.
pre_shipment → preparing → pickup → inbound → in_custody → transit → out_for_delivery → delivered ✓
└──────────── exception ──────────┘
└──────────── return / cancelled ✓
Reglas:
occurred_at y MUST tolerar la llegada en desorden.Las cuatro dimensiones de OTEP se alinean campo por campo con los principales estándares, de modo que cada uno es una proyección de salida de un evento OTEP.
| OTEP | GS1 EPCIS 2.0 | IATA ONE Record | UN/CEFACT |
|---|---|---|---|
occurred_at (+offset) |
eventTime + eventTimeZoneOffset |
eventDate + eventTimeType=Actual |
Fecha/hora del evento |
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 |
Código de estado de transporte |
actor |
sourceList / extension | Actor / Party | Party |
incident_reason |
disposition / ErrorDeclaration | event remark | Código de razón de estado |
Los valores por código (CBV de EPCIS bizStep/disposition, eventCode de ONE Record, código de UN/CEFACT)
se publican en el libro de códigos legible por máquina. Más allá de estos estándares internacionales, una línea temporal
OTEP también puede proyectarse a trazas de OpenTelemetry, observaciones de OGC SensorThings y
plataformas de comercio comunes (AfterShip, Shopify, Amazon, Walmart, BigCommerce, Magento,
WooCommerce, Etsy).
Confianza: los valores CBV de EPCIS son URNs estándar estables. Los valores de código de ONE Record y UN/CEFACT son el mejor ajuste para la última milla y deben validarse contra las listas de códigos oficiales antes de su uso externo. "Entregado al destinatario" no tiene un bizStep CBV exacto — se usa el ajuste más cercano (
receiving+received), o una URN de extensión de vocabulario de usuario.
OTEP se consume a través de una pequeña superficie HTTP de solo lectura; todos los endpoints son públicos.
| Verbo | Ruta | Devuelve |
|---|---|---|
| GET | /api/v1/otep/trackings/{tracking_number} |
la línea temporal de un número de seguimiento |
| GET | /api/v1/otep/trackings/{tracking_number}/events |
solo eventos |
| POST | /api/v1/otep/trackings/batch |
muchos números de seguimiento en una sola llamada |
| POST | /api/v1/otep/validate |
verificación de conformidad para una línea temporal enviada (§9) |
También está disponible una consulta GraphQL que expone la misma línea temporal.
La misma línea temporal se serializa en cualquier representación que solicites, mediante un parámetro de consulta
?format= o un perfil Accept:
| Solicitud | Representación |
|---|---|
?format=otep (por defecto) |
línea temporal OTEP nativa |
?format=epcis |
GS1 EPCIS 2.0 (JSON-LD ObjectEvents) |
?format=onerecord |
IATA ONE Record (LogisticsEvents) |
?format=uncefact |
estado de transporte UN/CEFACT |
?format=otlp |
trazas de OpenTelemetry |
?format=sensorthings |
observaciones de OGC SensorThings |
?format=aftership | shopify | amazon | walmart | bigcommerce | magento | woocommerce | etsy |
la forma de seguimiento/cumplimiento de la plataforma |
Los eventos que no pueden asignarse a un código en una proyección se omiten y se contabilizan (nunca se descartan silenciosamente).
OTEP es un protocolo general, no uno de paquetería. La capa de protocolo (sobre del evento, columna de
fases, máquina de estados) es universal; los códigos de estado concretos pertenecen a un perfil declarado en
la línea temporal mediante profile. Los códigos de §4 son el perfil parcel. Otros dominios —
mudanzas, entrega de alimentos, almacenamiento y más allá — agregan sus propios conjuntos de códigos bajo su perfil,
con espacio de nombres otep:<profile>:<code>, cada uno mapeándose a la misma columna de fases. Agregar un perfil
es una extensión, no un cambio de protocolo.
Un productor o consumidor es conforme a OTEP cuando cada evento que emite o acepta satisface estas tablas y reglas.
| Campo | Tipo | Req. | Restricciones |
|---|---|---|---|
otep_version |
string | MUST | semver, p. ej. 0.1 |
profile |
string | MUST | un perfil registrado |
subject |
object | MUST | §9.2 |
current_status |
string|null | SHOULD | un código de estado (§4) |
current_phase |
string|null | SHOULD | MUST ser igual a la fase de current_status si ambos están presentes |
delivered |
boolean | SHOULD | true si y solo si current_status = delivered |
events |
array | MUST | objetos de evento (§9.3), ordenables por occurred_at |
subjectAl menos UNO de tracking_number / order_id / package_id MUST estar presente.
| Campo | Tipo | Restricciones |
|---|---|---|
tracking_number |
string | no vacío |
order_id / package_id |
integer|null | |
external_tracking_number |
string|null | |
gs1_sscc |
string|null | 18 dígitos |
piece_id |
string|null |
| # | Campo | Tipo | Req. | Restricciones |
|---|---|---|---|---|
| 1 | occurred_at |
string | MUST | ISO-8601 con offset |
| 2 | recorded_at |
string|null | SHOULD | ISO-8601 |
| 3 | time_type |
string | MAY (por defecto actual) |
actual | estimated | scheduled |
| 4 | status_code |
string|null | MUST¹ | un código en §4.1 |
| 5 | phase |
string|null | SHOULD | MUST ser igual a la fase de status_code |
| 6 | incident_reason |
string|null | SHOULD² | una razón en §4.4 |
| 7 | description |
string|null | MAY | legible por humanos |
| 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 evento codificado MUST llevar un código de estado de §4.1. Un escaneo en bruto que aún no puedes clasificar MAY establecer
status_code = null, pero MUST preservar el código nativo en source.external_event_code y MUST
contabilizarse, nunca descartarse.
² Un evento de fase exception SHOULD llevar un incident_reason.
³ Los eventos con status_code ∈ {delivered, picked_up} SHOULD llevar un pod.
locationname (string) · code (string) · gln (GS1 GLN, 13 dígitos) · lat / lng (WGS-84) ·
country (ISO 3166-1 alpha-2). Todos opcionales.
source| Campo | Tipo | Req. | Restricciones |
|---|---|---|---|
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⁴ | tu código de estado en bruto |
raw |
object|null | MAY | carga útil original |
⁴ MUST estar presente cuando status_code es null, para que el código nativo nunca se pierda.
occurred_at MUST analizarse como ISO-8601. Normaliza las épocas numéricas y .NET /Date(ms)/
en la ingesta; no emitas esas formas.occurred_at, tolerar la llegada en desorden,
y deduplicar en (subject, status_code, occurred_at).phase, current_status, current_phase, delivered son proyecciones — si
están presentes MUST ser consistentes con la línea temporal de eventos.Verifica tu salida enviando una línea temporal mediante POST a POST /api/v1/otep/validate. Trata cualquier
errors como bloqueante; atiende los warnings. Se publican un JSON Schema legible por máquina y el libro de
códigos completo (cada código de estado, fase y correspondencia externa) para validación sin conexión.
OTEP es una especificación abierta, libre para que cualquier parte la implemente.
otep:<profile>:<code>, las fases como
otep:phase:<name>. Una vez publicado en una versión liberada, el significado de un identificador es inmutable.otep_version).x-
(otep:parcel:x-my_code) hasta que se registren.OTEP da la bienvenida a otros proveedores para que traigan su propio estándar de eventos de seguimiento, de modo que OTEP pueda interoperar con él, en cualquier dirección:
source.external_event_code; los códigos no mapeados se contabilizan, nunca se descartan.Incluso si no puedes adoptar OTEP directamente, eres bienvenido a agregar un único otep_status normalizado
a las respuestas de tu propia API y a compartir tus códigos de eventos de seguimiento para la correspondencia de equivalencias. Propón
correspondencias contra esta especificación (en lugar de bifurcar) para que los implementadores converjan; los nuevos formatos se integran en
la misma negociación de contenido ?format= y nunca rompen un consumidor existente.