# Tracking Event Codes

Generated live at https://api.flexmesh.ca/api/documentation/tracking-events?lang=en from the deployed `tracking` dictionary table (which status codes exist and whether customers see them) plus TrackingEvent keys and the localized `tracking.*` description strings. Always current with this environment's database.

## Event side, not order type

This dictionary is **not** an order-type list. A single order can emit both pickup-side and delivery-side events — for example a delivery that also has a collection stop, peer-to-peer (two legs), multi-leg transfer, or warehouse hand-off flows. Instant Deliver uses a separate dictionary (not listed on this page). Each tracking event is stamped with a **side** (pickup-side or delivery-side) that picks the matching description and visibility row for that status. The numeric `status_id` and stable `key` match `tracking_event_status_id` / `tracking_event_key` on API and webhook payloads; branch on those fields (and the event side when wording matters), never on order type and never on the localized description text.

## Customer visibility (`tracking.visible`)

The public tracking page and the public tracking API only surface events whose dictionary row has `tracking.visible = 1`, joined on status code **and** event side (same side as stamped on the tracking event). Rows marked **No** below still exist in the system (webhooks, operation history, internal tools) but are hidden from the customer-facing timeline. Visibility is read live from the `tracking` table of this deployment.

## Delivery-side events

Statuses used when a tracking event is on the **delivery side** of a journey (drop-off / last-mile leg). Applies across pure delivery orders, the delivery leg of peer-to-peer, delivery-with-pickup, multi-leg, and similar flows — not only “delivery-only” orders. **Customer visible** is `tracking.visible` for that status on this side.

| status_id | key | otep | otep phase | customer visible | description |
|---|---|---|---|---|---|
| 100 | `information_submitted` | `information_submitted` | `pre_shipment` | Yes | Your shipment information has been submitted. |
| 110 | `booking_confirmed` | `booking_confirmed` | `pre_shipment` | No | Your shipment has been confirmed. |
| 120 | `awaiting_pickup` | `awaiting_pickup` | `pre_shipment` | No | Your parcel is awaiting pickup. |
| 200 | `pushed_successful` | — | — | No | Your order has been sent for route planning. |
| 201 | `pushed_failed` | — | — | No | We could not send your order for route planning. It will be retried or re-planned. |
| 300 | `received` | `received` | `inbound` | Yes | Your parcel has arrived safely at {warehouse_name}. |
| 301 | `arrival_scan` | `arrival_scan` | `inbound` | Yes | Your parcel has been collected and arrived at {warehouse_name}. |
| 400 | `planned` | `in_transit` | `transit` | No | Your parcel is loaded and ready for delivery. |
| 401 | `removed_from_route` | `removed_from_route` | `exception` | No | Your delivery will be rescheduled soon; please wait for the update. |
| 402 | `route_cancelled` | `route_cancelled` | `exception` | No | Your delivery status will be updated shortly. |
| 450 | `start_delivery` | `out_for_delivery` | `out_for_delivery` | Yes | Your parcel is currently out for delivery. |
| 500 | `deliver_success` | `delivered` | `delivered` | Yes | Your parcel has been delivered successfully. Thank you! |
| 501 | `need_rescheduled` | `delivery_rescheduled` | `exception` | Yes | Unfortunately, we were unable to deliver your parcel. |
| 502 | `redelivered_later` | `delivery_rescheduled` | `exception` | Yes | Unfortunately, we were unable to deliver your parcel. |
| 503 | `not_delivered` | `delivery_failed` | `exception` | Yes | We encountered an issue with your delivery. Please contact our support team. |
| 510 | `pickuped` | `picked_up` | `pickup` | Yes | The parcel was successfully picked up by our courier. |
| 800 | `package_outbound` | `package_outbound` | `transit` | Yes | Your parcel has departed from {warehouse_name}. |

## Pickup-side events

Statuses used when a tracking event is on the **pickup side** of a journey (collection leg). Applies across pure pickup orders, the pickup leg of peer-to-peer, delivery-with-pickup, multi-leg, and similar flows — not only “pickup-only” orders. **Customer visible** is `tracking.visible` for that status on this side.

| status_id | key | otep | otep phase | customer visible | description |
|---|---|---|---|---|---|
| 100 | `information_submitted` | `information_submitted` | `pre_shipment` | Yes | Your parcel request has been received successfully. |
| 110 | `booking_confirmed` | `booking_confirmed` | `pre_shipment` | No | Your pickup request has been confirmed. |
| 120 | `awaiting_pickup` | `awaiting_pickup` | `pre_shipment` | No | Your parcel is awaiting pickup. |
| 200 | `pushed_successful` | — | — | No | Your pickup order has been sent for route planning. |
| 201 | `pushed_failed` | — | — | No | We could not send your pickup order for route planning. It will be retried or re-planned. |
| 300 | `received` | `received` | `inbound` | Yes | We have received your parcel at our facility. |
| 301 | `arrival_scan` | `arrival_scan` | `inbound` | Yes | Order a-scan |
| 400 | `planned` | `in_transit` | `transit` | No | Your parcel is currently being processed. |
| 401 | `removed_from_route` | `removed_from_route` | `exception` | No | Your parcel pickup will be rescheduled soon. |
| 402 | `route_cancelled` | `route_cancelled` | `exception` | No | Your pickup status will be updated shortly. |
| 450 | `start_delivery` | `out_for_delivery` | `out_for_delivery` | Yes | A courier is on the way to collect your parcel. |
| 500 | `deliver_success` | `delivered` | `delivered` | Yes | Your parcel was successfully picked up. |
| 501 | `need_rescheduled` | `delivery_rescheduled` | `exception` | Yes | Parcel pickup has been rescheduled. We will inform you of the new schedule soon. |
| 502 | `redelivered_later` | `delivery_rescheduled` | `exception` | Yes | Parcel pickup has been rescheduled. We will inform you of the new schedule soon. |
| 503 | `not_delivered` | `delivery_failed` | `exception` | Yes | We encountered an issue with your parcel. Please contact our support team. |
| 510 | `pickuped` | `picked_up` | `pickup` | Yes | Your parcel was successfully picked up. |
| 800 | `package_outbound` | `package_outbound` | `transit` | Yes | Your parcel has departed from {warehouse_name}. |

The **otep** / **otep phase** columns are generated live from `OTEPStatusInput::FROM_TRACKING_EVENT` and `PHASES` — the same bridge the public tracking API stamps as `otep_status` on each event. A dash (—) means this internal status has no parcel-profile OTEP code (for example routing-push 200/201); the event is still stored and may still be customer-visible when `visible = 1`.

## Build your own tracking page

Use the **public tracking API** to power a branded page on your site or app — no login, no API token. The same endpoint backs the built-in tracking page and the GraphQL `trackingPublic` operation. Combine it with the status dictionary on this page (codes, visibility, OTEP) and the flow patterns below.

### 1. Call the public endpoint

One GET by tracking number. Look up works against Superroute tracking numbers, external tracking numbers, and some third-party numbers that were stored without a third-party provider id.

```
GET https://api.flexmesh.ca/api/v1/tracking/{tracking_number}
```

- No authentication — public, rate-limited with the rest of the API.
- Path parameter: the Superroute tracking number, external tracking number, or (when applicable) third-party tracking number without a third-party provider id.
- HTTP **200** with `"result": true` when found; **404** with `"result": false` when not found or the order is cancelled.

### 2. Choose the language of descriptions

Event `description` strings follow the request locale via the `Accept-Language` header (API locale middleware). Send a project language code such as `en`, `chs`, `cht`, `de`, … or a common alias such as `zh-CN` → simplified Chinese. If the header is missing or unknown, the default app locale is used.

```
Accept-Language: chs
Accept-Language: zh-CN
Accept-Language: de
```

### 3. Response fields you will render

Only the public surface is returned — sender/recipient full addresses are **not** included (those appear only on the authenticated internal tracking endpoint).

| field | meaning |
|---|---|
| `result` | `true` when a live order was found |
| `data` | Event timeline (newest first) |
| `data[].tracking_event_status_id` | Numeric code — match the dictionary on this page |
| `data[].otep_status` | Open tracking code when mapped (same bridge as the **otep** column) |
| `data[].description` | Localized customer text for this event |
| `data[].updated_at` / `timestamp` / `updated_at_localized` | When it happened (UTC string, unix, local clock) |
| `data[].location_*` / `operation_location` | Where it happened, when known |
| `data[].reason` | Public return/failure reason text when present |
| `deliveried` / `returntosender` / `rejectedbyrecipient` | Coarse final-state flags for header chips |
| `postcode` | Normalized delivery postcode (for POD gate on your side if you need it) |
| `proofs[]` | Signature / photo files (`url`, `full_url`, `signed_url`, `type`, `file_id`) |
| `is_third_party_tracking` / `third_party_info` | Present when a label/carrier timeline was merged in |

### 4. Recommended UI flow

1. Collect a tracking number from the visitor and call the public GET endpoint (optionally with `Accept-Language`).
2. If `result` is false or the HTTP status is 404, show a not-found message and stop.
3. Use `data[0]` (newest event) for the headline status: prefer `description` for human text; use `tracking_event_status_id` or `otep_status` for icons / progress logic.
4. Render the full `data` list as a timeline (already newest-first). Group or filter only if you must — do not invent missing steps.
5. If `proofs` is non-empty and the latest status is a success milestone (typically 500 or 510), offer a “view proof” control; use `signed_url` when you need an expiring link, or `full_url` for a permanent path on this host.

### 5. Progress steppers

Do **not** hard-code a single chain for every parcel. Use the dictionary and flow patterns on this page: e.g. many last-mile deliveries land on 100 → 300/301 → 450 → 500; dual-leg journeys insert pickup-side 460 → 510 first. Branch on `otep_status` (stable open codes) or `tracking_event_status_id`, and treat the newest event as authoritative when a later exception or correction appears.

### 6. Proof of delivery

`proofs[]` carries photo/signature metadata when available. Your page may still choose to gate display behind a postcode check (the built-in tracking page does so for privacy); the API returns a normalized `postcode` field for that purpose. Never put full recipient street addresses on a fully public page — this endpoint deliberately omits them.

### 7. Related public surfaces

Pick the surface that fits your stack. All of them are public (no token) unless noted in the API docs.

| surface | when to use |
|---|---|
| `GET https://api.flexmesh.ca/api/v1/tracking/{tracking_number}` | Default branded tracking page (this guide) |
| GraphQL `trackingPublic(trackingNumber: …)` at `https://api.flexmesh.ca/graphql` | Same payload when your stack is GraphQL-first |
| `GET https://api.flexmesh.ca/api/v1/otep/trackings/{tracking_number}` | Vendor-neutral OTEP timeline / EPCIS / ONE Record / UN-CEFACT projections |

### 8. Minimal examples

```bash
curl -sS -H "Accept-Language: en" \
  "https://api.flexmesh.ca/api/v1/tracking/SR1234567890"
```

```javascript
const res = await fetch(`https://api.flexmesh.ca/api/v1/tracking/${encodeURIComponent(tn)}`, {
  headers: { "Accept-Language": "chs" },
});
const body = await res.json();
if (!body.result) {
  // show "not found"
} else {
  const events = body.data || [];          // newest first
  const latest = events[0];
  const proofs = body.proofs || [];
  // render latest.description, map latest.tracking_event_status_id or otep_status for the stepper
}
```

## Common tracking flow patterns

These are **typical sequences**, not a hard state machine. Real timelines skip steps (e.g. no warehouse scan), insert exceptions, or interleave pickup-side and delivery-side events on the same order. Numbers are `tracking_event_status_id`; public timelines only show rows with `tracking.visible = 1` (see the tables above). Branch integrations on `status_id` / `key` and treat the newest event (highest id) as authoritative.

### Warehouse stock → last-mile delivery

Most common own-driver path: order created, package received into a facility, driver starts the delivery route, then success or a delivery exception. Internal planning codes such as 400/401/402 often exist in the dictionary but are **not** customer-visible.

```mermaid
flowchart LR
  s100["100 information_submitted"] --> s300["300 received / 301 arrival_scan"]
  s300 --> s450["450 start_delivery"]
  s450 --> s500["500 deliver_success"]
  s450 --> s501["501 need_rescheduled"]
  s450 --> s502["502 redelivered_later"]
  s450 --> s503["503 not_delivered"]
  s450 --> s700["700 rejected_by_recipient"]
  s501 --> s450
  s502 --> s450
```

### Collection / pickup-only leg

Driver goes out to collect, then records pickup success (POD-capable) or a pickup exception. Pure pickup orders stay on the pickup side; dual-leg flows continue into a delivery-side sequence after 510.

```mermaid
flowchart LR
  s100["100 information_submitted"] --> s460["460 start_pickup"]
  s460 --> s510["510 pickuped"]
  s460 --> s512["512 repickup_later"]
  s460 --> s513["513 not_pickuped"]
  s512 --> s460
```

### Dual-leg journeys (pickup then deliver)

Used when one journey has both a collection stop and a drop-off — for example delivery-with-pickup, peer-to-peer, and many multi-leg transfers. The timeline typically shows **pickup-side** milestones first (460 → 510), then **delivery-side** milestones (450 → 500). Event side, not order type, selects the description row.

```mermaid
flowchart LR
  s100["100 information_submitted"] --> pickup["Pickup-side: 460 → 510"]
  pickup --> delivery["Delivery-side: 450 → 500"]
  pickup --> pFail["Pickup exception: 512 / 513"]
  delivery --> dFail["Delivery exception: 501 / 502 / 503 / 700"]
```

### Third-party / carrier fulfilment

Carrier booking may emit 110/120; product policy keeps those **hidden** on the public timeline so third-party looks like own-driver. Shared visible milestones then match warehouse + last mile (300/301 → 800 outbound → 450 → 500). Terminal carrier cancel uses 403 (not 402, which is self-delivery re-plan only).

```mermaid
flowchart LR
  s100["100 information_submitted"] --> s110["110 / 120 hidden on public timeline"]
  s110 --> s300["300 / 301 warehouse"]
  s300 --> s800["800 package_outbound"]
  s800 --> s450["450 start_delivery"]
  s450 --> s500["500 deliver_success"]
  s450 --> sFail["501 / 503 / 600 / 700"]
```

### Instant Deliver (task dispatch)

Uses its own dictionary (not listed in the pickup/delivery-side tables above). Happy path: submitted → courier assigned → heading to pickup → picked up → out for delivery → delivered. Reassign (411) and assignment cancelled (412) can loop before the run starts; 501 covers reschedule after a failed attempt.

```mermaid
flowchart LR
  s100["100 information_submitted"] --> s410["410 rider_assigned"]
  s410 --> s411["411 reassigned optional"]
  s410 --> s412["412 assignment_cancelled"]
  s412 --> s410
  s410 --> s460["460 start_pickup"]
  s460 --> s510["510 pickuped"]
  s510 --> s450["450 start_delivery"]
  s450 --> s500["500 deliver_success"]
  s450 --> s501["501 need_rescheduled"]
```

### Exception & terminal codes (quick map)

| status_id | key | typical meaning |\n|---|---|---|\n| 501 | need_rescheduled | Delivery failed; needs a new plan |\n| 502 | redelivered_later | Retry on the same route / later attempt |\n| 503 | not_delivered | Delivery problem / failed attempt |\n| 512 | repickup_later | Pickup failed; try again later |\n| 513 | not_pickuped | Pickup problem |\n| 600 | return_to_sender | Returned to shipper |\n| 700 | rejected_by_recipient | Recipient refused |\n| 403 | cancelled | Terminal cancel (distinct from 402 route cancel) |\n| 402 | route_cancelled | Route cancelled / re-plan (usually not customer-visible) |

### Rules of thumb for integrators

1. Happy-path anchors customers care about: **100** created, **300/301** in facility, **450** out for delivery, **500** delivered; on collection legs **460** out for pickup and **510** picked up.
2. Do not assume a fixed full chain — optional warehouse scans, third-party hops (800), and dual-leg side switches are normal.
3. Exceptions (501–503, 512–513, 600, 700) can appear after an “out for …” code; the order may later re-enter 450/460 after re-plan.
4. Ignore non-visible dictionary rows on the public page; webhooks and internal tools may still receive them. Always prefer the latest event id when correcting or revoking a prior success.

Placeholders such as `{warehouse_name}` are substituted at runtime with the real warehouse or location name. New dictionary rows and visibility flips ship via migrations; the page re-reads the table on every request so it cannot go stale relative to this environment.