Quickstart
Target: under 15 minutes from signup to a map on screen. In a web app, the JS SDK does steps 3 to 5 for you: a map in one line after a key. Below are the raw API calls, for any language.
1. Check the API is up
curl https://geo.dock.mn/health
# {"status":"ok"}2. Get a key
Get a free key at once on dock.mn/account with your e-mail (the key, the ten lines with it and a usage counter), from the playground button, or with curl, with or without {"email": ...}:
curl -X POST https://geo.dock.mn/v1/signup
# {"key":"dk_...","prefix":"dk_01234567","plan":"free","scopes":["door:read","geocode:read","route:read","tiles:read"],...}A free key reads only: 60 requests a minute (tiles have no per-minute limit); 1,000 requests, 25,000 tiles and 50 door misses a day. Three free keys per address per day. For higher caps or changes:read, ask dock for a partner key. A key looks like dk_ and 32 characters, is shown once, and carries scopes, for example door:read. Send it as ?access_token= or as Authorization: Bearer. Keep it on your server.
In an app: a token, not the key
A key in an app can be read out of it. Your server trades the key for an access token (OAuth2 client credentials) and gives the token to the app. A token works for an hour, only with the scopes you ask for, and is sent like a key.
curl -u ":$DOCK_KEY" -d grant_type=client_credentials -d "scope=door:read tiles:read" https://geo.dock.mn/v1/oauth/token
# {"access_token":"dt_...","token_type":"Bearer","expires_in":3600,"scope":"door:read tiles:read"}The key can also go in the body as client_secret, form encoded or JSON. Without scope the token gets all the key's scopes. Limits, quota and usage are the key's. Revoking the key ends its tokens within a minute. A token cannot buy another token. Errors follow RFC 6749: invalid_request, unsupported_grant_type, invalid_scope (400), invalid_client (401), each with error_description. On expired_token, fetch a new one.
| Status | error | Meaning |
|---|---|---|
| 401 | missing_key, invalid_key, revoked_key | No key, unknown key, or revoked key |
| 401 | invalid_token, expired_token | Unknown or expired access token. Get a new one from POST /v1/oauth/token |
| 403 | insufficient_scope | The key lacks the scope the route needs |
| 429 | rate_limited | Over the key's requests per minute (tiles have none). Wait Retry-After seconds |
| 429 | quota_exceeded | Your organization's daily quota is used up. Resets at 00:00 Ulaanbaatar time |
| 429 | miss_cap | Too many door lookups found no door today. Door lookups resume at 00:00 Ulaanbaatar time |
Every keyed response carries X-Rate-Limit-Limit, X-Rate-Limit-Remaining, X-Rate-Limit-Reset (unix seconds) and X-Rate-Limit-Interval (60).
Daily caps are per organization, shared by all its keys, and reset at 00:00 Ulaanbaatar time (16:00 UTC). Partner key defaults: 10,000 requests a day, 100,000 tiles a day (tiles are counted apart, with no per-minute limit), and 200 door lookups a day that find no door. Responses carry X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset (unix seconds). Ask us for higher caps.
See your usage with any key, whatever its scopes. This call is free and still answers when the quota is used up:
curl "https://geo.dock.mn/v1/usage?days=7&access_token=$DOCK_KEY"It returns your caps and, for each of the last days days (1 to 90, newest first), calls, tiles, door_misses and keys.
3. Check a building code
A code is 5 characters: digits and consonants. No vowels, no I, L, O, U. Cyrillic lookalike keys (К, М, Т, Х, Р, С, В, Н) are mapped automatically.
curl "https://geo.dock.mn/v1/door/7QSVX?access_token=$DOCK_KEY"Add a flat number after a space to get the flat if it is on record: 7QSVX%2017.
| Status | error | Meaning |
|---|---|---|
| 200 | The door. | |
| 400 | invalid_code | Not a building code. The reason is in message. |
| 404 | not_found | No door has this code. |
| 410 | retired_code | The door was retired. Codes are never reused. |
If a door is wrong, report it. A person reviews and fixes it; a report changes nothing on its own.
curl -X POST "https://geo.dock.mn/v1/door/report?access_token=$DOCK_KEY" \
-H 'Content-Type: application/json' \
-d '{"kind": "wrong_location", "code": "7QSVX", "point": [106.9377, 47.9227]}'kind: wrong_location (the real door's point, within 500 m), missing (no door where the code points), duplicate (duplicate_of: the other code), unmapped (a door with no code, point only). Answer 202. No phone numbers or names in note.
4. Search by name, or by point
Needs a key with geocode:read. Cyrillic and Latin both work, formal or informal ("Баянзүрх", "Bayanzurkh", "bayanzurh"). "3-р хороо" and "3r khoroo" are the same. One or two typos are fine.
curl -G "https://geo.dock.mn/v1/geocode" --data-urlencode "q=Сансар А 8" -d limit=5 -d access_token=$DOCK_KEY
curl "https://geo.dock.mn/v1/reverse?lat=47.9226&lon=106.9376&radius=50&access_token=$DOCK_KEY"results is best first (geocode) or nearest first (reverse). Each result has kind: door, business, building, district, khoroo, area, street or landmark. Only doors and registered businesses carry a code. Reverse also returns the district and khoroo covering the point; khoroo is null where no polygon exists yet. For suggestions while a person types, add -d proximity=106.9177,47.9186 (lon,lat: the courier, or the map centre): among names the typed text starts equally well, nearer places come first. The point is not logged.
Keep a result's id to fetch that one place again later. Doors and businesses add parents: the ids of the building, area, street, khoroo and district.
curl "https://geo.dock.mn/v1/address/$PLACE_ID?access_token=$DOCK_KEY"| Status | error | Meaning |
|---|---|---|
| 200 | Results, possibly empty. | |
| 400 | invalid_query | q missing or over 200 characters. |
| 400 | invalid_point, invalid_radius, invalid_limit | lat -90 to 90, lon -180 to 180, radius 1 to 1000 m, limit 1 to 20. |
| 400 | invalid_proximity | geocode: proximity is not lon,lat (lon -180 to 180, lat -90 to 90). |
| 400 | invalid_id | address: id is not a UUID. |
| 404 | not_found | address: no current place has this id, or it is inside a restricted site. |
| 410 | retired | address: the place was retired. Ids are never reused. |
To keep stored places in sync without a full pull, use GET /v1/changes (changes:read, a partner key). It returns id, kind, state and changed_at only, oldest first. state current: fetch it again with /v1/address/{id}; retired or hidden: drop it. While next is not null, get the next page with cursor=next. When done, poll again with since=until. until is the last search index refresh (nightly). First sync: since=1970-01-01T00:00:00Z.
curl "https://geo.dock.mn/v1/changes?since=2026-10-01T00:00:00Z&limit=1000&access_token=$DOCK_KEY"| Status | error | Meaning |
|---|---|---|
| 400 | invalid_since | since is not an ISO 8601 time with a zone (Z or +08:00). |
| 400 | invalid_limit, invalid_cursor | limit 1 to 1000; cursor is not next from a page. |
| 403 | insufficient_scope | The key lacks changes:read. |
Drivers on site confirm doors with POST /v1/confirm (confirm:write, a partner key). result is confirmed, wrong_location or missing. Name the door by code, or a door with no code by its id from the entrance tile layer. tap is where the driver tapped the door on the map; device is the phone GPS and must be within 40 m. driver is your own id for the driver, never a phone number; dock keeps only a hash. Map data does not change at once: confirmed counts toward the door confidence, the others are reviewed first. Within the hour, a confirmed tap within 5 m of the door raises it and its doors to confidence 2. Three such taps within 5 m of each other, from at least two drivers or orgs, raise it to 3. A newer wrong_location or missing holds the level until a newer confirmation. A confirmed tap over 5 m away also holds it, and is reviewed like wrong_location. Confirmations made before the point last moved no longer count.
curl -X POST "https://geo.dock.mn/v1/confirm?access_token=$DOCK_KEY" \
-H 'Content-Type: application/json' \
-d '{"result":"confirmed","code":"7QSVX","tap":[106.93763,47.92263],"device":[106.9377,47.9227],"driver":"drv-7f3a"}'| Status | error | Meaning |
|---|---|---|
| 200 | recorded, or already_recorded: the same driver said the same in the last 24 h. | |
| 400 | invalid_point, invalid_driver, invalid_observed_at | tap or device missing or outside Mongolia; driver looks like a phone or e-mail; observed_at over 7 days ago. |
| 404 | not_found | No door has the code, or no door has the id. Counts against the door miss cap. |
| 410 | retired | The door was retired. Reload the map. |
| 422 | implausible, too_far | GPS over 40 m from the tap; tap over 500 m from the door (report it as unmapped). |
5. Vector tiles
Needs a key with tiles:read. Sources: building (zoom 13 to 22), entrance (zoom 15 to 22). Nothing inside a restricted site is in any tile.
curl "https://geo.dock.mn/v1/tiles/building?access_token=$DOCK_KEY" # TileJSON
curl -o t.pbf "https://geo.dock.mn/v1/tiles/building/15/26116/11401?access_token=$DOCK_KEY"In MapLibre, use the TileJSON URL as a vector source. Its tile URLs carry your key. Each tile counts as one request against your key limit.
| Status | error | Meaning |
|---|---|---|
| 200 | The tile, application/x-protobuf. | |
| 204 | Empty tile. | |
| 400 | invalid_tile | z 0 to 22, x and y 0 to 2^z - 1. |
| 404 | not_found | No such source. |
Response rules
official_addressfirst,codeafter it.unofficial: trueunlessofficial_statusismatched. Your app must show "unofficial".- No owner and no phone number, ever. A door is a property of a place.