All topics
API for other systems Admins
Read the register from other systems and report where assets are.
mapzaa has an HTTP API, so a system that is not mapzaa can read the register and help keep it true. It is deliberately narrow. A key can read everything its organisation owns, and it can report where an asset is. Nothing else.
Reading is for anything that wants the register without scraping the dashboard: a report, a map of your own, a works system that needs to know what stands where. Reporting a position is for anything that knows better than the map where something has got to — a tracker on a vehicle, a sensor on a container, a phone in somebody's hand, a survey that corrected a hundred coordinates at once. Most of a register stands still, and a bench put somewhere once stays there; the API is here for the part of it that does not.
Getting a key
- Go to Settings › API keys and press Create key. Only administrators see the page.
- Name it after the system it belongs to, not after what it is. Works management system and Plough trackers, winter 25/26 are both things you can revoke a year later without having to guess.
- Choose what it may do. Read only lists and looks up assets. Read and report where assets are also moves pins. Give a key the narrower of the two whenever that will do.
- Press Create key in the dialog. The key is shown once, at the top of the page. Only a hash of it is stored, so there is nothing to look up afterwards — copy it into wherever it is going to live before you leave the page.
Give every system its own key. One that leaks, or one whose supplier you stop using, can then be revoked on its own without stopping anything else. Revoking takes effect immediately and cannot be undone, so when you are replacing a key: create the new one, move over, then revoke the old one.
Calling it
The base URL is https://api.mapzaa.com. Every request carries its key in the Authorization header and every answer is JSON. Start with the index, which proves the key works and says which organisation it reaches:
curl https://api.mapzaa.com/v1 \ -H "Authorization: Bearer mzk_…"
Anything that goes wrong comes back as {"error": {"code": "invalid_key", "message": "…"}}. Match on the code, which is stable; the message is English and is there for whoever is reading the log.
Reporting where something is
One asset at a time:
curl -X POST https://api.mapzaa.com/v1/assets/PLO-K3M9X2/location \
-H "Authorization: Bearer mzk_…" \
-H "Content-Type: application/json" \
-d '{"lat": 63.8258, "lng": 20.2630}'
A whole fleet in one request, which is what a fleet should do — one call per tick for all of it, rather than one call per vehicle per tick:
curl -X POST https://api.mapzaa.com/v1/locations \
-H "Authorization: Bearer mzk_…" \
-H "Content-Type: application/json" \
-d '{"updates": [
{"code": "PLO-K3M9X2", "lat": 63.8261, "lng": 20.2554},
{"code": "PLO-R7T2WD", "lat": 63.8190, "lng": 20.3011}
]}'
Entries are applied one at a time, so a single unknown code cannot throw away the nineteen good positions beside it. The answer says how many landed and names only the ones that did not: {"updated": 19, "failed": [{"asset": "PLO-XXXXXX", "error": "not_found"}]}.
Name an asset either by the code written on it or by the numeric id the API hands out. Only the second half of a code is matched, so PLO-K3M9X2 and K3M9X2 find the same thing — which means a code on a label keeps working after the asset is moved to another category and its prefix changes.
A report sets the coordinates and nothing else: not the category, not the status, and not the address, which is left exactly as you wrote it. An address is a fact about something that stands still, and re-deriving one for a moving vehicle every few seconds would produce a line that is wrong again before anybody reads it. Send "address" yourself if you do want it changed.
Around one report every ten seconds is a sensible interval: the map re-reads itself every fifteen, and a key may make 600 requests and report 6,000 positions a minute, a batch counting one per entry. That is a fleet of a thousand reporting every ten seconds through POST /v1/locations; past either limit the answer is 429 with the code rate_limited.
Seeing it move
Nothing has to be switched on. A position reported through the API lands in the same register the map draws, so the pin moves on its own, and the map refreshes every fifteen seconds for everyone who has it open. An asset that has reported in the last quarter of an hour is marked Live in the sidebar and on its pin, with the time it last said so — which is how you tell something that is reporting from something whose tracker has gone quiet.
Use the ordinary filters to watch one part of a fleet: a saved view of Category is Snow ploughs is a link you can leave open on a wall screen all night.
Reading the register
curl "https://api.mapzaa.com/v1/assets?category=Snow%20ploughs&moving=1" \ -H "Authorization: Bearer mzk_…"
Filters are combined with AND, and the answer carries a total so you know how far there is to page.
| Parameter | What it does |
|---|---|
status | active, maintenance or retired. |
category | A category id or its name. none asks for the assets that have no category. |
q | Matches the address, the description and the code. |
moving | 1 for the assets that have ever reported a position — the fleet, without having to know its codes. |
moved_since | Only what has reported since this moment, as an RFC 3339 timestamp. This is the one a poll should use. |
updated_since | Only what has changed at all since then, including edits made in the dashboard. |
limit, offset | Page size (1–1000, 100 by default) and where to start. |
There is also GET /v1/assets/{id or code} for a single asset, and GET /v1/categories for turning the names in a device's configuration into ids once rather than hard-coding a number somebody will later renumber.
Each asset comes back in the same shape — in the list as assets, beside total, limit and offset, and on its own as asset:
{
"id": 1042,
"ref": 57,
"code": "PLO-K3M9X2",
"category": {"id": 7, "name": "Snow ploughs", "color": "#2f6fed"},
"status": "active",
"location": {"lat": 63.8258, "lng": 20.263, "address": "Storgatan 12",
"movedAt": "2026-01-14T06:12:09Z"},
"description": "",
"fields": {"registration": "ABC 123"},
"createdAt": "2025-10-01T09:30:00Z",
"updatedAt": "2026-01-14T06:12:09Z"
}
category is null for an asset without one, and location.movedAt is null until something has reported a position. fields holds your own asset fields, by each field's key.
What a key deliberately cannot do
A key cannot create an asset or delete one, change a category, a status or a custom field, reach another organisation's register, sign anybody in, or see your people. It reports a position and it reads. That is the whole of it.
This is a choice, not an omission. A key lives somewhere you do not control — a box in a cab, a server somebody else runs, a config file that outlives whoever wrote it — and the smallest thing it can be allowed to do is the right thing for it to do. For the same reason, position reports are the one change mapzaa does not write to the audit trail: a plough reporting every ten seconds from November to April would bury every entry a person ever made. Creating and revoking a key is recorded there, and when a key was last used is shown on the key itself.
Error codes
| Code | Status | What it means |
|---|---|---|
missing_key | 401 | No Authorization header. |
invalid_key | 401 | The key is unknown or revoked. |
read_only | 403 | A read-only key tried to report a position. |
not_found | 404 | No asset with that id or code in your organisation. |
unknown_endpoint | 404 | No such path under /v1. |
bad_status, bad_category, bad_query, bad_timestamp, bad_limit, bad_offset | 400 | A filter value that cannot be used; the message says which and why. |
bad_body, bad_location, bad_address, batch_too_large | 400 | A report that cannot be used: not JSON, coordinates missing or out of range, an address over 300 characters, or more than 500 positions in one batch. |
rate_limited | 429 | Too many requests or positions in the last minute. Wait and send less often. |
internal | 500 | Something went wrong on our side. Try again. |
Something not covered here?
Email [email protected] — we usually reply the same day. If you are not set up yet, we will put your municipality on the map in about twenty minutes.