Operatives and dispatch
Operatives
GET /v1/operativeslists everyone, with app status, invite time and photo URL.POST /v1/operativeswith{ name, email?, phone?, trackingLicence?, sendInvite? }adds someone.sendInvite: trueemails the Tracker app invite (needsemail). Past the plan's active operatives you get402 operative_limit.PATCH /v1/operatives/{id}changes details, the background-tracking licence, oractive. Reactivating counts towards the limit again.POST /v1/operatives/{id}/invitesends or resends the invite, valid for 7 days.POST /v1/operatives/{id}/pairing-codereturns a code to type into the app, valid for 24 hours.POST /v1/operatives/{id}/sign-out-devicessigns them out of every phone.
Photos
curl -X PUT "$TRACKER/v1/operatives/opv_…/photo" \
-H "Authorization: Bearer $TRACKER_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @sam.jpg
Send the image itself: JPEG, PNG or WebP, under 1 MB. Anything else gets 415 photo_type, and larger files 413 photo_too_large. Send a square crop at about 480 px with location and camera details removed; the admin does this in the browser before uploading. DELETE the same path to remove it.
Customers only see the photo while that operative is on the way to them or has arrived.
Dispatch
GET /v1/dispatch?date=2026-11-02returns the day's board: a lane per active operative, plus the unassigned lane, each with its visits in order.PUT /v1/dispatch/2026-11-02with the lanes you're changing, each listing its visits in order:
{
"lanes": [
{ "operativeId": "opv_sam", "journeyIds": ["jrn_a", "jrn_b"] },
{ "operativeId": null, "journeyIds": ["jrn_c"] }
]
}
operativeId: null is the unassigned lane. A visit can only be in one lane (400 duplicate_visit). Lanes you leave out don't change. The response is the updated board, and customers see their operative's new name straight away.