Data Model: Loads, Legs, Stops, Updates and Carriers
Updated today
A load in TrackFlo is built from five objects, each with its own upsert endpoint and permalink.
Carrier ──┐
│ assigned to
Load ─────┴─► Load leg ─┬─► Stop (pickup)
(order) (route) ├─► Stop (delivery)
└─► Load update, Load update, … (append-only)Object | Maps to (typical TMS) | Identified by |
|---|---|---|
Load | order, shipment, BOL |
|
Load leg | route, movement, trip |
|
Stop | stop, pickup / delivery |
|
Load update | check call, callin, GPS ping, status event |
|
Carrier | carrier, payee |
|
A load has one or more legs (
load_leg_sequence, 1-based). Most brokerage loads have exactly one.A leg has two or more stops (
stop_sequence, 1-based). The load and the leg both name their first pickup and final delivery (origin_stop_id,destination_stop_idand their permalinks).Load updates attach to a leg. TrackFlo derives the leg's latest location, ETA and comment from them; you do not write those "latest" fields yourself.
Timestamps come in pairs:
*_utcand*_local. Send both; TrackFlo shows local time to users and computes with UTC. The one exception isplanned_appointment_utc, which has no_localtwin. See Timestamps below.A hard appointment is a zero-width window: send the same time in
earliest_appointment_time_*andlatest_appointment_time_*. A stop with no appointment close time is not shown on the board.A leg marked
no_contact_load: truegets no automated outreach (SMS, voice, WhatsApp, carrier email, MacroPoint), and the board shows a no-contact icon so trackers don't call. Send it when your TMS flags the movement or its order as do-not-contact.
Appointments and ETAs
A stop carries two kinds of appointment, and a load update can say which stop its ETA is for.
The current appointment is
earliest_appointment_time_*/latest_appointment_time_*. Update it whenever the appointment is rescheduled.The original plan is
planned_appointment_utcon the stop. Keep sending the first planned time after a reschedule. TrackFlo compares the stop's ETA against it and raises Planned ETA Drift when the ETA runs later than the plan by more than the company's ETA drift threshold. That exception keeps firing after a reschedule, when ETA-after-appointment goes quiet. A stop withoutplanned_appointment_utcnever raises it. If your TMS plans by date only, send 12:00 in the stop's local time, converted to UTC.Which stop an ETA is for goes in
eta_stop_permalinkon the load update (the stop'snative_system_stop_permalink). Without it, TrackFlo assumes the next stop not yet arrived. With it, the ETA is stored on the named stop, and it updates the leg's ETA on the board only when that stop is the next one not yet arrived. So a TMS that sends an ETA for every stop can send them all.
Freight
A load's freight goes in commodity, one object per order:
{
"description": "Frozen poultry",
"pieces": 24,
"weight": 37000,
"weight_unit": "LB",
"items": [
{ "description": "Frozen poultry", "pieces": 20, "weight": 30000, "weight_unit": "LB" },
{ "description": "Dry ice", "pieces": 4, "weight": 7000, "weight_unit": "LB" }
]
}The top level is the order total, and it is what TrackFlo shows. If your TMS keeps freight per line only, sum the lines into the totals and send the lines as
items. If it keeps totals only, leaveitemsout.piecescounts pieces only. A quantity your TMS records in pallets, bags or other handling units is not a piece count, so leavepiecesout rather than sending it.0is valid for bulk freight.weightneeds aweight_unit. Send the unit your TMS uses (LB,KG, …). TrackFlo never assumes one. A weight with no unit is refused, so drop the weight if your TMS gives none.Numbers are JSON numbers. Send
37000, not"37000.0".The whole object is replaced on every upsert. Send the complete object each time;
nullclears it. Other keys are accepted and stored.
Timestamps
ISO-8601, second precision is enough.
Field ends in | Send | Example |
|---|---|---|
| the UTC wall-clock time, with |
|
| the facility's local wall-clock time, no offset |
|
TrackFlo stores timestamps without a zone. That has one consequence worth knowing: a non-zero offset is not converted. 2026-09-06T10:37:00-05:00 sent in a _utc field is stored as 10:37 and read as 10:37 UTC, five hours early. Convert to UTC before you send, and never put an offset on a _local value.
A load update whose load_update_utc_timestamp is more than 15 minutes in the future is stored but ignored when TrackFlo derives the leg's latest location, ETA and comment; junk future dates would otherwise block real updates until the clock caught up.