# Get tracking events

`GET /v2/tracking?tracking_reference=UASDRW78`

**Base URLs:**

- Sandbox: `https://api.sandbox.bobgo.co.za`
- Production: `https://api.bobgo.co.za`

**Authentication:** Bearer token required (see the Authentication page).

## Example request

```bash
curl -X GET 'https://api.sandbox.bobgo.co.za/v2/tracking?tracking_reference=UASDRW78' \
  -H "Authorization: Bearer <API_KEY>"
```

Returns the tracking history for a shipment: every checkpoint the courier has
reported, plus the shipment's current status and the courier's branding.

This is the pull side of tracking. If you want the events as they happen rather
than on a schedule, subscribe to the `tracking/updated`
[webhook](../11_Webhooks/README.md), which delivers the same object, so the same
parsing code handles both.

Unlike every other endpoint, this one is not tied to your account: it looks the
tracking reference up across Bob Go, the way the public tracking page does.

## Parameters

| Name | Type | Required | Notes |
| --- | --- | --- | --- |
| `tracking_reference` | string | yes | Bob Go's tracking reference for the shipment. |

There is no paging: every checkpoint comes back, and `limit` and `offset` are
ignored.

## Response

**The response is an array**, even when you asked for one tracking reference.
Take the first element.

```json
[
  {
    "id": "UASDRW78",
    "shipment_tracking_reference": "UASDRW78",
    "status": "delivered",
    "status_friendly": "Delivered",
    "courier_slug": "ie",
    "courier_name": "Internet Express",
    "courier_phone": "011 973 3566",
    "courier_logo": "https://ik.imagekit.io/.../ie_logo.png",
    "service_level": "ECO - Economy",
    "merchant_name": "Bogus Company",
    "merchant_logo": "https://ik.imagekit.io/.../logo.jpg",
    "show_branding": true,
    "type": "shipment",
    "order_number": "1908",
    "channel_order_number": "1908",
    "last_checkpoint_time": "2023-06-05 12:15:41+00:00",
    "shipment_time_created": "2023-06-02 09:36:05+00:00",
    "shipment_estimated_delivery_date_from": "",
    "shipment_estimated_delivery_date_to": "",
    "checkpoints": [
      {
        "status": "delivered",
        "status_friendly": "Delivered",
        "country": "",
        "zone": "",
        "city": "",
        "zip": "",
        "location": "",
        "message": "PIN entered successfully",
        "time": "2023-06-05 14:15:41+02:00",
        "time_string": "14:15"
      },
      {
        "status": "out-for-delivery",
        "status_friendly": "Out for delivery",
        "country": "",
        "zone": "",
        "city": "",
        "zip": "",
        "location": "JHB",
        "message": "",
        "time": "2023-06-05 06:19:18+02:00",
        "time_string": "06:19"
      }
    ],
    "shipment_movement_events": {
      "collected_time": "2023-06-02 12:37:35+00:00",
      "in_transit_time": "2023-06-04 09:53:10+00:00",
      "out_for_delivery_time": "2023-06-05 12:11:56+00:00",
      "ready_for_pickup_time": "",
      "delivered_time": "2023-06-05 12:15:41+00:00"
    }
  }
]
```

| Field | Notes |
| --- | --- |
| `status` | The shipment's current tracking status. Use this for display. |
| `status_friendly` | The same status, written for a human. |
| `checkpoints` | Every event the courier reported, most recently received first. That is usually, but not always, the order they happened in, so sort on `time` if the order matters. |
| `grouped_checkpoints` | The same events grouped by day and sorted by `time`, for rendering a timeline. |
| `shipment_movement_events` | The five milestone timestamps, extracted from the checkpoints, and empty until reached. Usually the only part you need. |
| `courier_*`, `merchant_*`, `show_branding` | For rendering a tracking page. `show_branding` reflects the merchant's setting. |
| `last_checkpoint_time` | When the courier last reported anything. |
| `shipment_estimated_delivery_date_from` / `_to` | The estimated window, as full timestamps. Empty once the shipment is delivered or otherwise finished, and while it waits at a pickup point. |

`id` and `custom_order_name` are still returned but are due to be
discontinued. Key on `shipment_tracking_reference` and `channel_order_number`
instead.

Three things to expect from real courier data:

- **Fields are often blank.** `country`, `zone`, `city` and `zip` on a
  checkpoint are always empty, and `location` and `message` depend entirely on
  what the courier sends. Render what is there rather than requiring it.
- **Times are strings, not RFC 3339, and not all in one zone.** Checkpoint
  `time` and the estimated delivery window are South African time
  (`2023-06-05 14:15:41+02:00`), and `time_string` is only the time of day.
  `last_checkpoint_time`, `shipment_time_created` and the
  `shipment_movement_events` are UTC (`+00:00`). Parse the offset rather than
  assuming one.
- **Errors have their own shape.** `message` holds a JSON string rather than
  text. Parse it, and the error is in `data.message` and `data.code`. A
  tracking reference that does not exist comes back as a `500`.

Pickup point shipments carry extra fields, `collection_location`,
`delivery_location`, `pickup_point_provider_name` and the
`show_pickup_point_details` flags, for rendering locker collection details.

> The `shipment` object, with the collection and delivery contact details, is
> **only** included in the `tracking/updated` webhook payload. It is not
> returned here.
