Base URL: your workspace's API origin. All requests and responses are JSON unless noted. Times are ISO-8601 UTC instants.
Authentication
Mint an API key in Settings → API keys & webhooks and send
it as a bearer token. Keys are read-only by default; the
optional jobs:write scope additionally allows creating and
updating jobs — and nothing else.
curl -H "Authorization: Bearer sxk_..." https://api.visitstracker.com/visits
Reads (all keys)
| Endpoint | Returns |
|---|---|
GET /location/live | Latest position per tracked worker, quality-classified |
GET /location/trail?user_id&from&to | Fix stream + total/moving distance |
GET /location/nearest?lat&lng | Nearest workers by trusted position, with staleness |
GET /visits?user_id&from&to&customer_id | Derived + manual visits |
GET /visits/:id/photos | Photos and signatures on a visit |
GET /attendance?from | Punch sessions with site match + mock flags |
GET /jobs?date&user_id&status&days&limit&offset | Dispatch board. Windowed: without date it returns the last days (default 90) plus everything still ahead, limit 200 (max 500) — see the changelog |
GET /zones | Geofence zones |
GET /shifts?from&to | Planned rota |
GET /reports/mileage?days | Per-worker daily distance (+ device odometer delta) |
GET /reports/battery?days | Battery health per worker-day |
GET /reports/anomalies?date | Own-weekday anomaly findings |
GET /reports/rota?days | Planned vs actual with verdicts |
GET /reports/evidence?user_id&days | Weekly evidence PDF (binary) |
GET /reports/replay.svg?user_id&from&to | Animated route replay (SVG) |
GET /evidence-seals · /evidence-seals/:day/verify | Hash-chain seals; verify recomputes |
Writes (jobs:write scope)
| Endpoint | Body |
|---|---|
POST /jobs |
{customer_id, title, due_date, assigned_user_id?, window_start?, window_end?, recurs?, checklist?} |
PATCH /jobs/:id |
{status?, assigned_user_id?, notes?, recurs?, check_item?} |
Job status path: assigned → en_route → done | cancelled.
A job with an incomplete checklist refuses done; a derived
visit at the job's site auto-completes it.
Webhooks
Configured in Settings. Events: attendance.punch,
alert.fired (kinds: sos, mock_location, gone_dark,
job_missed_window), zone.transition. Payloads are signed:
X-SenseNxt-Signature: sha256=HMAC_SHA256(secret, rawBody)
X-SenseNxt-Event: alert.fired
{"event":"alert.fired","tenant_id":"…","at":"…",
"data":{"kind":"gone_dark","user":"Finn Field"}}
Verify by recomputing the HMAC over the exact raw body. Slack-format
hooks receive {"text": "…"} instead (unsigned-friendly, still
signed).
Public token surfaces
| Endpoint | What |
|---|---|
/public/share/:token(.json) | Expiring live-position page (ETA); 410 when expired/revoked |
/public/portal/:token(.json) | Standing per-customer service record (visits, photos, signatures) |
Errors
Non-2xx responses carry {"error": "machine_code", "message":
"human sentence"}. Notable codes: totp_required,
tenant_locked, week_approved,
checklist_incomplete, read_only,
rate_limited (with Retry-After).