> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bahnexpress.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracking and ETA

> Build a useful transport map and arrival experience.

Use the order resource to answer **when** Bahn expects to arrive. Use the tracking resource to answer **where** the vehicle is and whether the driver is approaching pickup.

These values are related, but they are not interchangeable. Do not calculate an ETA from the map position or turn a requested window into an ETA.

## Show the current ETA

The order contains separate ETA fields for pickup and delivery. Each ETA is an RFC 3339 timestamp, and `eta_updated_at` tells you when Bahn last updated it.

When an ETA is present, show it as the current estimate. If it is `null`, fall back to the agreed window and then the requested window. After pickup or delivery, show `actual_at` instead.

Bahn sends `com.bahn.order.eta_changed.v1` when an ETA appears, disappears, changes materially, or crosses a target boundary. Read the order after the event before you send a customer message or save the estimate.

## Read one tracking entry per vehicle

Call `GET /v2/orders/{order_id}/tracking`. The response contains one entry for each order item, so the primary vehicle and trade-in vehicle can have different tracking states and positions.

Use the item `id` to join each tracking entry to the matching vehicle in the order.

### Tracking state

| State         | Recommended UI                                         |
| ------------- | ------------------------------------------------------ |
| `not_started` | Show the pickup schedule without a live map.           |
| `active`      | Show the map when `position` is present.               |
| `paused`      | Explain that live location is temporarily unavailable. |
| `ended`       | Remove the live map and show the final order status.   |

The `phase` is `pickup` before collection and `transport` after collection.

### Position

When `position` is present, render `position.point` as one GeoJSON map marker. Use `accuracy_meters` for an accuracy circle and `heading_degrees` for marker direction when those values are available.

Show `observed_at` near the map. It is the time when Bahn observed the position, not the time when your application fetched it.

During a short location gap, the same position can remain available with its original `observed_at`. Keep the marker stable and let the timestamp show its age. When tracking is `paused`, `position` is `null` and the map should not show a live marker.

## Show pickup approach

`pickup_progress` helps the seller prepare for handover:

| Value        | Meaning                                                |
| ------------ | ------------------------------------------------------ |
| `on_the_way` | The driver is on the way to pickup.                    |
| `nearby`     | The estimated arrival at pickup is 30 minutes or less. |

Once progress becomes `nearby`, it remains nearby for that pickup approach. Use this field directly. A map position does not mean that the driver is nearby or has arrived.

`driver.name` can contain the known driver's full name during pickup approach. Show it when present and omit the driver element when it is `null`.

## Refresh the map efficiently

The tracking response includes an ETag. Send it in `If-None-Match` on the next request:

```http theme={null}
GET /v2/orders/{order_id}/tracking
If-None-Match: "trk_01k1a2b3c4d5e6f7g8h9j0k1m2"
```

A `304` response means that the tracking snapshot did not change. Poll every 30 to 60 seconds while the map is visible, and stop when the user leaves the page or tracking reaches `ended`.

Bahn does not send a webhook for each position. Tracking webhooks tell you about state or pickup-progress changes; polling keeps the marker current while the map is open.
