Release notes (written by hand until 2026-10-12)
0.85.0 (2026-10-14)
- New:
GET /v1/entrances/near?lat=&lon=gives the building entrances nearest a point, for a long-press on the map or "Энд байгаа entrance (internal)" from the user's position: nearest first, each asGET /v1/address/{id}/entranceslists it, plusbuildinganddistance_m. Generated entrances count and say so (sourcegenerated,confidence0). radius 50 m by default, up to 1000; limit 1 by default, up to 20. JS SDK 0.80.0:client.entrancesNear([lon, lat]). Nothing removed.
0.84.0 (2026-10-14)
- New:
GET /v1/address/{id}/entrancesgives a building's entrances in one call, for its card: numbered ones first, each with its level (matchedбүртгэлтэй,partialхэсэгчилсэн,noneалбан бус) and its code once a door is minted. An entrance without a door carries the building's official form from public sources, with its number. Generated entrances havesourcegeneratedandconfidence0 (таамаг). Demolished 410, restricted 404. JS SDK 0.79.0:client.entrances(id). Nothing removed.
0.83.0 (2026-10-13)
- New: dock Events.
POST /v1/eventsopens an event (name, area, start and end) under the organizer's contract (featureevents.live).POST /v1/events/{id}/positionstakes a participant's positions (dock.position.v1: format,consent: 'opt_in', a random participant token,[lon, lat, t]); no name, bib, phone or device is sent or stored. Points outside the area or time, or in a restricted site, are not stored.GET /v1/events/{id}/livegives the last point of each participant of the last 2 minutes, to 4 decimals, only with 5 or more live;GET /v1/events/{id}/heatgives 100 m cells with 5 or more participants (trace:read). 15 minutes after the end every point is erased and the cells stay. JS SDK 0.78.0:client.createEvent(),client.event(),client.startEventPositions(),client.stopEventPositions(),client.eventLive(),client.eventHeat(). Nothing removed.
0.82.0 (2026-10-13)
- New:
POST /v1/traffic/probestakes a fleet vehicle's last positions (dock.probe.v1: format, a random vehicle token made anew every 15 minutes,[lon, lat, t]points). Open to an org once its fleet contract is signed (featuretraffic.probes); no driver, phone, plate or device is sent or stored.GET /v1/traffic?bbox=gives the live speed per road for the last 15 minutes that ended, only roads that 5 or more vehicles passed (insights:read). JS SDK 0.77.0:client.startProbes(),client.stopProbes(),client.traffic(bbox). Nothing removed.
0.81.0 (2026-10-13)
- New:
POST /v1/track/{token}/door: the customer taps "энэ миний орох хаалга" on the tracking link; their word that the delivery's door is theirs, once per delivery, no photo, no name.GET /v1/track/{token}givescode(the door's code when the delivery is a door) anddoor_said. A delivery marked delivered with the driver's fix of the last 2 minutes within 8 m of the door's entrance is a stop for that door. - Changed: a door becomes trusted (level 3) by 3 stops within 8 m on 3 different days, or by 1 stop and the customer's word. Three taps on one day no longer make 3. A photo is never needed. Nothing removed.
0.80.0 (2026-10-13)
- New:
GET /v1/nearby?lat=&lon=&category=gives the places of one or more categories near a point, nearest first (category=pharmacy,category=restaurant,cafe; radius 1000 m, up to 10000; limit 10, up to 20). The categories are the map's place icons: hospital, school, kindergarten, stop, mall, market and more. Banks, fuel, pharmacies, restaurants, cafes and shops are businesses matched to the state registry only; until one matches, an empty list. JS SDK 0.76.0:client.nearby(point, categories). Nothing removed.
0.79.0 (2026-10-13)
- New: the
buildingtile andGET /v1/tilequerygive each buildingheight_source(where its height came from: a tag, levels, a type default, or estimated from footprint shape and size) andheight_confidence(3 tag, 2 levels, 1 default, 0 estimated). 0 and 1 are estimates, never measurements. Nothing removed.
0.78.0 (2026-10-13)
- Changed: tiles have no per-minute limit; they count against the organization's daily tile quota only. A map asks for many tiles at once, and a 429 left holes in it.
tile_rate_limitstays in responses and is not enforced. - New: the
entrancetile andGET /v1/tilequerygive each entrance itsbuilding_id. A newplacetile (from z11): districts, areas, public places, registered businesses, building and entrance numbers, each with a category and a rank. Nothing removed.
0.77.0 (2026-10-13)
- Changed:
POST /v1/orders/importreads addresses the way people type them. District letters and Latin names ("БГД", "хан уул", "HUD"), a phone number in the cell, entrance, floor and flat, and the words round a landmark ("behind the school", "next to") are not searched. With a district named, the first of the best five places in it is taken. A row found more than 30 km from the sheet's other addresses is searched again near them. Nothing removed.
JS SDK 0.75.0 (2026-10-12)
- New:
SearchBox, the search control on the map (map.addControl), with the picked place's card and mark (SEARCH_SOURCE). Nothing removed.
0.76.0 (2026-10-12)
- New: dispatch.
POST /v1/orders/importtakes deliveries pasted from a spreadsheet (tabs) or CSV; each address is found by dock's search, a district named in it read as where to look, and the place found checked to be in that district. One not found is kept as not found, never guessed. The customer's name and phone columns are not kept.GET /v1/orders,POST /v1/dispatch/plan(the same number of stops a driver, the shortest order by road, arrival times),PATCH /v1/orders/{id}(to another driver),GET /v1/fleet/positions,POST /v1/orders/{id}/trackand, with no key,GET /v1/track/{token}(dock.mn/t/...). The driver app:GET /v1/driver/jobs,POST /v1/driver/position,POST /v1/driver/jobs/{id}/status. Each company's apart (row-level security). Nothing removed.
0.75.0 (2026-10-12)
- New: many companies on one platform.
POST /v1/signuptakes the company'sname; with an e-mail, that person is the company's owner. The first key hasorg:admintoo.GET /v1/org: the company's name, one daily limit for all its keys, its people (fororg:adminkeys), its live keys.GET /v1/keys,POST /v1/keys(scopes picked from your own),DELETE /v1/keys/{prefix}.POST /v1/org/invites(admin, developer, driver) answers a one-use link;POST /v1/org/invites/acceptgives the invited person a key of their own. The database itself keeps each company's rows apart (row-level security). Nothing removed.
0.74.0 (2026-10-12)
- New:
legs[].stepsonGET /v1/route: turn-by-turn in Mongolian (mn) and English (en), with what to do (kind), where (location), metres and seconds to the next step, the street name and the Mongolian voice phrase ids (voice). The last step is at the road's end; when the entrance is over 15 m from the road it says to leave the car and walk. Nothing removed.
JS SDK 0.74.0 (2026-10-12)
- New:
Navigatorfollows a route fix by fix: says each turn ahead and at it, asks a new route after 3 fixes over 40 m off the line, and arrives within 20 m of the road's end.VoicePlayerplays the Mongolian phrases one after another, on by default.RouteStep,RouteLeg. Nothing removed.
0.73.0 (2026-10-12)
- New:
POST /v1/stylesalso takes{"name", "style"}, a whole MapLibre style (version 8), as dock Studio saves it: up to 512 KB, valid by the style spec, withaccess_tokentaken out of its URLs; a key left in it is refused (400invalid_style).GET /v1/styles/{id}serves it as saved, with your?access_token=added to source URLs on this API. In a list it shows as{"kind": "maplibre"}. Nothing removed.
0.72.0 (2026-10-12)
- New:
POST /v1/signuptakes an optional JSON body{"email": ...}, the account's contact (dock.mn/account). Kept with the organization, never logged, never in an answer or an export; not verified yet. A bad address is 400invalid_email. No body still works. Nothing removed.
JS SDK 0.73.0 (2026-10-12)
- New: the SDK as one script tag,
https://dock.mn/sdk/v1/dock.jsanddock.css, with maplibre-gl inside, onwindow.dock;dock.createMap(client, options). No npm step. - Changed: the default
baseUrlishttps://geo.dock.mn(was api.dock.mn, which is not dock's map API). Swift and Android defaults changed the same way. Nothing removed.
JS SDK 0.72.0 (2026-10-10)
- New:
sendConfirmPhoto(id, jpeg)(API 0.71.0), the photo of the orts sent afterconfirm()answered, with its id. Uint8Array, ArrayBuffer or Blob, sent as it is.ConfirmPhotoReceipt. Swift and Android: not yet. Nothing removed.
0.71.0 (2026-10-10)
- New:
POST /v1/confirm/{id}/photo, the photo of the orts a driver took with one of your confirmations (the walk, MVP.md). The body is one JPEG as the camera saved it,image/jpeg, up to 2 MB, 320 to 8000 px a side. Its metadata (GPS, the phone, the time) is removed before it is stored. One photo per confirmation: the same photo again isunchanged, another one is 409photo_exists. Only your own confirmations; one in a restricted site is 404. A person reviews it once the entrance has a door. It is never shown on a public page. Needsconfirm:write. Nothing removed.
Playground matching (2026-10-06)
- Playground: a Matching tab (
matching): trace (2 to 100 lon,lat, separated by;), times (optional, one Unix second per point), mode. The roads it went along are drawn on the map with the SDK's matchingSource and matchingLayers; each piece listed in minutes and km, each point with the piece it landed on and how many metres off. A click on the map adds a point. The JS SDK and curl snippets show the request sent. No SDK or API change.
Playground trips (2026-10-06)
- Playground: a Trips tab (
optimized-trips): stops (2 to 12 codes or lon,lat, separated by;), round trip, mode. The trip is drawn on the map with the SDK's optimizedTripSource and optimizedTripLayers, and the stops listed in the order to drive, each leg in minutes and km. A click on the map adds a stop. The JS SDK and curl snippets show the request sent. No SDK or API change.
Playground matrix (2026-10-06)
- Playground: a Matrix tab: from and to (1 to 10 codes or lon,lat a side, separated by
;), mode. The answer is shown as a table, a row per start and a column per end, each cell minutes and km. The JS SDK and curl snippets show the request sent. No SDK or API change.
Playground isochrone (2026-10-06)
- Playground: an Isochrone tab: from (code or lon,lat), minutes (
contours_minutes), mode. The answer is drawn on the map with the SDK's isochroneSource and isochroneLayers. The JS SDK and curl snippets show the request sent. No SDK or API change.
Playground tilequery (2026-10-06)
- Playground: a Tilequery tab: point, radius, limit, layers (
sources), geometry. The Tiles map shows on it, and a click asks about that point. The JS SDK and curl snippets show the request sent. No SDK or API change.
Playground near (2026-10-06)
- Playground: Search has a Near field (
proximity, lon,lat), and a Map centre button puts the Tiles map's centre in it. The JS SDK and curl snippets show the point sent, 7 decimals. Empty: not sent. No SDK or API change.
Playground kinds (2026-10-06)
- Playground: Search and Reverse have a box per kind (
types). The ticked kinds go togeocodeorreverse, and the JS SDK and curl snippets show the same list. None ticked: every kind, andtypesis not sent. Search has the 8 geocode kinds, Reverse the 4 it can keep. No SDK or API change.
JS SDK 0.71.0, Swift SDK 0.64.0, Android SDK 0.65.0 (2026-10-06)
- New:
typesonSuggesterin all three SDKs, and onattachSuggestin JS. Suggests only the kinds asked, astypesongeocode: a street picker field. A typed door code or a door link is looked up as a door only whentypeshas door. Empty or absent: every kind, as before. In JS another kind is refused with 400invalid_typesbefore any request. Nothing removed.
JS SDK 0.70.0, Swift SDK 0.63.0, Android SDK 0.64.0 (2026-10-06)
- New:
typesongeocode(API 0.70.0), in all three SDKs. Keeps only the kinds asked of door, business, building, district, khoroo, area, street and landmark, chosen before the limit: asking for streets gives the best streets. With no network the picked places answer with only those kinds too. Sent comma separated, a repeat once, in the order given. Empty or absent: every kind, as before. In JS another kind is refused before any request with 400invalid_types, as the API would;GEOCODE_TYPES. Swift and Kotlin take aGeocodeTypeenum. Nothing removed.
0.70.0 (2026-10-06)
- New:
typesonGET /v1/geocode, like thetypesfilter of Mapbox's forward geocoding andtypesonGET /v1/reverse. One or more ofdoor,business,building,district,khoroo,area,street,landmark, comma separated; a repeat is taken once. Only those kinds come back, up tolimitof them, in every layer: asking for streets gives the best streets, not the streets among the best places. In a "near" query the landmark is still found among every kind. A named door comes only whentypeshasdoor. Absent or empty: every kind, as before. Another kind or an empty item is 400invalid_types. Not in the SDKs yet. Nothing removed.
JS SDK 0.69.0, Swift SDK 0.62.0, Android SDK 0.63.0 (2026-10-06)
- New:
typesonreverse(API 0.69.0), in all three SDKs. Keeps only the kinds asked of door, business, building and landmark, chosen before the limit: asking for doors gives the nearest doors. Sent comma separated, a repeat once, in the order given. Empty or absent: every kind, as before. In JS another kind is refused before any request with 400invalid_types, as the API would;REVERSE_TYPES, typeReverseType. Swift and Kotlin take aReverseTypeenum. Nothing removed.
0.69.0 (2026-10-06)
- New:
typesonGET /v1/reverse, like thetypesfilter of Mapbox's reverse geocoding. One or more ofdoor,business,building,landmark, comma separated; a repeat is taken once. Only those kinds come back, up tolimitof them: asking for doors gives the nearest doors, not the doors among the nearest places. District and khoroo are the same. Absent or empty: every kind, as before. Another kind or an empty item is 400invalid_types. Not in the SDKs yet. Nothing removed.
JS SDK 0.68.0, Swift SDK 0.61.0, Android SDK 0.62.0 (2026-10-06)
- New:
tilequery(API 0.68.0), the same name in all three SDKs. The buildings and entrances at or near a point, nearest first, as the GeoJSON FeatureCollection the API sends: what a tap on the map is on.sourcesis building, entrance or both (the default);radius0 to 1000 metres, sent to the centimetre (TILEQUERY_RADIUS_MAX_M; SwifttilequeryRadiusMaxM);limit1 to 50 (TILEQUERY_LIMIT_MAX; SwifttilequeryLimitMax);geometrykeeps one type. An empty or repeated source is refused before any request with 404not_found, a point that is not a number with 400invalid_point, others with 400invalid_radiusorinvalid_limit, as the API would. TypesTilequery,TilequeryFeature,TilequeryProperties,TilequeryInfo(Swift and Kotlin alsoTilequeryPoint,TilequerySource,TilequeryGeometry). Scopetiles:read. Nothing removed. - The API reference now types the tilequery fields
name_mn,name_en,letter(string),height_m(number),levelsandconfidence(integer); they read as objects before. The answer itself is unchanged.
0.68.0 (2026-10-06)
- New:
GET /v1/tiles/{sources}/tilequery/{lon},{lat}, the tile features at or near a point, nearest first, like Mapbox's Tilequery API.sourcesisbuilding,entranceor both joined by a comma.radiusis 0 to 1000 metres, default 0: the building the point is in; an entrance needs a radius.limit1 to 50, default 5.geometryis one ofpolygon,linestring,point. A trailing.jsonis taken. The answer is a GeoJSON FeatureCollection: each feature is the part of it nearest the point (a Point), its tile fields andtilequery(distancein metres,geometry,layer). The same rows as the tiles: current, not demolished, nothing in a restricted site, no door codes. The 7th decimal of a point is a mark for your organization. Scopetiles:read; counts againstrate_limitanddaily_quota, not the tile budgets. Not in the SDKs yet. Nothing removed.
JS SDK 0.67.0 (2026-10-06)
- New:
routeSource(trip)androuteLayers({ source?, color?, width?, font? }), arouteanswer with its via ends on a MapLibre map in two lines. The source holds the line (mode,distance_m,duration_s), then one point per end where the line reaches it, in the order passed (numberfrom 1:from, each via,to;role;via, its place in the list sent, null forfromandto;gap_m), credited to the answer'ssource. An answer from an API before 0.67.0 has no via:fromandtoonly. The layers are the line and a ring per end, ids<source>-lineand<source>-end; withfont, a font stack on the style'sglyphs, also<source>-number, each end's number. The dock style has no glyphs: leavefontout on it.ROUTE_SOURCEis'dock-route'. JS only; no API change. Nothing removed.
JS SDK 0.66.0, Swift SDK 0.60.0, Android SDK 0.61.0 (2026-10-06)
- New:
viaonroute(API 0.67.0) in all three SDKs: up to 10 ends passed on the way, in order (ROUTE_VIA_MAX; SwiftrouteViaMax), each a door code or a point. JS{ via }, Swiftvia:afterto:, Androidvialast, so calls without it read as before. An empty list sends nothing. A long list, or a code holding;, is refused before any request with the API's 400invalid_via.Routegainslegs(TripLeg) andvia(RouteEnd), both optional: absent from an older API. Nothing removed.
0.67.0 (2026-10-06)
- New:
viaonGET /v1/route, like the waypoints between the first and last coordinates in Mapbox's Directions API. 1 to 10 ends joined by;, each a door code (to its entrance; needsdoor:readas well) orlon,lat, passed in the order sent. The line may turn back at a via end. Each is refused asfromandtoare, named by its place:via[1]: no door has the code ...,via[0] is inside closure .... Sent but empty, or more than 10: 400invalid_via. The answer gainslegs, one per hop (distance_m,duration_s; one withoutvia), andvia, the via ends in the order sent as route ends,snappedwhere the line passes them ([]without).measuredcovers the whole line. Nothing removed; a route withoutviais the same as before, plus those two fields.
JS SDK 0.65.0 (2026-10-06)
- New:
optimizedTripSource(round)andoptimizedTripLayers({ source?, color?, width?, font? }), anoptimizedTripsanswer on a MapLibre map in two lines. The source holds the trip line (trips_index,distance_m,duration_s), then one point per stop where the trip reaches it, in the order to drive (numberfrom 1,stop, its place in the list sent,gap_m), credited to the answer'ssource. The layers are the line and a ring per stop, ids<source>-lineand<source>-stop; withfont, a font stack on the style'sglyphs, also<source>-number, each stop's number. The dock style has no glyphs: leavefontout on it. An answer whosewaypoint_indexis not 0 to n - 1 once each is refused with RangeError.OPTIMIZED_TRIP_SOURCEis'dock-optimized-trip'. JS only; no API change. Nothing removed.
JS SDK 0.64.0, Swift SDK 0.59.0, Android SDK 0.60.0 (2026-10-06)
- New:
optimizedTrips(API 0.66.0), the same name in all three SDKs. 2 to 12 stops (OPTIMIZED_TRIPS_MAX; SwiftoptimizedTripsMax), each a door code or a point, put in the order with the least time on the road, with the trip in that order.roundtripis optional and sent only when given. The answer isOptimizedTrip:trips(Trip,TripLeg) andwaypoints(TripWaypoint, withwaypoint_index). A short or long list, or a code holding;, is refused before any request with 400invalid_coordinates. Scoperoute:read, anddoor:readfor a code. Nothing removed.
0.66.0 (2026-10-06)
- New:
GET /v1/optimized-trips, the order to visit stops in with the least time on the road, like Mapbox's Optimization API (v1).coordinatesis 2 to 12 ends joined by;, each a door code (to its entrance; needsdoor:readas well) orlon,lat. The trip starts at the first, as Mapbox'ssource=first.roundtrip,trueby default, goes back to it;falseends at the last sent, asdestination=last. The answer hastrips, one trip (distance_m,duration_sat the router's free-flow speeds, a GeoJSONgeometry, andlegs, one per hop), andwaypoints, the stops in the order sent, each a route end withwaypoint_index, its place in the trip. Codes, restricted sites, today's closures and door misses asGET /v1/matrix. A stop no road reaches is 422no_route. Scoperoute:read. Not in the SDKs yet. Nothing removed.
JS SDK 0.63.0 (2026-10-05)
- New:
matchingSource(trip)andmatchingLayers({ source?, color?, width? }), amatchinganswer on a MapLibre map in two lines. The source holds one line per matched piece (matchings_index,distance_m,duration_s), then one point per tracepoint on a road (point, its place in the trace sent,matchings_index,gap_m), credited to the answer'ssource; null tracepoints are left out. The layers are a line and a dot per point, ids<source>-lineand<source>-point.MATCHING_SOURCEis'dock-matching'. JS only; no API change. Nothing removed.
JS SDK 0.62.0, Swift SDK 0.58.0, Android SDK 0.59.0 (2026-10-05)
- New:
matching(API 0.65.0), the same name in all three SDKs. A GPS trace of 2 to 100 points (MATCHING_MAX; SwiftmatchingMax), with optional whole Unix seconds per point, matched to the roads:matchings, one line per unbroken piece, andtracepoints, one per point, null where no road was found. Fewer or more points, and timestamps not one per point, not whole seconds or going back, are refused before any request with 400invalid_coordinatesorinvalid_timestamps. In Swift and Kotlin a tracepoint is aMatchTracepoint, apart from the trip recorder'sTracePoint. Scoperoute:read. Nothing removed. - Fixed in openapi.json:
MatchDto.tracepointsis a list that is never null, of items that may be null. It said the list itself could be null. The answer did not change.
0.65.0 (2026-10-05)
- New:
GET /v1/matching, a GPS trace matched to the road network, like Mapbox's Map Matching API.coordinatesis 2 to 100 points aslon,latjoined by;, in the order recorded.timestamps, optional, is one Unix time in whole seconds per point, never going back; with them two far points seconds apart are a break, not a fast road. The answer hasmatchings, one per unbroken piece (distance_m,duration_sat the router's free-flow speeds, a GeoJSONgeometry), andtracepoints, one per point (matchings_index,location,gap_m), null where no road was found. A point inside a restricted site is left out and null; fewer than 2 left is 422not_accepted. No road near the trace is 422no_match. Closures are not avoided: a trace is where someone went. The trace is neither stored nor logged. Scoperoute:read. Not in the SDKs yet. Nothing removed.
JS SDK 0.61.0 (2026-10-05)
- New:
isochroneSource(zone)andisochroneLayers({ source?, color?, opacity? }), anisochroneanswer on a MapLibre map in two lines. The source holds the zones largest first, credited to the answer'ssource; the layers are a fill and an outline, ids<source>-filland<source>-line, so each nearer zone lies darker on top.ISOCHRONE_SOURCEis'dock-isochrone'. JS only; no API change. Nothing removed.
JS SDK 0.60.0, Swift SDK 0.57.0, Android SDK 0.58.0 (2026-10-05)
- New:
isochrone(API 0.64.0), the same name in all three SDKs. The area reached from one end (a door code or a point, as inroute) within each of 1 to 4 times, as a GeoJSON FeatureCollection, the largest first. Times are whole minutes from 1 to 60, increasing (ISOCHRONE_CONTOURS_MAX,ISOCHRONE_MINUTES_MAX; SwiftisochroneContoursMax,isochroneMinutesMax); others are refused before any request with 400invalid_contours. In Swift and KotlinIsochroneGeometryis a Polygon or a MultiPolygon, andpolygonsgives every piece. Scoperoute:read, anddoor:readfor a code. Nothing removed.
0.64.0 (2026-10-05)
- New:
GET /v1/isochrone, the area reached from one end within some minutes, like Mapbox's Isochrone API.fromis one end as inGET /v1/route: a door code (from its entrance; needsdoor:readtoo) orlon,lat.contours_minutesis 1 to 4 whole minutes from 1 to 60, increasing, joined by commas (5,10,15). The answer is a GeoJSON FeatureCollection: one polygon per contour, the largest first,properties.contourin minutes. Add it to a map as a source as it is.fromcomes back as a route end (point,snapped,gap_m,door). Restricted sites and today's closures as for a route. Free-flow speeds. Scoperoute:read. Not in the SDKs yet. Nothing removed.
JS SDK 0.59.0, Swift SDK 0.56.0, Android SDK 0.57.0 (2026-10-05)
- New:
matrix(API 0.63.0), the same name in all three SDKs. Free-flow time and distance from every end infromto every end intoin one call, each end a door code or a point as inroute;null(Swiftnil) where no path joins them. Up toMATRIX_MAX(SwiftmatrixMax), 10, ends a side. An empty or longer list, or a code holding;, is refused before any request with 400invalid_fromorinvalid_to. Scoperoute:read, anddoor:readfor a code. Nothing removed.
0.63.0 (2026-10-05)
- New:
GET /v1/matrix, travel time and distance from everyfromto everytoin one call, like Mapbox's Matrix API.fromandtoare each 1 to 10 ends joined by;, each as inGET /v1/route: a door code (to its entrance; needsdoor:readtoo) orlon,lat.durations_s[i][j]anddistances_m[i][j]go fromfrom[i]toto[j], at free-flow speeds;nullwhere no path joins them.fromandtolist each end as a route end (point,snapped,gap_m,door). Restricted sites and today's closures as for a route. Every code that is no door counts as a door miss, then the first is refused by its place (to[1]: no door has the code ...). One call for the whole matrix. Scoperoute:read. Not in the SDKs yet. Nothing removed.
JS SDK 0.58.0, Swift SDK 0.55.0, Android SDK 0.56.0 (2026-10-05)
- New:
usagegivesendpoints(API 0.62.0), calls and door misses per route. Optional:undefinedin JS,nilin Swift,nullin Android from an API before 0.62.0. Nothing removed.
0.62.0 (2026-10-05)
GET /v1/usageaddsendpoints: every route your org's keys called, over the same days asdays. Each row hasendpoint(method and route pattern, likeGET /v1/door/:code; never a code, a name or a search text),callsandmisses(lookups that found no door). Most calls first, ties A to Z. The tiles route counts tiles; thecallsof the other routes add up to thecallsof the days. Routes with no calls are left out. The usage page shows them in a table. Nothing removed.
JS SDK 0.57.0, Swift SDK 0.54.0, Android SDK 0.55.0 (2026-10-05)
- New: door photo (API 0.60.0, 0.61.0), the same names in all three SDKs.
sendDoorPhotosends one JPEG of the gate of the door your name points at, for review;doorPhotoreads back the photo a person accepted, as bytes and itssha256. Pass thesha256you hold and an unchanged photo comes back with no bytes (304). Scopenames:write, featurehotel.notes, else 403feature_off. The name is refused before any request as for door names. Nothing removed.
0.61.0 (2026-10-05)
- New:
GET /v1/door-names/{name}/photo. The org that holds an unexpired door name reads back the gate photo a person accepted, for its app to show its own guests. The newest accepted photo is served; a waiting or refused one, or another org's, never is. The body is the JPEG as stored,Content-Type: image/jpeg, theETagis thesha256the send answered;If-None-Matchwith it is 304.Cache-Control: private, no-cache. Do not put it on a public page. A name your org does not hold, whose door was retired or lies in a restricted site, or with no accepted photo, is 404. Scopenames:write, featurehotel.notes. No SDK change. Nothing removed.
0.60.0 (2026-10-05)
- New:
POST /v1/door-names/{name}/photo. The org that holds an unexpired door name sends one photo of its entrance or gate: the body is a JPEG file,Content-Type: image/jpeg, up to 2 MB, 320 to 8000 px on a side. Its metadata is removed before it is stored: GPS, phone model, time, XMP, comments, thumbnails, bytes after the image. The orientation and the colour profile stay. The answer hassha256,bytes,width,heightandstatus(queued,updated,unchanged). A person reviews the photo; no call serves photos yet. Sending again replaces the waiting photo, the old one kept in history. Scopenames:write, featurehotel.notes. Not a JPEG is 400not_jpeg, broken isinvalid_photo, too smallphoto_too_small, too bigphoto_too_big, over 2 MB 413too_large. A name your org does not hold, or whose door was retired, is 404. No SDK change. Nothing removed.
JS SDK 0.56.0, Swift SDK 0.53.0, Android SDK 0.54.0 (2026-10-05)
- New: door notes (API 0.58.0, 0.59.0), the same names in all three SDKs.
writeDoorNoteswrites how guests find the entrance of the door your name points at, in mn, en or both, for review;doorNotesreads your own notes for that door, eachwaiting,acceptedorrefused. Scopenames:write, featurehotel.notes, else 403feature_off. The name is refused before any request as for door names: 400invalid_nameorpersonal_data. A phone number or an e-mail in a note comes back as the API's 400personal_data. Nothing removed.
0.59.0 (2026-10-05)
- New:
GET /v1/door-names/{name}/notes. The org that holds a door name reads the notes it wrote for that door:stateiswaiting,acceptedorrefused, withreceived_atandreviewed_at, the time a person reviewed it. The waiting note first, then the newest review, at most 20. Another org's notes are never listed. Scopenames:write, featurehotel.notes. As when writing: a name your org does not hold, or whose door was retired, is 404. No SDK change. Nothing removed.
0.58.0 (2026-10-05)
- New:
POST /v1/door-names/{name}/notes. The org that holds an unexpired door name writes how guests find its entrance:instructions_mn,instructions_enor both, up to 500 characters each. The note waits for a person to review it;GET /v1/door/{code}keeps serving the notes the map holds until then. Writing again replaces your waiting text (updated,unchanged); history keeps the old one. Scopenames:write, featurehotel.notes, else 403feature_off. A phone number or an e-mail is 400personal_data. A name your org does not hold, or whose door was retired, is 404. No gate photo yet. No SDK change. Nothing removed.
0.57.0 (2026-10-05)
- New:
GET /v1/corridors/{name}giveslabel_mnandconfidence_note_mn, and each fact linetext_mn, the same line in Mongolian with the same numbers. Null when there is none: unknown, not a translation. Lane D writestext_mnonly when its numbers equal the English line; the other lines are null until lane D has their words. Compare rows inGET /v1/corridorsalready carrylabel_mn. A Mongolian field that is not text or null makes the file not whole: 503unavailable. No SDK change. Nothing removed.
Change feed clock (2026-10-05)
GET /v1/changes:untilis now one second before the last index refresh, not the refresh itself. A write made just after a refresh could carry a time up to a fraction of a second before it, when the server clock stepped back, and a partner polling fromuntilnever saw it. Now it shows in the next window. Every id listed can still be fetched. No API change, still 0.56.0.
JS SDK 0.55.0, Swift SDK 0.52.0, Android SDK 0.53.0 (2026-10-04)
- New: door names (API 0.52.0, 0.53.0), the same names in all three SDKs:
registerDoorName,doorNames,doorByName,doorNameTransferCode,cancelDoorNameTransfer,claimDoorName. 403feature_offuntil thedoor_namesfeature is open. A name with no letter, a phone number or an e-mail is refused with 400invalid_nameorpersonal_databefore any request: the name travels in the URL. In Swift and Android a claim answers aDoorNameReceiptwithstatustransferred. Nothing removed.
JS SDK 0.54.0, Swift SDK 0.51.0, Android SDK 0.52.0 (2026-10-04)
- New:
usagegivesfeatures(API 0.51.0), the features open to your org, A to Z. Optional:undefinedin JS,nilin Swift,nullin Android from an API before 0.51.0. Nothing removed.
Swift SDK 0.50.0, Android SDK 0.51.0 (2026-10-04)
- New: door QRs, offline, as in JS SDK 0.53.0.
doorQr(_:unit:)(KotlindoorQr(text, unit)) gives the modules of thedoorLinklink;doorQrSvgthe SVG text. SwiftdoorQrImage(_:unit:px:margin:)draws aCGImage; KotlindoorQrPixels(text, unit, px, margin)gives ARGB pixels for aBitmap. ISO/IEC 18004, error correction level M, the same modules in all three SDKs (sdk/testdata/door-qr.json).nilornullwhen the text cannot be a code. No library needed. No API or JS change. Nothing removed.
JS SDK 0.53.0 (2026-10-04)
- New: door QRs, offline.
doorQrSvg(text, unit, { px, margin })draws the door's QR as an SVG, thedoorLinklink inside;doorQr(text, unit)gives its modules (modules[y][x],size,version). ISO/IEC 18004, error correction level M.nullwhen the text cannot be a code. No library needed. No API, Swift or Android change. Nothing removed.
JS SDK 0.52.0, Swift SDK 0.49.0, Android SDK 0.50.0 (2026-10-04)
- New: door links, offline.
doorLink(text, unit)(SwiftdoorLink(_:unit:)) makes the link to share a door,https://map.dock.mn/d/7QSVX?u=17, the unit percent-encoded;null(Swiftnil) when the text cannot be a code.parseDoorLink(text)(SwiftparseDoorLink(_:)) reads the first door link in a text, a pasted message or the URL a link opened the app with, asparseDoorCodereads a code and a unit;nullwhen there is none. New constantDOOR_LINK_BASE(SwiftdoorLinkBase). The address field (attachSuggest,Suggester) looks a pasted door link up as its door alone, with no geocode. No API change. map.dock.mn does not serve the door page yet. Nothing removed.
JS SDK 0.51.0, Swift SDK 0.48.0, Android SDK 0.49.0 (2026-10-04)
- New:
routetakes a departure time (API 0.50.0): JSroute(from, to, { departAt })(aDateor an ISO 8601 string with an offset), Swiftroute(from:to:mode:departAt:), Androidroute(from, to, mode, departAt)(aDate). ADateis sent in UTC.measured.bandis the hour band read,null(Swiftnil) withoutdepartAtand from an API before 0.50.0. In Swift and Androidbandis a String, aRoadBandvalue. Nothing removed; calls withoutdepartAtare unchanged.
JS SDK 0.50.0, Swift SDK 0.47.0, Android SDK 0.48.0 (2026-10-04)
- New:
traceRoadstakes an hour band (API 0.49.0): JStraceRoads(month, bbox, { band }), SwifttraceRoads(month:bbox:band:), AndroidtraceRoads(month, bbox, band).am_peakMonday to Friday 07:00 to 09:59,pm_peakMonday to Friday 17:00 to 19:59,off_peakevery other hour, Saturday and Sunday whole, by the Ulaanbaatar time of a trip's first stored point. Each band passes the trip cells rule on its own trips. Left out: all hours, as before.rule.hourssays which. New typeRoadBand(Swift.amPeak,.pmPeak,.offPeak; AndroidAM_PEAK,PM_PEAK,OFF_PEAK). Nothing removed; calls without a band are unchanged.
JS SDK 0.49.0, Swift SDK 0.46.0, Android SDK 0.47.0 (2026-10-04)
- New:
Route.measured(API 0.48.0), on a driving route:month, the latest month of published road speeds;roads_m(Swift and AndroidroadsM), metres of the line on roads with a published moving speed;duration_s(Swift and AndroiddurationS), the route time with those roads at that speed.null(Swiftnil) when walking or cycling, when the speeds could not be read, or from an older API.duration_sstays free-flow. New typeRouteMeasured. Nothing removed.
JS SDK 0.48.0, Swift SDK 0.45.0, Android SDK 0.46.0 (2026-10-04)
- New:
traceRoads(month, bbox)(SwifttraceRoads(month:bbox:)) readsGET /v1/traces/roads, the moving speed of all partners' trips per road in a month (needsinsights:read, API 0.47.0): GeoJSON, one feature per OSM way,idthe way id as text, the line dock's current road with that id (LineString, or MultiLineString in pieces),km_h(Swift and AndroidkmH), counted meters over counted seconds on the way, to 0.1, andname_mn,name_en,highway(Swift and AndroidnameMn,nameEn), null when unknown. Only roads under the trip cells rule; no road means too few trips, not a slow road. No trip count. A month is published 14 days after it ends (400month_openbefore). Empty until dock's road lines are loaded. New typesTraceRoads,TraceRoad,TraceRoadGeometry(Swift and Android alsoTraceRoadProperties;geometry.lineslists every piece). Nothing removed.
JS SDK 0.47.0, Swift SDK 0.44.0, Android SDK 0.45.0 (2026-10-04)
- New:
ownKinds(month)(SwiftownKinds(month:)) readsGET /v1/traces/kinds/own, what kind of building your own trips end at in a month, for your dashboard (needstrace:read, API 0.46.0): the month totaltripsandkinds, each kind with trips and itstrips, most first. The kind is the building type when the trip was stored;unknownis a building with no type,noneno building within 30 m. No group rule: the trips are yours. The month you are in is answered too;closedonce final. New typesOwnKinds,OwnKind; in JSkindisBuildingKind, the API's nine values, and a String in Swift and Android. Nothing removed.
JS SDK 0.46.0, Swift SDK 0.43.0, Android SDK 0.44.0 (2026-10-04)
- New:
ownKhoroos(month)(SwiftownKhoroos(month:)) readsGET /v1/traces/khoroos/own, where and when your own trips end per khoroo and hour of arrival in a month, for your dashboard (needstrace:read, API 0.45.0): the month totaltrips, the trips in no current khoroo polygonno_khoroo(Swift and AndroidnoKhoroo), and one feature per khoroo with its polygon, district, number, itstripsand every hour with trips. No group rule: the trips are yours. The month you are in is answered too;closedonce final. Until khoroo polygons are loaded every trip is inno_khoroo. New typesOwnKhoroos,OwnKhoroo(Swift and Android alsoOwnKhorooProperties); the hours areKhorooHour, and in Swift and Android the geometry isKhorooPolygon. Nothing removed.
JS SDK 0.45.0, Swift SDK 0.42.0, Android SDK 0.43.0 (2026-10-04)
- New:
traceSpeeds(month, bbox)(SwifttraceSpeeds(month:bbox:)) readsGET /v1/traces/speeds, the moving speed of all partners' trips per 500 m cell in a month (needsinsights:read, API 0.44.0): GeoJSON, one feature per cell on the trip cells grid withkm_h(Swift and AndroidkmH), counted meters over counted seconds, to 0.1. Only cells under the trip cells rule; no cell means too few trips, not a slow road. No trip count. Per cell, not per road. A month is published 14 days after it ends (400month_openbefore). New typesTraceSpeeds,TraceSpeed(Swift and Android alsoTraceSpeedProperties); the geometry is the trip cells polygon. Nothing removed.
JS SDK 0.44.0, Swift SDK 0.41.0, Android SDK 0.42.0 (2026-10-04)
- New:
traceKhoroos(month)(SwifttraceKhoroos(month:)) readsGET /v1/traces/khoroos, where and when trips end in a month, all partners together (needsinsights:read, API 0.43.0): GeoJSON, one feature per khoroo with its polygon, district, number and each hour of arrival that passed the trip cells rule. No khoroo total. A month is published 14 days after it ends (400month_openbefore). The answer is empty until khoroo polygons are loaded. New typesTraceKhoroos,TraceKhoroo,KhorooHour(Swift and Android alsoTraceKhorooProperties,KhorooPolygon). Nothing removed.
JS SDK 0.43.0, Swift SDK 0.40.0, Android SDK 0.41.0 (2026-10-04)
- New:
allHours(month)(SwiftallHours(month:)) readsGET /v1/traces/hours, when trips start in a month, all partners together (needsinsights:read, API 0.41.0): each weekday and hour that passed the trip cells rule, weekday then hour; no total. A month is published 14 days after it ends (400month_openbefore). New typeAllHours; its slots areHourSlot, as inOwnHours. Nothing removed.
JS SDK 0.42.0, Swift SDK 0.39.0, Android SDK 0.40.0 (2026-10-04)
- New:
ownHours(month)(SwiftownHours(month:)) readsGET /v1/traces/hours/own, when your trips start in a month (needstrace:read, API 0.40.0): each trip once, at the Ulaanbaatar weekday and hour of its first point; month total, 7 weekdays, 24 hours, every weekday and hour with trips,closedfrom 14 days after the month ends. New typesOwnHours,HourSlot. Swift and Android name the arraysbyWeekday,byHour. Nothing removed.
JS SDK 0.41.0, Swift SDK 0.38.0, Android SDK 0.39.0 (2026-10-04)
- New:
ownArrivals(month)(SwiftownArrivals(month:)) readsGET /v1/traces/arrivals/own, how long your trips spend in the last 200 m in a month (needstrace:read, API 0.39.0): trips, slow (5 minutes or more), median, p75 and p90, trips per minute, up to 100 end cells by slow trips,closedfrom 14 days after the month ends. New typesOwnArrivals,ArrivalMinute,ArrivalCell(Swift and Android alsoArrivalCellProperties). Nothing removed.
JS SDK 0.40.0, Swift SDK 0.37.0, Android SDK 0.38.0 (2026-10-04)
- New:
ownConfirmations(month)(SwiftownConfirmations(month:)) readsGET /v1/confirm/own, your drivers' confirmations and door reports in a month (needsconfirm:read, API 0.38.0): counts by result for the month and each day on site, different drivers (never the ids), up to 100 failure points,closedfrom 8 days after the month ends. New typesOwnConfirmations,ConfirmCounts,ConfirmDay,ConfirmFlagged. Nothing removed.
Swift SDK 0.36.0, Android SDK 0.37.0 (2026-10-04)
- New, as JS SDK 0.38.0:
ownTraceCellsreads your organization's own trips per 500 m cell and month (GET /v1/traces/cells/own,trace:read),closedonce the counts are final.traceCellsreads all partners' trips per cell for a published month, only cells from 3 or more organizations (GET /v1/traces/cells,insights:read). Both takemonthYYYY-MM and aBBox, sent at 7 decimals. New typesBBox,TraceCells,OwnTraceCells,TraceCell,TraceCellPolygon,TraceCellProperties. Nothing removed.
Swift SDK 0.35.0, Android SDK 0.36.0 (2026-10-04)
- New, as JS SDK 0.39.0:
reportClosuresendsPOST /v1/closures, a stretch or a crossing that cannot be driven; theidempotencyKeygoes in theIdempotency-Keyheader, so a retry after a timeout stores nothing twice. A key the API would refuse throwsDockErrorbad_idempotency_keybefore any request.closurereads one of your reports back, note included (GET /v1/closures/{id}).liftClosureopens the road again (POST /v1/closures/{id}/lift). All three needclosures:write. New typesClosureReport,ClosureGeometry,ClosureKind,Closure,ClosureProperties; SwiftisValidIdempotencyKey(_:)andidempotencyKeyPattern, AndroidIDEMPOTENCY_KEY_RE. Nothing removed.
JS SDK 0.39.0 (2026-10-04)
- New:
reportClosure(report, { idempotencyKey })sendsPOST /v1/closures, a stretch or a crossing that cannot be driven; the key goes in theIdempotency-Keyheader, so a retry after a timeout stores nothing twice. A key the API would refuse rejects withDockErrorbad_idempotency_keybefore any request.closure(id)reads one of your reports back, note included (GET /v1/closures/{id}).liftClosure(id, { on })opens the road again (POST /v1/closures/{id}/lift). All three needclosures:write. New typesClosureReport,ClosureGeometry,Closure; new constantIDEMPOTENCY_KEY_RE. Nothing removed.
JS SDK 0.38.0 (2026-10-04)
- New:
ownTraceCells(month, bbox)readsGET /v1/traces/cells/own, your own trips per 500 m cell and month, withclosed(needstrace:read, API 0.37.0).traceCells(month, bbox)readsGET /v1/traces/cells, all partners together, only cells that meet the group rule (needsinsights:read). New typesBBox,TraceCell,TraceCells,OwnTraceCells. Nothing removed.
JS SDK 0.37.0, Swift SDK 0.34.0, Android SDK 0.35.0 (2026-10-04)
- New: a
crossingsOnRoutewarning'sdeltacarrieslake_name_osm(lakeNameOsm): the OSM text when the pack folded the lake's name to one script, elsenull. Showlake. Packs before national crossings v11 read as before, with itnull. Nothing removed.
JS SDK 0.36.0, Swift SDK 0.33.0, Android SDK 0.34.0 (2026-10-04)
- New:
crossingsOnRoutecarriesname_osm(nameOsm) on each warning: the OSM text when the pack folded a name that mixed Latin and Cyrillic in a word to one script, elsenull. Showname. The answer addsname_ruleandname_rule_mn(nameRule,nameRuleMn), the pack's rule,nullwhen the pack has none. The pack type readsnames_folded,name_rule,name_rule_mn. Packs before offline v13 read as before, with thesenull. Nothing removed.
JS SDK 0.35.0, Swift SDK 0.32.0, Android SDK 0.33.0 (2026-10-03)
- New:
sandOnRoute,wetlandsOnRoute,passesOnRouteandwaterOnRoutecarryname_osm(nameOsm): the OSM text when the pack folded a name that mixed Latin and Cyrillic in a word to one script, elsenull. Showname. Each answer addsname_ruleandname_rule_mn(nameRule,nameRuleMn), the pack's rule,nullwhen the pack has none. The pack types readnames_folded,name_rule,name_rule_mn. Packs before offline v11 (sand) and v12 read as before, with thesenull. Nothing removed.
JS SDK 0.34.0, Swift SDK 0.31.0, Android SDK 0.32.0 (2026-10-03)
- New:
packRoutereads the manifest'sconfidence_noteandconfidence_note_mn(packs from offline v10) and carries them asconfidence_noteandconfidence_note_mn(confidenceNote,confidenceNoteMn): whatconfidencemeans, for the driver.trace_note(traceNote) is now the pack's sentence when the manifest has both, else the SDK's own as before. A pack with one language only gets the SDK's pair, never one from each. On a scored pack with no note,confidence_noteisnull. Older packs read as before. Nothing removed.
JS SDK 0.33.0, Swift SDK 0.30.0, Android SDK 0.31.0 (2026-10-03)
- New:
passesOnRoutereads a pass'sheights_differ(heightsDiffer) from the pack:truewhen the OSM and DEM heights are both there and more than 50 m apart,falsewithin,nullwhen either is missing.differcounts the passes on the route with ittrue,nullwhen the pack has no flag.PassDemreadsdiffer_m,differ_rule,differ_rule_mnanddiffer. Packs with passes v2 read as before, with thesenull. Nothing removed.
JS SDK 0.32.0, Swift SDK 0.29.0, Android SDK 0.30.0 (2026-10-03)
- New:
packRoutebuilds the Valhalla request for a route on an offline steppe pack, through 2 or more stops: the manifest'srequest(costing and costing_options), the route day's closure rings asexclude_polygons(the pack's closures block, or a newerGET /v1/closuresanswer), units km. It names the closures avoided and carriesattribution,scoredand, when the pack has no trace scores,trace_noteandtrace_note_mn(traceNote,traceNoteMn) to say to the driver. A stop outside the pack's bbox throws; a stop inside an avoided ring throwsEndInClosureError(PackRouteError.endInClosure,EndInClosureException).PackRouteManifestreads the manifest in Swift and Kotlin. Nothing removed.
JS SDK 0.31.0, Swift SDK 0.28.0, Android SDK 0.29.0 (2026-10-03)
- New:
passesOnRoutereads a pass's DEM height beside the OSM one:dem_m(demM), the whole-metre height of the Copernicus GLO-30 cell the node falls in,nullwhen the pack has none there, never mixed intoele_m.top_dem_m(topDemM) is the highest on the route;demis the pack's DEM block, with theattributionto show beside a DEM height.PackPassesreadswith_demanddem. Packs with passes v1 read as before, with thesenull.top_mstays OSM only. Nothing removed.
JS SDK 0.30.0, Swift SDK 0.27.0, Android SDK 0.28.0 (2026-10-03)
- New:
checkPackchecks a downloaded steppe pack against itsmanifest.json: each file in thefilesrows, by size, then sha256, in manifest order. Each file isok,missing,bytes(another size, not hashed) orsha256(same size, other bytes);okonly when all are,badnames the rest. A file name that is not a plain file name throws before any file is read. JS reads through your function (Web Crypto); Swift and Kotlin read the pack folder 1 MB at a time.PackManifestFilesreads the manifest in Swift and Kotlin. Nothing removed.
JS SDK 0.29.0, Swift SDK 0.26.0, Android SDK 0.27.0 (2026-10-03)
- New:
waterOnRoutegives the OSM wells, springs, taps and drinking water within 2000 m (the pack'snear_m,nearM) of a route line, from a steppe pack'swater.geojson, in route order: metres from the start and off the line (along_m,off_m;alongM,offM).drinkableis only what OSM says,null(nil) when it says nothing, never guessed.PackWaterreads the file. From OSM tags, not field checked. Packs built before 2026-10-03 have no such file. Nothing removed.
JS SDK 0.28.0, Swift SDK 0.25.0, Android SDK 0.26.0 (2026-10-03)
- New:
sandOnRoutegives the stretches through OSM sand (natural=sand) a route runs on, from a steppe pack'ssand.geojson, in route order, by the rule ofwetlandsOnRoute: metres from the start (along_m,alongM) and metres on the stretch (on_m,onM); under 20 m (SAND_MIN_ON_M,sandMinOnM) the route only crosses it, unless it runs on all of it.PackSandreads the file. From OSM tags, not field checked. Packs built before 2026-10-03 have no such file. Nothing removed.
JS SDK 0.27.0, Swift SDK 0.24.0, Android SDK 0.25.0 (2026-10-03)
- New:
passesOnRoutegives the OSM mountain passes (mountain_pass,saddle) a route goes over, from a steppe pack'spasses.geojson, in route order: passes whose node lies within 5 m (match_m,matchM) of the line, with metres from the start (along_m,alongM), metres off the line (off_m,offM), the OSM height (ele_m,eleM) and the highest height on the route (top_m,topM, null when none has one).PackPassesreads the file. From OSM tags, not field checked. Packs built before 2026-10-03 have no such file. Nothing removed.
JS SDK 0.26.0, Swift SDK 0.23.0, Android SDK 0.24.0 (2026-10-03)
- New:
wetlandsOnRoutegives the stretches through OSM wetland (marsh,bog,wet_meadow,fen,unknown) a route runs on, from a steppe pack'swetlands.geojson, in route order: metres from the start (along_m,alongM) and metres on it (on_m,onM). A route on less than 20 m of a stretch (WETLAND_MIN_ON_M,wetlandMinOnM) only crosses it, unless it runs on all of it.PackWetlandsreads the file. From OSM tags, not field checked. Packs built before 2026-10-03 have no such file. Nothing removed.
JS SDK 0.25.0, Swift SDK 0.22.0, Android SDK 0.23.0 (2026-10-03)
- New: a steppe pack's
crossings.geojsonfordford_node_id(fordNodeIdin Swift and Kotlin), on each warning too. A ford from an OSM ford node alone, where OSM draws no water line:waterwayisunknown, no name, no flow; say "OSM ford, water not drawn". The pack'sford_nodes,ford_node_ruleandford_node_rule_mn(fordNodes,fordNodeRule,fordNodeRuleMn) give the count and the rule.null(nil) in packs built before that rule. Other warnings unchanged. Nothing removed.
JS SDK 0.24.0, Swift SDK 0.21.0, Android SDK 0.22.0 (2026-10-03)
- New: a steppe pack's
crossings.geojsonfrom_seasonal,intermittent_ruleandintermittent_rule_mn(fromSeasonal,intermittentRule,intermittentRuleMnin Swift and Kotlin): the count of crossings marked intermittent from the OSMseasonaltag alone, with nointermittenttag, and the rule.null(nil) in packs built before that rule. The crossingintermittentfield is unchanged, warnings unchanged. Nothing removed.
JS SDK 0.23.0, Swift SDK 0.20.0, Android SDK 0.21.0 (2026-10-03)
- New: a steppe pack's
crossings.geojsonone_point,split_ruleandsplit_rule_mn(onePoint,splitRule,splitRuleMnin Swift and Kotlin): the count of crossings joined into the one beside them because their water way is split into two ways at the road, and the rule.null(nil) in packs built before that rule. Warnings unchanged. Nothing removed.
JS SDK 0.22.0, Swift SDK 0.19.0, Android SDK 0.20.0 (2026-10-03)
- New: a steppe pack's
crossings.geojsonnear_bridge,bridge_ruleandbridge_rule_mn(nearBridge,bridgeRule,bridgeRuleMnin Swift and Kotlin): the count ofnonecrossings left out of the pack because a bridge is drawn just off the water, and the rule.null(nil) in packs built before that rule. Warnings unchanged. Nothing removed.
JS SDK 0.21.0, Swift SDK 0.18.0, Android SDK 0.19.0 (2026-10-03)
- New: every crossing in a steppe pack carries
road: the OSMhighwayandsurfacetags of the route ways andpaved(true, false, ornullwith no surface tag). On each warning ofcrossingsOnRoute. Anonecrossing on a paved road may be a bridge or culvert OSM does not tag. The pack'son_paved,road_ruleandroad_rule_mn(onPaved,roadRule,roadRuleMnin Swift and Kotlin). New typeCrossingRoad.null(nil) on a new track alone and in packs built before the rule. Nothing removed.
Swift SDK 0.17.0, Android SDK 0.18.0 (2026-10-03)
- New:
Suggester, the address field for iOS and Android, the same rules asSuggesterandattachSuggestin the JS SDK. Asks from the third character, 200 ms after the last key; a newer key cancels the request in flight and a late answer is dropped; a typed door code is looked up as a door first.showgets the list,choose(item)passes a chosengeocoderesult topick. New typesSuggestion(Swift.door,.result; KotlinSuggestion.Door,Suggestion.Result),labelfor it, constantssuggestMinCharsandsuggestDelayMs(KotlinSUGGEST_MIN_CHARS,SUGGEST_DELAY_MS). No view ships: the app draws the list. Nothing removed.
JS SDK 0.20.0, Swift SDK 0.16.0, Android SDK 0.17.0 (2026-10-03)
- New: a river crossing on an unnamed river or stream whose water reaches one named river carries
flows_into(flowsIntoin Swift and Kotlin):name,name_en,river_way_id,ways. Say "unnamed, flows into Орхон гол". Read from the steppe pack's crossings, on each warning ofcrossingsOnRoute. From the OSM drawing, not checked on the ground.channel_ofwins when both are set.null(nil) elsewhere and in packs built before the rule. New typeCrossingFlowsInto. Nothing removed.
JS SDK 0.19.0, Swift SDK 0.15.0, Android SDK 0.16.0 (2026-10-03)
- New: a river crossing on an unnamed channel of a named river carries
channel_of(channelOfin Swift and Kotlin):name,name_en,river_way_id. Say "unnamed channel of Улаан гол". Read from the steppe pack's crossings, on each warning ofcrossingsOnRoute. From the OSM drawing, not checked on the ground.null(nil) elsewhere and in packs built before the rule. New typeCrossingChannelOf. Nothing removed.
Swift SDK 0.14.0 (2026-10-03)
- New:
dockStyle(_:options:),dockSources(_:),dockLayers(_:),client.styleJSON(_:),client.styleURL(_:): the dock style, the same JSON as the JS and Android SDKs.basemapStyle(_:url:options:),PackBasemap.fromManifest(_:): the style for a steppe pack'sbasemap.pmtiles, the same JSON and the same refusals (StyleError) as the JS and Android SDKs. A style is aJSONValue;jsonString()is the text to write to a file for MapLibre Native iOS. AlsoStyleOptions,BasemapStyleOptions,BasemapLang,basemapExcluded,ubCenter,isValidStyleId(_:). Nothing removed.
0.56.0 (2026-10-05)
- New:
GET /v1/corridorsandGET /v1/corridors/{name},route:read(PLAN 15 product 2, PLAN 17 product 6). The steppe corridors, one compare row each: share of cross-sections with 3 or more ruts, envelope area, years, confidence; withtotalsandattribution. One corridor: every fact line withtext,value,unit,confidence,sourceandcaveat, none surer thanconfidence_cap. Every number is copied from the facts.json lane D'smake factswrites, never computed here. Sentinel-2 at 10 m, not field checked; null means unknown. No file, or a file that is not whole: 503unavailable, never half. Labels in English for now. No SDK change. Nothing removed.
0.55.0 (2026-10-04)
- A row that is, as a whole, a door name answers its door in
POST /v1/clean, behind thedoor_namesfeature (PLAN 17 product 4, PLAN 18). Any spelling that folds to the name finds it ("сансар-бэйкери"). The row getsmethoddoor_name, the door's official address and status, its entrance point (marked for your org, asGET /v1/door/{code}marks it), the building the entrance is on (null for a gate on a plot), emptychoices, andmatch_confidencenull. The parsed parts stay as read. The holder org is not shown. Each such row writes one resolution log row.matchedcounts rows with a building or a named door. An expired name, a retired or restricted door, part of a name, and orgs without the feature get the same rows as before. A name lookup error answers the rows without names, never a 503. No new field, no SDK change. Nothing removed.
0.54.0 (2026-10-04)
- A typed door name answers its door in
GET /v1/geocode, behind thedoor_namesfeature (PLAN 17 product 4, PLAN 18). When the whole query is a live door name, in any spelling that folds to it ("сансар-бэйкери"), the named door comes first: layer 1,nearnull, score the best layer 1 score or 1.6 (a whole name), whichever is higher. It is not repeated below, andlimitholds. The holder org is not shown. Serving it writes one resolution log row, asGET /v1/door-names/{name}does. Orgs without the feature, and part of a name, get the same results as before. No new field, no SDK change. Nothing removed.
0.53.0 (2026-10-04)
- Door names are transferable, behind the
door_namesfeature (PLAN 17 product 4). The holder makes a code withPOST /v1/door-names/{name}/transfer-code(names:write):dnt_and 24 characters, shown once, works once, for 7 days or until the name expires if sooner. A new code replaces the old one;DELETEon the same path clears it. The other org sends it toPOST /v1/door-names/{name}/claim{"transfer_code": "dnt_..."}and holds the name from then, same door, sameexpires_at. 409transfer_refusedfor a wrong, cleared or timed-out code, counted against the door miss cap; 409already_yours; 422too_manypast 50 names. No org is named to the other.GET /v1/door-namesaddstransfer_untilon each name, null when no code waits. Nothing removed.
0.52.0 (2026-10-04)
- New, behind the
door_namesfeature (403feature_offuntil it is open to your org): door names, a spoken name a business holds for its door (PLAN 17 product 4).POST /v1/door-names{"name": "Сансар бэйкери", "code": "7QSVX"}registers it for 365 days, no fee yet; your own name with another code moves it. New scopenames:write.GET /v1/door-nameslists your names.GET /v1/door-names/{name}(door:read) answers the door asGET /v1/door/{code}does, official address first; the holder is not shown. Names are unique by folded form: case, spaces, punctuation and Latin letters typed for Cyrillic do not make another name. Refused: a phone number, an e-mail, a door code. 409name_taken, 410retired_code, 422too_manypast 50 names. A code or name that finds no door counts against the door miss cap. Nothing removed.
0.51.0 (2026-10-04)
GET /v1/usageaddsfeatures: the features open to your org, A to Z, likecargo.eta. A route that belongs to any other feature answers 403feature_off. Empty when none is open, which is every org today: no route is behind a feature yet. A change shows within 60 s, as a revoked key does. The usage page shows them. Nothing removed.
0.50.0 (2026-10-04)
GET /v1/route?depart_at=2026-10-05T08:15:00%2B08:00: a driving route'smeasuredreads the speeds of the departure's hour band (GET /v1/traces/roads?band=), by its Ulaanbaatar weekday and hour:am_peakMonday to Friday 07:00 to 09:59,pm_peakMonday to Friday 17:00 to 19:59,off_peakevery other hour, Saturday and Sunday whole. The same rule that puts a trip in a band.measured.bandnames the band read; withoutdepart_atit isnull, all hours, as before.depart_atis ISO 8601 with an offset (Zor+08:00; send+as%2B); a time without one is not taken as Ulaanbaatar time but refused, 400invalid_depart_at. Past or future times both work; the route line andduration_sdo not change. Walking and cycling:measuredstaysnull. Empty today: dock's road lines are not loaded yet, soroads_mis 0. Nothing removed.
0.49.0 (2026-10-04)
GET /v1/traces/roads?band=am_peak: moving speed per road in one hour band (PLAN 9 Phase 7, speed profiles by hour and weekday). A trip counts whole in the band of its first stored point, Ulaanbaatar time:am_peakMonday to Friday 07:00 to 09:59,pm_peakMonday to Friday 17:00 to 19:59,off_peakevery other hour, Saturday and Sunday whole. Each road passes the group rule on that band's trips alone (3 or more organizations, 5 or more trips after the biggest), so a road shown for all hours can be left out of a band. Withoutband: all hours, as before.rule.hourssays which hours an answer covers. Any other value: 400bad_band. Trips matched before this release count in their bands too. Empty today, as all hours: dock's road lines are not loaded yet. Nothing removed.
0.48.0 (2026-10-04)
GET /v1/route: a driving route now carriesmeasured, the time with all partners' measured moving speed per road (PLAN 9 Phase 7, ETA in the route API).measured.monthis the latest monthGET /v1/traces/roadspublishes (14 days after it ends). Each road of the line with a published speed that month is driven at that speed: the router's seconds on it come out, the measured seconds go in, turn costs stay.measured.roads_mis the meters of the line on such roads; 0 means none, andmeasured.duration_sequalsduration_s. Only roads under the rule of/v1/traces/roads(3 or more organizations, 5 or more trips after the biggest), so no single partner's speed shows. Moving speed leaves out stops under 3 km/h; a month average, not by hour. Walking and cycling:null. Speeds unreadable for one answer:null, the route still answers.duration_sis unchanged, free-flow. Needs onlyroute:read. Empty today: no road has a published speed yet, soroads_mis 0. Nothing removed.
0.47.0 (2026-10-04)
GET /v1/traces/roads?month=2026-09&bbox=106.7,47.8,107.2,48.0: moving speed per road and month, all partners together (PLAN 18, speed profiles per segment). Each trip is matched to OSM ways with Valhalla map matching (Meili) when it is stored. A segment between two stored points counts on a way when both points matched that way and it passes the rule of/v1/traces/speeds: 1 to 60 seconds apart, 3 to 130 km/h, its midpoint more than 200 m from the trip's first and last point.km_his the way's counted meters over its counted seconds, to 0.1. GeoJSON, one feature per way:idthe OSM way id as text, the line of dock's current road with that id,km_h,name_mn,name_en,highway. A road shows only under the rule of/v1/traces/cells: 3 or more organizations, and 5 or more trips after taking out the biggest one. Every other road is left out: no road means too few trips, not a slow road. A way dock has no road line for yet, or one crossing a restricted site, is left out; dock has no road lines loaded yet, so the answer is empty until it does. No trip count. Test and internal organizations do not count. Published 14 days after the month ends,month_openbefore; a trip is matched only before then. Your trace delete takes a trip out; the 90-day erase of the points keeps it. Needsinsights:read. The upload answer is unchanged. Nothing removed.
0.46.0 (2026-10-04)
GET /v1/traces/kinds/own?month=2026-10: your trips per kind of building at the trip end, per month, for your dashboard (PLAN 18, what kind of deliveries). Each trip counts once, by the building whose footprint holds its last stored point, else the nearest within 30 m. The kind is the building type when the trip was stored (apartment,ger_house,commercial,public,industrial,garage,other);unknownis a building with no type yet,noneis no building within 30 m. A later change to the building does not move a trip already counted. A building inside a restricted site is not read. No group rule: the trips are yours. Month total (trips) and every kind with trips, most first. Same months as/v1/traces/cells/own;closedfrom 14 days after the month ends. Your trace delete takes a trip out; the 90-day erase of the points keeps it. Your organization only. Needstrace:read. Nothing removed.
0.45.0 (2026-10-04)
GET /v1/traces/khoroos/own?month=2026-10: your trips per khoroo and hour of arrival, per month, for your dashboard (PLAN 18, where and when). Each trip counts once, in the Ulaanbaatar hour (0 to 23) of its last stored point and the khoroo holding the centre of that point's 500 m cell, as/v1/traces/khoroos. A cell counts whole in one khoroo; where current polygons overlap, the first by district code and number. No group rule: the trips are yours. Month total (trips), trips ending in no current khoroo polygon (no_khoroo), and per khoroo its total and every hour with trips. There are no khoroo polygons yet: until they are loaded every trip is inno_khoroo. Same months as/v1/traces/cells/own;closedfrom 14 days after the month ends. Your trace delete takes a trip out; the 90-day erase of the points keeps it. Your organization only. Needstrace:read. Nothing removed.
0.44.0 (2026-10-04)
GET /v1/traces/speeds?month=2026-09&bbox=106.7,47.8,107.2,48.0: moving speed per 500 m cell and month, all partners together (PLAN 18, congestion hot spots). Each trip is cut into segments between its stored points. A segment counts when its points are 1 to 60 seconds apart, it moves at 3 to 130 km/h, and its midpoint is more than 200 m from the trip's first and last point (parking and the walk to the door are not road speed). It counts in the cell holding its midpoint.km_his the cell's counted meters over its counted seconds, to 0.1. A cell shows only under the rule of/v1/traces/cells: 3 or more organizations, and 5 or more trips after taking out the biggest one. Every other cell is left out: no cell means too few trips, not a slow road. No trip count (/v1/traces/cellshas one). Per cell, not per road: no map matching yet. Test and internal organizations do not count. Published 14 days after the month ends,month_openbefore. Needsinsights:read. Nothing removed.
0.43.0 (2026-10-04)
GET /v1/traces/khoroos?month=2026-09: trips per khoroo and hour of arrival, all partners together (PLAN 18, delivery heatmaps by hour and khoroo). Each trip counts once, in the Ulaanbaatar hour (0 to 23) of its last stored point and the khoroo holding the centre of that point's 500 m cell. A khoroo and hour shows only under the rule of/v1/traces/cells: 3 or more organizations, and 5 or more trips after taking out the biggest one. Every other hour is left out: no hour means too few, not zero. No khoroo total. A GeoJSON FeatureCollection, one feature per khoroo with its polygon, district, number and hours. Current khoroo polygons only; there are none yet, so the answer is empty until they are loaded. Counts are kept from today. Test and internal organizations do not count. Published 14 days after the month ends,month_openbefore. Needsinsights:read. Nothing removed.
0.42.0 (2026-10-04)
GET /v1/traces/cells: test and internal organizations no longer count toward a cell, asGET /v1/traces/hours. They are not one of the 3 organizations, their trips are not in the total, and they are never the biggest one taken out. A cell that showed only because of them is now left out; a cell still shown may have fewer trips.rulehas a new key,orgs, that says so. Counts are kept; only what is shown changes. Nothing removed.
0.41.0 (2026-10-04)
GET /v1/traces/hours?month=2026-09: trips per weekday and hour, all partners together (PLAN 18, road load by hour). Each trip counts once, in the Ulaanbaatar weekday (1 Monday to 7 Sunday) and hour (0 to 23) of its first stored point. A weekday and hour shows only under the rule of/v1/traces/cells: 3 or more organizations, and 5 or more trips after taking out the biggest one. Every other slot is left out: no slot means too few, not zero. No total. Test and internal organizations do not count. Published 14 days after the month ends,month_openbefore. Needsinsights:read. Nothing removed.
0.40.0 (2026-10-04)
GET /v1/traces/hours/own?month=2026-10: your trips per weekday and hour, for your dashboard (PLAN 18, when). Each trip counts once, in the Ulaanbaatar weekday (1 Monday to 7 Sunday) and hour (0 to 23) of its first stored point. Month total, 7 weekday counts, 24 hour counts, and every weekday and hour with trips. Same months as/v1/traces/cells/own;closedfrom 14 days after the month ends. Your trace delete takes a trip out; the 90-day erase of the points keeps it. Your organization only. Needstrace:read. Nothing removed.
0.39.0 (2026-10-04)
GET /v1/traces/arrivals/own?month=2026-10: your trips' arrival minutes per month, for your dashboard (PLAN 18, minutes lost finding the door). For each trip: whole minutes from the first point after it was last more than 200 m from its end, to its end; 60 means 60 or more. Circling inside 200 m counts; a trip that never left 200 m of its end is not counted. The minutes include parking and the walk to the door, so compare months and places with them. Month totals with median, p75 and p90, trips per minute, and up to 100 cells where trips ended, by trips of 5 minutes or more. Same 500 m grid and months as/v1/traces/cells/own;closedfrom 14 days after the month ends. Your trace delete takes a trip out; the 90-day erase of the points keeps it. Your organization only. Needstrace:read. Nothing removed.
0.38.0 (2026-10-04)
GET /v1/confirm/own?month=2026-10: your drivers' confirmations and door reports per month, for your dashboard (PLAN 18). Counts by result for the month and for each day, the number of different drivers (by your driver ids, never the ids), and the failure points: up to 100 doors and entrances your drivers said are wrong, missing or a duplicate, most first. A row counts on the Ulaanbaatar day the driver was on site (observed_at, else when received). The month you are in is answered too;closedistruefrom 8 days after the month ends. No door in a restricted site is listed. No other organization is read. New scopeconfirm:read, read-only:confirm:writedoes not read it, andconfirm:readcannot confirm or report. Nothing removed.
0.37.0 (2026-10-04)
GET /v1/traces/cells/own?month=2026-10&bbox=west,south,east,north: your own trips per 500 m cell and month, for your dashboard (PLAN 18). The same grid and months asGET /v1/traces/cells, your organization's trips only, every cell with one of them, no group rule. The month you are in is answered too;closedistruefrom 14 days after the month ends, when the counts are final. No other organization is read. New scopetrace:read, read-only:trace:writedoes not read it, andtrace:readcannot send or delete. Nothing removed.
0.36.1 (2026-10-03)
POST /v1/clean: new methodnumber_area_ambiguous. A number in a named area that ties in 2 or 3 places gives a building andchoices, withmatch_confidencenull(no test rows yet, not 0). A tie in 4 or more places gives no building. Example: "Улаанхуаран 24" has 2 choices, both in Bayanzurkh; "Сансар 24" (4 places) and "Бага тойрог 10" (12 places) have no answer. Before, such an address came asname_ambiguouswith 0.536. No field changed. Nothing removed.
0.36.0 (2026-10-03)
POST /v1/clean: every row haschoices. For methodname_ambiguousornumber_ambiguous, up to 3 places the address could be, the answer first. Each hasbuilding_id,point,district, the nearest area name within 1000 m (area) and street name within 150 m (street), so a person can pick ("Сансар 45, near Tengis or near the 3rd hospital?"). A choice in a restricted site keeps only its district (withheld). Any other method:[]. The answer itself is unchanged. On the gold set, 85 of 541 rows carry choices. The upload page addsdock_choices.- Each ambiguous answer is logged as its method and building ids only, no address text, to fix area outlines.
JS SDK 0.18.0, Android SDK 0.15.0 (2026-10-03)
- New:
basemapStyle(basemap, url, options)gives a whole MapLibre style for a steppe pack's newbasemap.pmtiles(OpenMapTiles from OSM, all Mongolia, zoom 0 to 14), from the manifest'sbasemapblock: land cover, water, steppe tracks, roads, borders, buildings, and withglyphsalso names inlang(mnoren). Onepmtiles://source with the block's attribution. A block with apoiorhousenumberlayer is refused. Kotlin alsoPackBasemap.fromManifest(text). AlsoBASEMAP_EXCLUDED, and in JS the typesPackBasemap,BasemapStyleOptions,BasemapLang. The Swift SDK does not have it yet. Nothing removed.
JS SDK 0.17.0, Swift SDK 0.13.0, Android SDK 0.14.0 (2026-10-03)
- New:
poisOnRoute(pois, line, nearM)reads a steppe pack's newpois.geojson: the registered fuel stations and ger camps withinnearM(default 2000 m, as on the steppe route pages) of a route line, in route order, withalong_mandoff_m(alongM,offM) in whole metres, registered name andreg_number(regNumber). Places not matched to the state registry are not in the pack;unlisted(unlistedFuel,unlistedGerCamp) gives their counts for all Mongolia, andregistry_checked(registryChecked) false means the registry was not read. The current pack lists 0 places, with 1,268 fuel stations and 175 ger camps counted. New typesPackPois,RoutePois,PoiOnRoute.crossingsOnRoutegives the same answers as before. Nothing removed.
JS SDK 0.16.0 (2026-10-03)
- New:
attachSuggest(input, client, { onPick }), one address input for a checkout or a pickup form (PLAN 18, entry path 5). Suggestions from the third character, 200 ms after the last key; a newer key cancels the older request. A typed door code is looked up as a door first. Arrows, Enter, click, Escape; ARIA combobox. Each option islabel(), so unmatched places say "(unofficial)".Suggesteris the same logic with no DOM. AlsoSUGGEST_MIN_CHARS,SUGGEST_DELAY_MSand the typesSuggestion,SuggestOptions,AttachSuggestOptions,SuggestBox,SuggestClient. Nothing removed. - The checkout sample uses it.
JS SDK 0.15.0, Swift SDK 0.12.0, Android SDK 0.13.0 (2026-10-03)
crossingsOnRoute: new fielddeltaon each warning, and on the pack's crossings, for a river crossing on a delta channel:lake(the lake's OSM name, can benull),lake_osm(lakeOsm, asrelation/114846) andriver. There the river splits into a lake and no model reach says what share a channel carries, so the flow isnull: say "delta channel, flow unknown".null/nilelsewhere and in packs built before the rule. In the current pack 3 of 5,525 crossings, all in the Khovd delta into Khar-Us nuur. Nothing removed.
Swift SDK 0.11.0 for CocoaPods (2026-10-03)
- New:
Dock.podspecin thesdk/swiftfolder. A CocoaPods app addspod 'Dock', :path => '../sdk/swift'. Same sources, platforms (iOS 15, macOS 13) and version as the Swift package. Not on CocoaPods trunk. The SDK did not change.
Android SDK 0.12.0 (2026-10-03)
- New: a map style for MapLibre Native.
styleJson(options)gives the dock style (the same as the JS SDK'sdockStyle) with its sources keyed, forStyle.Builder().fromJson;styleUrl(id)gives a saved style forfromUri. AlsoStyleOptions,dockStyle,dockSources,dockLayers,UB_CENTER. Nothing removed. - The emulator run now draws both styles with MapLibre Native 13.6.1 on Android 16 and checks the colors. MapLibre Native Android 13 needs API 23, so this check does not run on API 21.
Android SDK 0.11.2 (2026-10-03)
- Fixed: with telemetry on, the first
geocode()crashed the app on Android withExceptionInInitializerError(PatternSyntaxException). The search queue split words with a regular expression flag Android does not have. Every earlier Android SDK version had it; on the JVM it worked. Now the same white space is spelled out, so searches are cut into words as before. Found on an Android 16 emulator. Nothing else changed. - The emulator run now also checks search telemetry and a trip recorded from the emulator's GPS.
Android SDK 0.11.1 (2026-10-03)
- Fixed:
parseDoorCode, anddoor()which calls it, crashed on Android 5.0 to 6.0 (API 21 to 23) withNoSuchMethodError: codePoints(). That call is Android API 24. Found on an Android 5.0 emulator; same answers on all 74 shared cases now. Nothing else changed. - Every release now runs on emulators, API 21 and API 36, from an app built with the Android Gradle Plugin, and the jar is checked against API 21 at build time.
JS SDK 0.14.0, Swift SDK 0.11.0, Android SDK 0.11.0 (2026-10-03)
crossingsOnRoute: the month's m3/s (month_m3s,monthM3s) is now alsonullwhere the flow'sconfidenceis 0.1 or under, not only on a reach marked doubtful. Packs mark 0.1 where no model reach follows the OSM river (snapnone_fits) and where the channel follows only a small reach and the river's main stem is taken (main_stem). The steppe dashboard already hid the number there. In the current pack that is 448 of 2,587 river crossings, was 23. The high-water months are still given.- New field
snapon the flow and on each warning:matched,none_fits,main_stemornearest;null/nilin packs built before the match. New constantLOW_FLOW_CONFIDENCE(lowFlowConfidencein Swift), 0.1. Nothing removed.
Swift SDK 0.10.0, Android SDK 0.10.0 (2026-10-03)
- New
crossingsOnRoute, offline, as in JS SDK 0.13.0: the fords and unbridged crossings a route line passes within 5 m, in route order, from a steppe pack'scrossings.geojson(PackCrossings(json:)in Swift,PackCrossings.fromJson(text)in Kotlin). Fields in camel case:alongM,offM,highWater,highMonths,monthM3s(nil/nullon a reach marked doubtful). NewubMonth(), the default month. Same answers as JS on the shared test cases and on three real routes. Nothing changed or removed. - Android: built as a Maven artifact,
mn.dock:dock-android:0.10.0, with a POM, a sources jar and a javadoc jar. The POM lists kotlinx-coroutines and kotlinx-serialization, so an app adds one dependency line. Not on Maven Central yet: your contact gives you the repository folder (Android page, Install). The jar is the same.
JS SDK 0.13.0 (2026-10-03)
- New
crossingsOnRoute(crossings, line, month?), offline: the fords and the crossings with no OSM bridge or ford that a route line passes within 5 m, in route order, from a steppe pack'scrossings.geojson. Each hasalong_m,off_m, and on a riverhigh_waterfor the month (default this month, Ulaanbaatar),high_monthsand the month's normal flow in m3/s (nullon a reach marked doubtful). Show the pack's note and the flow attribution with them. Nothing changed or removed.
JS SDK 0.12.0, Swift SDK 0.9.0, Android SDK 0.9.0 (2026-10-03)
geocodetakesproximity, a point ([lon, lat]in JS,LonLatin Swift and Kotlin), sent as the API 0.35.0 parameter: among names the typed text starts equally well, nearer places come first. Rounded to 7 decimals asroutedoes. Not kept on the device, not sent with telemetry; offline answers do not read it. Without it every request is as before. Nothing removed.
0.35.2 (2026-10-03)
GET /v1/geocode: of two places with the same score, the one with a matched official address comes first, then partial, then none; then the higher confidence (null and 0 count the same). The tie used to break on id. Today no row has an official address or confidence above 0, so every answer is as 0.35.1. Fields unchanged. Nothing removed.
0.35.1 (2026-10-03)
GET /v1/geocode, typing a name: a place partner app users picked comes before an equally good match. It counts with 5 or more picks in a year from 2 or more partners. Whole name, then name start, then a later word, as before. Picks come fromPOST /v1/telemetry/search, sent only after the user opted in. No partner has sent picks yet, so every answer is as 0.35.0 today. Fields unchanged. Nothing removed.
0.35.0 (2026-10-03)
GET /v1/geocodetakes an optionalproximity=lon,lat(where the person is, or the map centre). Among names the query starts equally well, the nearer place comes first: whole name, then name start, then a later word, as before, each nearer first. A whole name far away still leads a name start next door. The point is not logged. A bad value is 400invalid_proximity. Without it, every answer is as 0.34.2. Synthetic set cut to 3 characters, right place in the top 3: 0.385 with no point, 0.431 with the target's district centre, 0.394 with the city centre. Fields unchanged. Nothing removed.
0.34.2 (2026-10-03)
GET /v1/geocode, typing a name: a place whose name the query starts now comes before places that only look alike. The whole name first, then names that start with the query, then names with a later word that does; a district or area before a building of the same kind of match, then the shorter name. "Бая" gives Bayangol and Bayanzurkh districts, no longer "байр". Synthetic set cut to 3 characters: right place in the top 3 0.330 to 0.385; at 4 characters 0.399 to 0.472; whole queries unchanged (top-1 0.991).scoreof such a row can reach 1.749. Fields unchanged. Nothing removed.
0.34.1 (2026-10-03)
GET /v1/geocode, landmark-relative queries: when the query names a kind ("university south of X, kindergarten next door"), places of that kind, or of no known kind, now come before places of another kind; the reranking model orders each group. A query that says only "building" or "place" is ordered as before.scorestays the model's. Described-place test sets, 600 queries each: top-1 0.963 to 0.983 (12 better, 0 worse) and 0.978 to 0.983 (3, 0). Fields unchanged. Nothing removed.
JS SDK 0.11.0, Swift SDK 0.8.0, Android SDK 0.8.0 (2026-10-03)
- New offline helper:
parseDoorCode(text)reads a typed or pasted door code as the API does, with no request: the code in upper case Latin and what followed it (unit), or why not:empty,length(not 5 characters) orletters(a vowel, I, L, O, U, or a sign). Same Cyrillic lookalike keys and same spaces as the API, checked on the same 74 cases (sdk/testdata/door-code.json). AlsoDOOR_CODE_ALPHABETandDOOR_CODE_LENGTH(SwiftdoorCodeAlphabet,doorCodeLength). door()refuses a text that cannot be a code before any request, with the sameDockErrorthe API sends: 400invalid_codeand its message. A refused text no longer costs a request. Nothing removed.
Swift SDK 0.7.0, Android SDK 0.7.0 (2026-10-03)
- Offline search, as in JS SDK 0.10.0. Each place passed to
pickis kept on the device, and whengeocodegets no answer (DockErrorcodenetwork) it answers from them withofflinetrue, most picked first. Places only: not the typed text, time or point. In memory by default, 100 places.placeCache: PlaceCacheOptions(store: UserDefaultsPlaceStore())(Swift) orplaceCache = PlaceCacheOptions(store = ...)(Kotlin, aPlaceStoreoverSharedPreferences) keeps them across launches;nilornullturns it off. Same key and format as the JS SDK. Newplaces()andforgetPlaces().pickused to do nothing while telemetry was off; it now keeps the place, and still sends nothing.GeocodeResponsegetsoffline(false for an API answer, not on the wire). Nothing removed.
JS SDK 0.10.0 (2026-10-03)
- Offline search. Each place passed to
pickis kept on the device, and whengeocodegets no answer (DockErrorcodenetwork) it answers from them withoffline: true, most picked first. Places only: not the typed text, time or point. In memory by default, 100 places;placeCache: { store: localStorage, max }keeps them across page loads,placeCache: falseturns it off. Newplaces()andforgetPlaces().pickused to do nothing while telemetry was off; it now keeps the place, and still sends nothing. API answers are unchanged. Nothing removed.
JS SDK 0.9.0, Swift SDK 0.6.0, Android SDK 0.6.0 (2026-10-02)
- New offline helper: JS
closureAt(avoid, point), Swiftavoid.closure(at:), Kotlinavoid.closureAt(point). It gives the id of the first closure in anexcludePolygonsanswer whose ring holds the point, or null. Valhalla drops every road inside a ring, so a trip that starts or ends there gets "No path could be found for input": check both ends before asking your own Valhalla. Same rule as the API's 422end_in_closureonGET /v1/route, checked on the same 700 points. Theroute()doc comment now listsend_in_closureandclosures_over_limit. Nothing removed.
0.34.0 (2026-10-02)
GET /v1/route: afromortoinside a road closure active today is 422end_in_closure, and the message names the closure id. Before, the router dropped every road inside the closure and the answer was 422no_route, "No path could be found for input". Route to a point outside the closure. Nothing removed.
0.33.0 (2026-10-02)
POST /v1/closurestakes anIdempotency-Keyheader (1 to 64 of letters, digits and._:-; a uuid fits). A retry after a timeout with the same key and the same body answers the first report, stores nothing and setsIdempotent-Replayed: true. The same key with another body is 422idempotency_key_reused. Keys are per organization and never expire. Without a key nothing changes. Nothing removed.
0.32.0 (2026-10-02)
GET /v1/routeavoids every road closure active today (Ulaanbaatar), in every mode. New fieldclosuresin the answer:as_of(the route day) andavoided(the closure ids kept out). If the day's closures are over the router's limits for one request (10,000 m of ring, 100 vertices), the answer is 422closures_over_limit: no closure is dropped to give a route. Closures that cannot be read are 503unavailable. On a day without closures the route is the same as before.
JS SDK 0.8.0, Swift SDK 0.5.0, Android SDK 0.5.0 (2026-10-02)
- JS
Route, SwiftRouteand KotlinRoutehave the newclosuresfield (RouteClosures). Nothing removed.
JS SDK 0.7.0, Swift SDK 0.4.0, Android SDK 0.4.0 (2026-10-02)
closures()readsGET /v1/closures(route:read): road closures not over today, each with its ring. Keep the answer on the phone.excludePolygons(closures, day)works offline: the rings of the closures active on the route day (default today, Ulaanbaatar) for your own Valhalla'sexclude_polygons. Over Valhalla's limits for one request it throws (JSClosuresOverLimitError, SwiftClosuresError.overLimit, KotlinClosuresOverLimitException): refuse the route, never drop a closure. A day before the list'sas_ofthrows: fetch again. JS also exportsisActiveandubToday. Nothing removed. API still 0.31.0.
0.31.0 (2026-10-02)
- Road closures (PLAN.md 20.14). A partner reports a flooded or washed out stretch, a bridge down, snow, mud or works with
POST /v1/closures: a point or a line of up to 3000 m, from and to days, a note. New scopeclosures:write. The sender is the key's organization. A report is never edited or deleted: lift it withPOST /v1/closures/{id}/liftwhen the road opens.GET /v1/closures/{id}returns your own report, note included. GET /v1/closures(route:read) returns every closure current today or starting later, with the ring to send Valhalla asexclude_polygons. Same shape and same rings as an offline pack's closures block. No partner name, no note. Confidence 0.5: one partner's word. No reports inside a restricted site. A note with a run of 6 or more digits (a phone number) is refused. Nothing removed.
0.30.1 (2026-10-02)
GET /v1/geocode, landmark-relative queries: the description is now searched with the English word beside its place word ("здание, внутри аптека (pharmacy)"), in Mongolian, Russian, Japanese, Korean and Chinese, as the reranking model already read it. English is read as typed. Kind-only test set, 486 queries: a fitting place first 0.951 to 0.965 (7 better, 0 worse). Described-place test set, 600 queries: top-1 0.952 to 0.953. Fields unchanged. Nothing removed.
0.30.0 (2026-10-02)
GET /v1/geocode, landmark-relative queries: when the query also describes the place ("building east of Max Mall, pharmacy inside"), the first 10 places found by description are read again side by side with that description by a reranking model and put in its order (PLAN.md 7, layer 3). Those havelayer3 andscoreis the model's, 0 to 1. New value:layer3. When the model is slow or down they staylayer2, as before. Described-place test set, 200 queries: top-1 0.70 to 0.82, top-3 0.84 to 0.91. Nothing removed.
0.29.1 (2026-10-02)
GET /v1/geocode, landmark-relative queries: a landmark whose name has a comma is read whole ("west of U.S. Embassy - Ulaanbaatar, Mongolia"). Before, only the part after the comma ("Mongolia") was read. The longer reading counts only when a landmark by that name is found; otherwise the landmark stops at the comma, as before. Fields unchanged. Nothing removed.
0.29.0 (2026-10-02)
- Buildings without a name are now searchable too (633,322 of 643,661 buildings have no name).
GET /v1/geocodefinds them by description (layer2) and within 150 m of a landmark.GET /v1/reversereturns one only when the point is inside it.GET /v1/address/{id}andGET /v1/changesknow them. Names are null,long_mnandlong_engive district and area,official_statusisnone. Name search (layer1) never returns them. Fields unchanged. Nothing removed.
0.28.1 (2026-10-02)
GET /v1/geocode, landmark-relative queries: the words that close the relation no longer count as the description. Mongolian тал in a case and нь ("зүүн талд", "баруун талын", "урд нь"), Japanese 側, の, に ("東側の建物"), Korean 에 (있는) ("동쪽에 있는 건물"), Chinese 的 ("东边的建筑"). Fields unchanged. Nothing removed.
0.28.0 (2026-10-02)
GET /v1/geocodereads landmark-relative queries (PLAN 7): "Хүннү моллын ард", "Max Mall-iin urd", "east of Max Mall", "к востоку от ...", "...の近く", "... 동쪽", "...东边". It finds the landmark by name, then lists first the places within 150 m on the side said (within 55 degrees). Mongolian ар (behind) is north, урд (front) south. They rank on the rest of the query, without the landmark name (layer2), or by distance when it says nothing more (layer1).- Each result has a new field,
near: that landmark (kind,id, names,relation,metres). null for every other result. Name search order is unchanged. Nothing removed.
0.27.0 (2026-10-02)
GET /v1/geocode: each result has a new field,layer. 1: the words of a name or address matched. 2: the place description matched the meaning (PLAN 7 layer 2), for example "building with a blue roof next to a school". Layer 2 places only fill the slots left when name matches are fewer thanlimit, after every name match. Comparescoreonly within one layer. Name search order is unchanged. Nothing removed.
0.26.0 (2026-10-02)
GET /v1/traces/cells?month=2026-09&bbox=west,south,east,north, trips per 500 m cell and month, all partners together (PLAN 19). New scopeinsights:read. A cell is shown only when 3 or more organizations sent trips through it and 5 or more trips are left after taking out the biggest organization's. Other cells are left out: no cell means too few, not zero. A month is published 14 days after it ends, from trips received by then. Counts stay after the raw points are erased at 90 days. YourDELETE /v1/traces/{id}takes the trip out of the counts too. Nothing else changed.
0.25.0 (2026-10-02)
POST /v1/oauth/token, OAuth2 client credentials (PLAN 19). Your server trades its key for adt_access token that works for an hour with the scopes it asks for, and gives the token to an app, so the key never ships in an app. Tokens go where keys go:?access_token=orAuthorization: Bearer. New 401 errors for tokens only:invalid_token,expired_token. Keys work as before. Nothing removed.
0.24.0 (2026-10-02)
pointin door, geocode and reverse results and inGET /v1/address/{id}is given to 6 decimals (about 0.1 m). The 7th decimal is a mark for your organization (PLAN 19): the same for a point every time, and it moves the point under 0.12 m. The top-levelpointof a reverse answer comes back as you sent it. No field or type changed.
JS SDK 0.6.0 (2026-10-02)
- Needs maplibre-gl 6.4.1 or later in 6.x (
peerDependencies>=6.4.1 <7). Every version up to 6.4.0, all of 4 and 5 included, has a critical XSS in its HTML sanitizer (GHSA-jrc7-96c5-q579). On 4 or 5:npm install maplibre-gl@6and add the worker lines from Bundlers. The sample apps load maplibre-gl 6.11.2 from unpkg as an ES module. No code change. API still 0.23.0.
Android SDK 0.3.0 (2026-10-02)
- Search telemetry and trip recording in Kotlin, with the JS and Swift consent rules.
setTelemetry(on),pick(result),flushTelemetry(),appInBackground(): off by default, final text, result count and pick only, 50 a batch, sent when the app callsappInBackground()fromonStop, dropped when the user turns it off.startTrace(Consent.OPT_IN, source),stopTrace(),cancelTrace(),trace(id),deleteTrace(id): points in memory only, [lon, lat, t], split every 20,000 points. The app keeps its own location permission and pushes its fixes into aPositionFeed. Nothing removed. API still 0.23.0, Swift still 0.3.0.
Swift SDK 0.3.0 (2026-10-02)
- Search telemetry and trip recording in Swift, with the JS consent rules.
setTelemetry(_:),pick(_:),flushTelemetry(): off by default, final text, result count and pick only, 50 a batch, sent when the app goes to the background, dropped when the user turns it off.startTrace(consent: .optIn, source:),stopTrace(),cancelTrace(),trace(_:),deleteTrace(_:): points in memory only, [lon, lat, t], split every 20,000 points. The app keeps its own location permission and pushes Core Location fixes into aPositionFeed. Nothing removed. API still 0.23.0, Android still 0.2.0.
0.23.0 (2026-10-02)
- The monthly field audit (PLAN 5), for dock staff keys only:
GET /v1/auditlists samples,GET /v1/audit/{tag}serves one checklist in walk order with every answer so far,PATCH /v1/audit/{tag}/{seq}records what the surveyor found (found,wrong_location,missing,no_access, ornullto clear).foundneeds the measured point and the method (rtk,imagery_tap,tape). The device position must be within 40 m (150 m forno_access). Every answer is kept. Needsaudit:write, which only an internal org can hold. Partner calls are unchanged.
Swift and Android SDK 0.2.0 (2026-10-02)
label(door, lang)in Swift (label(_:lang:),Lang.mn,.en) and Kotlin (label(place, lang),Lang.MN,EN), same rules as the JSlabel(): the spoken form, then the name, then the long form, then the code, never the id, and "(unofficial)" / "(албан бус)" when there is no matched official address. Works onDoor,RouteDoor,GeocodeResult,ReverseResultandAddress. The iOS and Android pages use it. Nothing removed. API still 0.22.0.
iOS and Android SDK pages (2026-10-02)
- Two new pages, iOS SDK and Android SDK, in Mongolian and English: install, every call, errors, tiles, deprecated routes, keys and a reference table. Every code block on them runs in
swift testand./gradlew teston a fake transport. No SDK or API change: Swift and Android 0.1.0, API 0.22.0.
Android SDK 0.1.0 (2026-10-02)
- First cut of the Android SDK, Kotlin in sdk/android (Java 8 bytecode, Android API 21 and up, kotlinx-coroutines and kotlinx-serialization): suspend calls
geocode,reverse,door,route,place,changes,confirm,reportDoor,usage, same names and errors (DockError) as the JS and Swift SDKs. The flat type isDoorUnit, so it does not hide Kotlin'sUnit.authorizedUrl()adds the key to dock tile URLs only, for MapLibre Native. No map view, telemetry or trip recording yet. Not on Maven yet. No API change, still 0.22.0.
Swift SDK 0.1.0 (2026-10-02)
- First cut of the iOS SDK, a Swift package in sdk/swift (iOS 15, macOS 13, no dependencies):
geocode,reverse,door,route,place,changes,confirm,reportDoor,usage, same names and errors (DockError) as the JS SDK.authorizedURL(_:)adds the key to dock tile URLs only, for MapLibre Native. No map view, telemetry or trip recording yet. Not on a package index yet. No API change, still 0.22.0.
Database connections (2026-10-02)
- The API no longer exits when the database drops an idle connection (a restart, for example). It logs a warning and opens a new connection on the next request. On shutdown it waits until every connection has closed. No API change, still 0.22.0.
Change feed watermark (2026-10-01)
GET /v1/changes:untilno longer waits behind database maintenance (autovacuum). Only open writes hold it back, as documented. A change still appears after the next index refresh, never lost. No API change, still 0.22.0.
Confidence levels (2026-10-01)
- Driver confirmations now raise
confidence, hourly. Oneconfirmedtap within 5 m of the entrance: 2. Three within 5 m of each other from at least two drivers or orgs: 3. A newerwrong_locationormissingholds the level, and so does aconfirmedtap over 5 m away, which now waits for review likewrong_location. Levels are only raised here; moving a point drops it one level, as before. No API change, still 0.22.0.
JS SDK 0.5.0 (2026-10-01)
place(id):GET /v1/address/{id}, a place by id with itsparents.changes(since, { limit, cursor }): one page ofGET /v1/changes; with a cursor,sinceis left out; aDategoes as UTC.confirm(confirmation):POST /v1/confirm; TypeScript refuses aconfirmedwithouttapor bothcodeandentrance;observed_attakes aDate. No API change, still 0.22.0.
0.22.0 (2026-10-01)
POST /v1/confirm: a driver on site confirms a door (bycode) or an entrance (by its tileid), or says it is elsewhere (wrong_location) ormissing. The driver taps the door; phone GPS (device) only checks the tap and must be within 40 m.driveris your own id for the driver, kept only as a hash; a phone number or e-mail is refused. The same driver saying the same within 24 h counts once. Map data does not change at once. Queued confirmations carryobserved_at, up to 7 days old. Needsconfirm:write(partner keys).
0.21.0 (2026-10-01)
GET /v1/changes?since=: ids, kinds, states and times of doors and places changed since a time, so a partner syncs stored places without a full pull.current: fetch it again with/v1/address/{id};retiredorhidden: drop it. The window is[since, until), whereuntilis the last search index refresh, so everycurrentid listed can be fetched. Pages bycursor,limit1 to 1000. Nothing inside a restricted site is listed. Needschanges:read(partner keys).
0.20.0 (2026-10-01)
GET /v1/address/{id}: fetch one door or place again by theida geocode or reverse result gave. Same fields as a search result, plusparents: the ids of the building or khashaa, area, street, khoroo and district a door stands in (a business through its door). Official address first, code after it. A retired door or a demolished building is410 retired; ids are never reused. Anything inside a restricted site is404. A place added since the last search index refresh is404until the refresh. Needsgeocode:read.
0.19.1 (2026-10-01)
DELETE /v1/traces/{id}works from a browser: CORS now allowsDELETE. Before,deleteTrace(id)in a browser failed with a network error and the trip stayed (it worked from Node). New sample app:examples/driver.html, a driver trip with the opt-in box.
0.19.0 (2026-10-01)
- JS SDK 0.4.0:
startTrace({ consent: 'opt_in' }),stopTrace(),cancelTrace(),trace(id),deleteTrace(id). Off until started. Records[lon, lat, t]from geolocation in memory only, at most one point a second, fixes over 50 m accuracy and outside Mongolia left out; sends toPOST /v1/traceson stop, split every 20,000 points. POST /v1/traces: send one trip a user recorded, for users who opted in, asdock.trace.v1. Exactly three keys:format,consent: "opt_in",points([lon, lat, t], WGS84, whole seconds UTC, 2 to 20,000 points, inside Mongolia). Any other key (device id, user, accuracy) refuses the whole upload. 11 refusal codes, never a value from the body. Besides the points, only the organization and the day are kept: no key, IP or device. Points inside a restricted site are not stored (restricted). The same trip sent twice is stored once. Raw points are erased after 90 days (purge_after). Bodies up to 2 MB. Needstrace:write.GET /v1/traces/{id}: your trace as stored, GeoJSON. Another organization's id is 404.DELETE /v1/traces/{id}: erase your trace now. Points and times go; the id stays and is never reused.
0.18.0 (2026-10-01)
POST /v1/telemetry/search: send place searches and what was picked, for users who opted in (consent: "opt_in", else400 consent_required). Up to 50 searches a request:q,resultsshown,picked(kind,id,rank). Only the organization and the Ulaanbaatar day are kept: no key, IP, device or time of day. Phone numbers and e-mail addresses are removed before storage. A pick inside a restricted site is dropped whole (dropped). Needsgeocode:read; one request is one call. Keep it off by default.- JS SDK 0.3.0:
telemetryoption andsetTelemetry(on), off by default. When on,geocodesearches (final text only) andpick(result)go to this route in batches of 50, on page hide and onflushTelemetry(). The checkout sample has the consent box.
0.17.0 (2026-10-01)
POST /v1/clean: each row hasnumbers: entrance, floor, unit and pavilion as values. "5-р entrance (internal)" is 5, "В1 давхар" is -1 (a basement is negative), "1206-1208 тоот" is "1206", "1207", "1208". Units stay strings ("0-9а"). A gate with no number gives none. Empty lists when absent. The spans as typed stay as they were.
0.16.0 (2026-10-01)
POST /v1/clean: address cleaning. Up to 100 rows per request, 300 characters each. Per row: district, khoroo, microdistrict, street, building, entrance, floor, unit (as typed), the matched building id and point, the method that found it, that method's share right on test rows (match_confidence), the building source, and the text left over. No state address register is matched yet, so every row isunofficial. A building in a restricted site is withheld (withheld). Needsclean:read; free keys do not carry it. Addresses are not stored or logged.
0.15.0 (2026-10-01)
POST /v1/alerts: tell me when today'scalls,tilesordoor_missespass a share of the daily cap (percent, default 80). Fires at most once per Ulaanbaatar day. Optionalwebhook_urlgets one signedPOSTper firing (Dock-Signature, HMAC-SHA256;webhook_secretshown once). Any key, not counted. Up to 20 per org (409 alert_limit), one per metric and percent (409 alert_exists). An alert never changes.GET /v1/alerts: your org's alerts and their firings of the last 30 days, with webhook delivery.- Usage page: alerts, add one, firings.
0.14.0 (2026-10-01)
POST /v1/styles: save a map style (name,background,building,building_outline,extrude) and get a style id,st_and 10 characters. Missing fields take the dock defaults. Needstiles:read. Up to 100 per org (409 style_limit). A style never changes: to change colors, save a new one.GET /v1/styles: your org's styles, newest first.GET /v1/styles/{id}: the style as MapLibre JSON, sources on this API. Only your org's keys read it; any other id is404 not_found.- JS SDK:
createMap(maplibregl, dock, { styleId })loads a saved style. New optionsbuildingandbuildingOutline. Entrance colors show confidence and stay the same on every map. - Style editor page: pick colors, preview them on dock tiles, save, copy the code.
0.13.0 (2026-10-01)
GET /v1/versions: API versions, deprecated routes, sunset days. No key, not counted.- Versioning policy: breaking changes only in
/v2. A route is deprecated at least 365 days before its sunset. Its answers carryDeprecation,SunsetandLinkheaders; from the sunset day it answers410 sunset. No route is deprecated today. - Status page: API and docs site up or down now, share up per day over 90 days, incidents, deprecated routes. Updated every 5 minutes.
0.12.0 (2026-10-01)
GET /v1/usageaddsplan(freeorcustom) andupgrade. For a free org,upgradeholds the partner plan: 10,000 requests, 100,000 tiles and 200 door misses a day, 1,200 tiles a minute, and the scopeschanges:readandconfirm:write.nullfor a partner org. Prices are not published yet.- Usage page: your plan, and for a free key its limits next to the partner plan.
0.11.0 (2026-10-01)
POST /v1/signup: a free key at once. No email, no name, no body. The key is in the answer only; store it then. Free keys read only (door:read,geocode:read,route:read,tiles:read): 60 requests and 600 tiles a minute; 1,000 requests, 25,000 tiles and 50 door misses a day. Three keys per address per day, and a daily cap on free keys for everyone (429 signup_limited, seeRetry-After). Both reset at 00:00 Ulaanbaatar time.- Playground: a "Get a free key" button fills the key field.
0.10.0 (2026-10-01)
GET /v1/route?from=&to=&mode=: a route between two points or door codes. A code routes to its entrance, not the building centre.fromandtoare a code (7QSVX) orlon,lat(106.9301,47.9201).mode:driving(default),walking,cycling. The answer has a GeoJSON line, metres and seconds. The line follows the road network and ends where it is nearest the entrance; the straight distance left to the entrance isto.gap_mand is not drawn as road. Needsroute:read, anddoor:readtoo when a code is used. An unknown code is404and counts against the daily door miss cap. No routes to or from restricted sites (422 not_accepted). Coverage: Ulaanbaatar. No traffic yet.
0.9.0 (2026-10-01)
- CORS: browsers on any site can call the API with a key in the
Authorizationheader. No cookies are used. Preflight requests need no key and are not counted.Retry-After,X-Quota-*andX-Rate-Limit-*are readable from browser code. - Usage page: paste a key to see today's calls, tiles and door misses against your caps, and up to 90 days of history. The key is not stored.
0.8.1 (2026-10-01)
info.versioninopenapi.jsonnow matches the API release (it was stuck at 0.5.0).
0.8.0 (2026-10-01)
POST /v1/door/report: report a wrong, missing, duplicate or unmapped door.kind:wrong_location(code, the realpoint, within 500 m),missing(code),duplicate(code,duplicate_of),unmapped(pointonly). Answer202,status: queued. Door data does not change until a person reviews the report. The same report from the same organization within 24 h returns the first one (already_queued). Scopedoor:read. An unknown code is404and counts toward the daily door miss cap. Runs of 6 or more digits and e-mail addresses innoteare removed before storing.
0.7.1 (2026-10-01)
- JSON and tiles are compressed, gzip or brotli by
Accept-Encoding. Tiles are 2.2x smaller on average, search JSON 4 to 5x. Responses under 1 KB are sent as they are. Map libraries, browsers andcurl --compresseddecompress on their own. Every response carriesVary: Accept-Encoding.
0.7.0 (2026-10-01)
GET /v1/usage: your organization's calls, tiles and door misses per Ulaanbaatar day, the caps, and the calling key's scopes and per-minute limits. Any key works. Free: not counted, not stopped by the daily quota.?days=1 to 90, default 7. Today includes calls made a moment ago.
0.6.1 (2026-10-01)
GET /v1/geocode: swapped, missing or extra letters in short names. A word of 4 to 12 letters that is not in the index is corrected to the nearest indexed word (1 or 2 edits; two neighbouring letters swapped count as 1) and searched again. "Банягол" now finds Bayangol district. A corrected hit'sscoreis 0.1 plus 0.05 per edit lower.
0.6.0 (2026-10-01)
- Daily quotas per organization, shared by its keys, reset at 00:00 Ulaanbaatar time. Defaults 10,000 requests and 100,000 tiles a day. Over the quota:
429 quota_exceeded. HeadersX-Quota-Limit,-Remaining,-Reset. - Tiles have their own per-minute budget per key (default 1,200), apart from the request limit. A map view no longer uses up the request limit.
GET /v1/door/{code}: lookups that find no door are capped at 200 a day per organization (code guessing). At the cap:429 miss_capuntil midnight. Other routes keep working.
0.5.1 (2026-10-01)
GET /v1/geocode: a query equal to a place's name ranks that place above longer names that contain it. "Khan-Uul district" now returns the district first, not "Khan-Uul District Sports Center".scorevalues for exact names are 0.3 higher.
0.5.0 (2026-10-01)
GET /v1/tiles/{source}(TileJSON) andGET /v1/tiles/{source}/{z}/{x}/{y}(vector tile). Sourcesbuilding,entrance. Needstiles:read. Empty tile is 204.- Base URL is
https://api.dock.mn(was api.map.dock.mn in earlier docs; not live yet).
0.4.0 (2026-10-01)
GET /v1/geocode?q=: doors, registered businesses, buildings, districts, khoroos, areas, streets and landmarks by name. Cyrillic and Latin, informal spellings, ordinals, one or two typos. Best first, withscore.GET /v1/reverse?lat=&lon=: nearest doors, businesses, buildings and landmarks withinradius(default 50 m), plus the district and khoroo covering the point.- Same result order as a door: official address first, code after it. Both need
geocode:read. - Nothing inside a restricted site is returned. Query text is not logged.
0.3.0 (2026-10-01)
GET /v1/door/{code}is live. Returns the door: official address first, then code, natural address (mn, en, short and long), entrance point, entrance kind and number, confidence 0 to 4, source, instructions.7QSVX 17: the flat is returned when it is on record.404 not_foundfor unknown codes,410 retired_codefor retired doors.
0.2.0 (2026-10-01)
- API keys. Every
/v1route needs a key with the route's scope.GET /v1/door/{code}needsdoor:read. - Per-key requests per minute,
429withRetry-After. Rate limit headers on every keyed response. - Daily usage per key and endpoint, by Ulaanbaatar day.
0.1.0 (2026-09-28)
GET /health,GET /health/ready.GET /v1/door/{code}: validates code format. Lookup returns501.- OpenAPI at
/openapi.json.