Base URL: https://location.evanrthomas.com (on the box: http://127.0.0.1:18810).
Live, typed reference: /api-docs.
Token (env var in secrets/location.env) |
Used by | How to send |
|---|---|---|
LOCATION_API_TOKEN |
/location/*, /subscriptions*, /events |
Authorization: Bearer <t> only |
LOCATION_OVERLAND_TOKEN |
POST /ingest/overland |
Bearer, or ?token=<t> |
LOCATION_OWNTRACKS_TOKEN |
POST /ingest/owntracks |
Bearer, ?token=<t>, or HTTP Basic password (any username) |
Wrong/missing token → 401. /healthz and /docs are public (no coordinates exposed).
cd ~/github.com/ethomas2/location-tracking && set -a && . secrets/location.env && set +a
B=https://location.evanrthomas.com
H="Authorization: Bearer $LOCATION_API_TOKEN"
curl -s $B/healthz
# {"ok":true,"fixes":1234,"last_fix_at":"2026-10-02T21:03:11Z"}
POST /ingest/overlandOverland's format: {"locations": [GeoJSON Feature, ...]}; each feature is a Point with coordinates: [lon, lat]
and properties.timestamp (ISO 8601), horizontal_accuracy, battery_level, motion, device_id. Always answers
{"result":"ok"} (even if some features were malformed and skipped). Resent batches are de-duplicated on
(device, timestamp).
curl -s "$B/ingest/overland?token=$LOCATION_OVERLAND_TOKEN" -H 'Content-Type: application/json' -d '{
"locations": [{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-122.0839, 37.3861]},
"properties": {"timestamp": "2026-10-02T14:00:00-07:00", "horizontal_accuracy": 12,
"battery_level": 0.8, "motion": ["walking"], "device_id": "iphone"}
}]
}'
# {"result":"ok"}
POST /ingest/owntracksOwnTracks HTTP mode (Settings → Connection → Mode HTTP, URL https://location.evanrthomas.com/ingest/owntracks,
Authentication on, password = LOCATION_OWNTRACKS_TOKEN). Only _type: "location" messages are stored; answers [].
Device name = last segment of topic, else the X-Limit-D header, else owntracks.
curl -s "$B/ingest/owntracks" -u "evan:$LOCATION_OWNTRACKS_TOKEN" -H 'Content-Type: application/json' \
-d '{"_type":"location","lat":37.3861,"lon":-122.0839,"acc":10,"tst":1790000000,"batt":80}'
A fix looks like:
{"id": 42, "lat": 37.3861, "lon": -122.0839, "accuracy_m": 12.0, "timestamp": "2026-10-02T21:00:00Z",
"battery": 0.8, "motion": "walking", "source": "overland", "device": "iphone",
"received_at": "2026-10-02T21:04:51Z"}
| Endpoint | Params | Returns |
|---|---|---|
GET /location/current |
device (optional) |
newest fix, or 404 if none |
GET /location/history |
since, until (ISO 8601; no offset = UTC), limit (1–10000, default 100), device |
{"count": n, "fixes": [...]} newest first |
DELETE /location/history |
device (required), since, until |
{"deleted": n} |
curl -s -H "$H" $B/location/current | jq
curl -s -H "$H" -G $B/location/history --data-urlencode since=2026-10-02T00:00:00-07:00 -d limit=500 | jq '.count'
Details and payloads: Subscriptions & callbacks.
| Endpoint | Notes |
|---|---|
POST /subscriptions |
body {rule, target, callback_url?, secret?, note?, once?} → 201 subscription |
GET /subscriptions |
active ones; ?include_inactive=true adds fired once subs |
GET /subscriptions/{id} |
one subscription (secret is never returned; has_secret is) |
DELETE /subscriptions/{id} |
204; also drops its undelivered events |
GET /events |
recent fired events + delivery status; limit, subscription_id |
curl -s -H "$H" -H 'Content-Type: application/json' $B/subscriptions -d '{
"rule": {"type": "geofence", "name": "Home", "lat": 37.3861, "lon": -122.0839, "radius_m": 150, "on": "both"},
"callback_url": "https://example.com/hook", "secret": "change-me"
}' | jq
curl -s -H "$H" $B/subscriptions | jq
curl -s -H "$H" -X DELETE $B/subscriptions/<id> -o /dev/null -w '%{http_code}\n'
curl -s -H "$H" "$B/events?limit=10" | jq '.[] | {type, status, attempts, last_error}'