JS SDK reference
Every export of @dock/js as the TypeScript compiler declares it, with its doc comment: the same text as the package's dist/*.d.ts, one block per source file. Written from sdk/js/src by scripts/sdk-reference.ts; make test-docs fails when this page is older than the code, or when an export has no doc comment. How to use each call, with examples: JS SDK.
index.ts
ts
/** This SDK version, as in package.json. */
export const VERSION = "0.82.0";client.ts
ts
/** The dock API, used when ClientOptions has no baseUrl. */
export const DEFAULT_BASE_URL = "https://geo.dock.mn";
/** Same shape as the API's key check (api/src/modules/auth/api-key.ts). */
export const KEY_RE: RegExp;
/** Ends per side in one matrix call, as the API's MATRIX_MAX (api/src/modules/route/route-input.ts). */
export const MATRIX_MAX = 10;
/** Contours in one isochrone call, as the API's CONTOURS_MAX (api/src/modules/route/route-input.ts). */
export const ISOCHRONE_CONTOURS_MAX = 4;
/** The longest contour in minutes, as the API's CONTOUR_MINUTES_MAX. */
export const ISOCHRONE_MINUTES_MAX = 60;
/** Points in one matching call, as the API's TRACE_MAX (api/src/modules/route/route-input.ts); at least 2. */
export const MATCHING_MAX = 100;
/** Ends a route passes between from and to, as the API's VIA_MAX (api/src/modules/route/route-input.ts). */
export const ROUTE_VIA_MAX = 10;
/** Stops in one optimizedTrips call, as the API's STOPS_MAX (api/src/modules/route/route-input.ts); at least 2. */
export const OPTIMIZED_TRIPS_MAX = 12;
/** The widest tilequery radius in metres, as the API's TILEQUERY_RADIUS_MAX_M (api/src/modules/tiles/tilequery.ts). */
export const TILEQUERY_RADIUS_MAX_M = 1000;
/** Features in one tilequery answer, as the API's TILEQUERY_LIMIT_MAX; 5 when no limit is sent. */
export const TILEQUERY_LIMIT_MAX = 50;
/** The kinds reverse can keep, as the API's REVERSE_KINDS (api/src/modules/search/search-store.ts). */
export const REVERSE_TYPES: readonly ReverseType[];
/** The kinds geocode can keep, as the API's PLACE_KINDS (api/src/modules/search/search.dto.ts). */
export const GEOCODE_TYPES: readonly PlaceKind[];
/** The categories nearby takes, as the API's NEARBY_CATEGORIES (api/src/modules/search/search.dto.ts). */
export const NEARBY_CATEGORIES: readonly NearbyCategory[];
/** The fetch the client calls: globalThis.fetch by default, your own for tests or old runtimes. */
export type Fetch = (input: string, init?: RequestInit) => Promise<Response>;
/** What new Client() takes. Only key is required. */
export interface ClientOptions {
/** dk_ and 32 characters. Lives here only; every call and every tile takes it from here. */
key: string;
/** Default https://geo.dock.mn. */
baseUrl?: string;
/** For tests or old runtimes. Default globalThis.fetch. */
fetch?: Fetch;
/**
* Called once per route when an answer says the route is deprecated. Default: one console.warn per route.
* Pass a function to send it to your own logs, or `() => {}` to stay quiet.
*/
onDeprecation?: (notice: DeprecationNotice) => void;
/**
* Share geocode searches and picks with dock (PLAN.md 18). Default false. Set true only after the user opted in;
* setTelemetry() changes it later. Sent: the typed text, the result count, the pick. Not sent: time, point,
* device, session.
*/
telemetry?: boolean;
/**
* Places the user picked, kept on this device so geocode still answers when the network is down (PLAN.md 18).
* Default on, in memory, 100 places. Pass { store: localStorage } to keep them across page loads, false for none.
* Nothing here is sent anywhere.
*/
placeCache?: PlaceCacheOptions | false;
}
/** A route the API marks deprecated (Deprecation, Sunset and Link headers, PLAN.md 17). */
export interface DeprecationNotice {
/** "GET /v1/door/:code", the route, not the URL: no codes, no query. */
route: string;
/** Day it was announced, YYYY-MM-DD Ulaanbaatar, or null if the header did not parse. */
deprecated: string | null;
/** Day the route stops answering (410 sunset from 00:00 Ulaanbaatar), YYYY-MM-DD, or null. */
sunset: string | null;
/** The status page with the replacement route, or null. */
link: string | null;
}
/** The Idempotency-Key format: 1 to 64 of letters, digits and ._:- */
export const IDEMPOTENCY_KEY_RE: RegExp;
/** Options every call takes. signal aborts the request. */
export interface CallOptions {
signal?: AbortSignal;
}
/** What startTrace() takes. */
export interface TraceOptions {
/** Must be 'opt_in': start only after the user said yes to sharing this trip. */
consent: 'opt_in';
/** Default navigator.geolocation. A native bridge or a test can pass its own. */
source?: PositionSource;
/** Fixes less accurate than this, in meters, are left out. Default 50. Accuracy is never sent. */
maxAccuracyM?: number;
}
/** What startProbes() takes. */
export interface ProbeOptions {
/** Default navigator.geolocation. A native bridge or a test can pass its own. */
source?: PositionSource;
/** Fixes less accurate than this, in meters, are left out. Default 50. Accuracy is never sent. */
maxAccuracyM?: number;
/** Seconds between sends, 5 or more. Default 30. */
intervalS?: number;
/** A failed send, the batch dropped: probes are live, a late one is not counted. On feature_off probes stop. */
onError?: (err: DockError) => void;
}
/** What startEventPositions() takes. */
export interface EventPositionOptions {
/** Must be 'opt_in': start only after the participant said yes to sharing their position with the organizer. */
consent: 'opt_in';
/** Default navigator.geolocation. A native bridge or a test can pass its own. */
source?: PositionSource;
/** Fixes less accurate than this, in meters, are left out. Default 50. Accuracy is never sent. */
maxAccuracyM?: number;
/** Seconds between sends, 5 or more. Default 10. */
intervalS?: number;
/** A failed send, the batch dropped. On event_closed, not_found or feature_off sending stops. */
onError?: (err: DockError) => void;
}
/**
* An API refusal or a failed request. `code` is the API's `error` field (invalid_code, not_found,
* rate_limited, quota_exceeded, miss_cap, ...), or `network` when no response came back.
*/
export class DockError extends Error {
/** HTTP status, 0 when no response came back. */
readonly status: number;
/** The API's error field, or network. */
readonly code: string;
/** Seconds, from Retry-After on 429 and 503. null otherwise. */
readonly retryAfter: number | null;
constructor(status: number, code: string, message: string, retryAfter?: number | null);
}
/** Shape MapLibre's transformRequest returns. */
export interface RequestParameters {
url: string;
headers?: Record<string, string>;
}
/**
* The dock API v1. JSON calls send the key in the Authorization header. Map requests to this API
* (TileJSON, tiles) get ?access_token= through transformRequest: one request per tile, no CORS preflight,
* and the API never logs query strings.
*/
export class Client {
/** The API this client calls, without a trailing slash. */
readonly baseUrl: string;
constructor(options: ClientOptions);
/**
* Name or address to places, best first. Needs geocode:read. limit 1 to 20. With no network (DockError code
* network) the answer comes from places the user picked on this device, with `offline: true`, score = times picked,
* fields as last seen; with none of them matching, the network error is thrown. `proximity` is [lon, lat], where the
* person is or the map centre: among names the query starts equally well, nearer places come first. It is sent with
* this request only, never kept or sent with telemetry; offline answers do not read it. `types` (API 0.70.0, Mapbox's
* types): only these kinds, chosen before the limit, offline too; sent once each, in the order given. Absent or
* empty: every kind. Another kind is refused here with the API's 400 invalid_types: no request.
*/
geocode(q: string, options?: CallOptions & {
limit?: number;
proximity?: LonLat;
types?: PlaceKind[];
}): Promise<GeocodeAnswer>;
/**
* The user's consent changed. Off drops every search not yet sent. On, searches go in batches of 50 and when the
* page is hidden (tab closed, app switched).
*/
setTelemetry(on: boolean): void;
/**
* The user chose this result from a geocode list. It is kept on this device for offline search (placeCache), and
* with telemetry on it closes the search sent to dock.
*/
pick(result: GeocodeResult): void;
/** Places the user picked on this device, most picked first: a "your places" list. [] with placeCache false. */
places(): Place[];
/** The user asked to forget their places: clears memory and the store. */
forgetPlaces(): void;
/**
* Sends what is waiting now, for example when an order is placed. null when telemetry is off or nothing waits.
* A failed send rejects and its searches are not kept: no retry, nothing stored in the browser.
*/
flushTelemetry(): Promise<SearchTelemetryReceipt | null>;
/**
* Starts recording the user's trip (PLAN.md 15). Off until called; call it only after the user said yes. Points
* stay in memory, never in browser storage. Throws when consent is not 'opt_in', when a trip is already being
* recorded, or when there is no geolocation.
*/
startTrace(options: TraceOptions): void;
/** true while a trip is being recorded. */
get tracing(): boolean;
/**
* Stops recording and sends the trip to POST /v1/traces, split every 20,000 points. Needs trace:write. Keep each
* receipt's id: deleteTrace(id) erases the trip. [] when nothing was recorded or under 2 points. A failed send
* rejects and keeps the unsent points in memory: call stopTrace() again to retry, cancelTrace() to drop them.
*/
stopTrace(options?: CallOptions): Promise<TraceReceipt[]>;
/** Stops recording and drops the trip unsent: the user changed their mind. */
cancelTrace(): void;
/**
* Starts sending this vehicle's position for live traffic (POST /v1/traffic/probes), every 30 seconds while fixes
* come. For fleets whose contract with dock covers their vehicles' positions: the API answers feature_off to any
* other organization, and probes stop. Needs trace:write. No driver, phone, plate or device is sent: the vehicle is
* a random token made anew every 15 minutes. Points stay in memory, never in storage. Throws when probes are
* already on or there is no geolocation.
*/
startProbes(options?: ProbeOptions): void;
/** true while probes are on. */
get probing(): boolean;
/** Stops probes and sends what is left. null when nothing was left to send. */
stopProbes(options?: CallOptions): Promise<ProbeReceipt | null>;
/**
* Live speed per road (GET /v1/traffic): the last 15 minutes that have ended, only roads that 5 or more fleet
* vehicles passed. km_h is their moving meters over moving seconds, to 0.1. No road means too few vehicles, not an
* empty road. bbox inside Mongolia, sides at most 1 degree. Needs insights:read.
*/
traffic(bbox: BBox, options?: CallOptions): Promise<Traffic>;
/**
* The usual speed per road for this hour band (GET /v1/traffic/typical), for a map layer. Never live: show it as
* typical.label ("ердийн"). Needs tiles:read only, the scope of a map key; counted like a tile request. No road
* means no speed: features can be empty. bbox inside Mongolia, sides at most 1 degree.
*/
trafficTypical(bbox: BBox, options?: CallOptions): Promise<TypicalTraffic>;
/**
* Opens an event for its live page and heat map (POST /v1/events): a name, an area and a time. For organizers whose
* contract with dock is signed; any other organization gets feature_off. Needs trace:write. Every point is erased 15
* minutes after the end; the heat cells stay.
*/
createEvent(input: EventInput, options?: CallOptions): Promise<DockEvent>;
/** One of your organization's events (GET /v1/events/{id}). Needs trace:read. */
event(id: string, options?: CallOptions): Promise<DockEvent>;
/**
* Starts sending this participant's position to an event (POST /v1/events/{id}/positions), every 10 seconds while
* fixes come, only after the participant said yes. No name, bib, phone or device is sent: the participant is a random
* token made at start. Points stay in memory, never in storage. Sending stops on event_closed, not_found or
* feature_off. Needs trace:write. Throws when already sending or there is no geolocation.
*/
startEventPositions(eventId: string, options: EventPositionOptions): void;
/** true while positions are being sent to an event. */
get sendingPositions(): boolean;
/** Stops sending positions and sends what is left. null when nothing was left to send. */
stopEventPositions(options?: CallOptions): Promise<EventPositionsReceipt | null>;
/**
* Live dots for your event's page (GET /v1/events/{id}/live): each participant's last point of the last 2 minutes, to
* 4 decimals, sorted by place, nothing about the participant; empty when fewer than 5 are live. Needs trace:read.
*/
eventLive(id: string, options?: CallOptions): Promise<EventLive>;
/**
* Heat per area for your event (GET /v1/events/{id}/heat): participants per 100 m cell, cells with 5 or more, for the
* whole event or one 15-minute window (an ISO time or Date on the quarter hour). Needs trace:read.
*/
eventHeat(id: string, options?: CallOptions & {
window?: string | Date;
}): Promise<EventHeat>;
/** One of your organization's trips as stored, GeoJSON [lon, lat, t]: the export. Needs trace:write. */
trace(id: string, options?: CallOptions): Promise<Trace>;
/** Erases a trip's points and times now. The id is kept, never reused. Twice is fine. Needs trace:write. */
deleteTrace(id: string, options?: CallOptions): Promise<TraceDeleted>;
/**
* Trips per 500 m cell in a month, all partners together (PLAN.md 18, 19). `month` is YYYY-MM (Ulaanbaatar),
* published 14 days after it ends (400 month_open before). `bbox` inside Mongolia, sides at most 1 degree. A cell
* shows only with trips from 3 or more organizations; no cell means too few, not zero. Needs insights:read.
*/
traceCells(month: string, bbox: BBox, options?: CallOptions): Promise<TraceCells>;
/**
* Moving speed per 500 m cell in a month, all partners together: where traffic is slow (PLAN.md 18, 19). The same
* grid, month and bbox as traceCells. km_h is the counted meters over the counted seconds, to 0.1; parking and the
* last 200 m are left out. Per cell, not per road. A cell shows only under the trip cells rule; no cell means too
* few trips, not a slow road. No trip count. Needs insights:read.
*/
traceSpeeds(month: string, bbox: BBox, options?: CallOptions): Promise<TraceSpeeds>;
/**
* Moving speed per road in a month, all partners together: speed profiles per road (PLAN.md 18, 19). Each trip is
* matched to OSM ways with Valhalla when it is stored; a segment counts on a way under the traceSpeeds rule when both
* its points matched that way. km_h is the counted meters over the counted seconds, to 0.1. A road shows only under
* the trip cells rule; no road means too few trips, not a slow road. The line is dock's current road with that OSM id.
* No trip count. `band` gives one hour band's trips only, under the same rule on its own trips; left out, all hours.
* `rule.hours` says which. band needs API 0.49.0. Needs insights:read.
*/
traceRoads(month: string, bbox: BBox, options?: CallOptions & {
band?: RoadBand;
}): Promise<TraceRoads>;
/**
* Your organization's own trips per cell, the same grid: your dashboard (PLAN.md 18, 20.13). Every cell with one of
* your trips, no other organization read. The month you are in is answered too; `closed` is true once the counts are
* final. Needs trace:read, a read-only scope: trace:write does not read it.
*/
ownTraceCells(month: string, bbox: BBox, options?: CallOptions): Promise<OwnTraceCells>;
/**
* Your organization's arrival minutes in a month: how long your trips spend in the last 200 m, parking and the walk
* to the door included (PLAN.md 18). `month` is YYYY-MM (Ulaanbaatar). Month totals with median, p75 and p90, trips
* per minute, and up to 100 end cells on the trip cells grid, slowest first. No other organization is read.
* `closed` is true from 14 days after the month ends. Needs trace:read.
*/
ownArrivals(month: string, options?: CallOptions): Promise<OwnArrivals>;
/**
* When your organization's trips start in a month: each trip once, at the Ulaanbaatar weekday and hour of its first
* stored point (PLAN.md 18). `month` is YYYY-MM (Ulaanbaatar). Month total, 7 weekday counts, 24 hour counts, and
* every weekday and hour with trips. No other organization is read. `closed` is true from 14 days after the month
* ends. Needs trace:read.
*/
ownHours(month: string, options?: CallOptions): Promise<OwnHours>;
/**
* When trips start in a month, all partners together: road load by weekday and hour (PLAN.md 18, 19). `month` is
* YYYY-MM (Ulaanbaatar), published 14 days after it ends (400 month_open before). A weekday and hour shows only under
* the trip cells rule; no slot means too few, not zero. No total. Needs insights:read.
*/
allHours(month: string, options?: CallOptions): Promise<AllHours>;
/**
* Where and when trips end in a month, all partners together: trips per khoroo and Ulaanbaatar hour of arrival
* (PLAN.md 18, 19). `month` is YYYY-MM (Ulaanbaatar), published 14 days after it ends (400 month_open before). A
* khoroo and hour shows only under the trip cells rule; no hour means too few, not zero. No khoroo total. Current
* khoroo polygons only. Needs insights:read.
*/
traceKhoroos(month: string, options?: CallOptions): Promise<TraceKhoroos>;
/**
* Your organization's own trips per khoroo and Ulaanbaatar hour of arrival in a month: your dashboard (PLAN.md 18,
* 20.13). `month` is YYYY-MM (Ulaanbaatar), an open month too; `closed` once final. No group rule. Each trip counts
* once: in one khoroo, or in `no_khoroo` where no current khoroo polygon holds its end. Needs trace:read.
*/
ownKhoroos(month: string, options?: CallOptions): Promise<OwnKhoroos>;
/**
* Your organization's own trips per kind of building at the trip end in a month: what kind of deliveries you make
* (PLAN.md 18, 20.13). `month` is YYYY-MM (Ulaanbaatar), an open month too; `closed` once final. No group rule. Each
* trip counts once, by the building type when it was stored; `unknown` is a building with no type, `none` no building
* within 30 m. Needs trace:read.
*/
ownKinds(month: string, options?: CallOptions): Promise<OwnKinds>;
/**
* Point to the places at and around it, nearest first. Needs geocode:read. `types` (API 0.69.0, Mapbox's types): only
* these kinds, chosen before the limit, so limit doors are the nearest doors; sent once each, in the order given.
* Absent or empty: every kind. Another kind is refused here with the API's 400 invalid_types: no request. `point` is [lon, lat], longitude first: a Leaflet
* latlng goes in as [lng, lat]. Sent as lon; the API reads lng as lon too, for a hand-written request.
*/
reverse(point: LonLat, options?: CallOptions & {
radius?: number;
limit?: number;
types?: ReverseType[];
}): Promise<ReverseResponse>;
/**
* Places of these categories near a point, nearest first, each with its category and distance (API 0.80.0). Needs
* geocode:read. A chip such as "Эмийн сан" is ['pharmacy'], "Хоол" ['restaurant', 'cafe']. radius in metres, 1000
* when absent, at most 10000; limit 10 when absent, at most 20. Categories are sent once each, in the order given;
* none, or another category, is refused here with the API's 400 invalid_category: no request. `point` is [lon, lat], longitude first: a Leaflet
* latlng goes in as [lng, lat]. Sent as lon; the API reads lng as lon too, for a hand-written request.
* photo: true adds each landmark's or building's photo (or null) to its row, in the same read.
*/
nearby(point: LonLat, categories: NearbyCategory[], options?: CallOptions & {
radius?: number;
limit?: number;
photo?: boolean;
}): Promise<NearbyResponse>;
/**
* A door from its 5-character code. Cyrillic lookalike keys are accepted. `unit` is the flat or office
* typed after the code ("7QSVX 17"). Needs door:read. 404 not_found, 410 retired_code, 400 invalid_code.
* A text that cannot be a code (parseDoorCode) is refused here with the API's 400 invalid_code: no request.
*/
door(code: string, options?: CallOptions & {
unit?: string;
}): Promise<Door>;
/**
* The address unit at a point, for a tap on the map: the хашаа that holds it, else the smallest building (GET
* /v1/door/at). Needs door:read. `code` is null while the unit has none. 404 not_found when no unit holds the point,
* and always inside a restricted site. `point` is [lon, lat], longitude first: a Leaflet latlng goes in as
* [lng, lat]. Sent as lon; the API reads lng as lon too, for a hand-written request.
*/
doorAt(point: LonLat, options?: CallOptions): Promise<UnitAt>;
/**
* Register a spoken name for one of your doors ("Сансар бэйкери" for 7QSVX), held for a year (API 0.52.0). Your own
* name with another code moves to that door. Needs names:write and the door_names feature (403 feature_off until
* dock opens it for your organization). 409 name_taken, 410 retired_code, 404 not_found, 422 too_many (50 names).
*/
registerDoorName(name: string, code: string, options?: CallOptions): Promise<DoorNameReceipt>;
/** Your organization's door names, A to Z, expired ones flagged until another organization takes them. Needs names:write. */
doorNames(options?: CallOptions): Promise<OwnDoorNames>;
/**
* The door a name names, as door() answers it, official address first. Any spelling of the name finds it: case,
* spaces, punctuation and Latin lookalike letters. Needs door:read and the door_names feature. 404 not_found counts
* against the door miss cap. A name the API would refuse (no letter, a phone number or an e-mail) is refused here
* with its 400: no request, so it never travels in a URL.
*/
doorByName(name: string, options?: CallOptions): Promise<NamedDoor>;
/**
* A one-time code that lets another organization claim one of your names (API 0.53.0). Shown once; a new one
* replaces the old. Works for 7 days, or until the name expires if sooner. Needs names:write.
*/
doorNameTransferCode(name: string, options?: CallOptions): Promise<DoorNameTransfer>;
/** Clears the waiting transfer code of one of your names. `cancelled` is false when none was waiting. Needs names:write. */
cancelDoorNameTransfer(name: string, options?: CallOptions): Promise<DoorNameTransferCancel>;
/**
* Take a name with the transfer code its holder gave you. The door and expires_at stay; the code is used up. Needs
* names:write. 409 transfer_refused (wrong, cleared or timed out; counts a door miss), 409 already_yours, 422 too_many.
*/
claimDoorName(name: string, transferCode: string, options?: CallOptions): Promise<DoorNameClaim>;
/**
* Write how guests find the entrance of the door one of your names points at, in mn, en or both (API 0.58.0). The
* note waits for a person to review it; door() serves the notes the map holds until then. Writing again replaces
* your waiting text. Needs names:write and the hotel.notes feature (403 feature_off until dock opens it). 400
* personal_data for a phone number or e-mail in a note, invalid_notes, invalid_body; 404 not_found for a name your
* organization does not hold. A name the API would refuse is refused here with its 400: no request.
*/
writeDoorNotes(name: string, notes: DoorNotesBody, options?: CallOptions): Promise<DoorNoteReceipt>;
/**
* Your notes for the door one of your names points at, each waiting, accepted or refused (API 0.59.0). Never another
* organization's. Needs names:write and the hotel.notes feature; 404 not_found as for writeDoorNotes.
*/
doorNotes(name: string, options?: CallOptions): Promise<DoorNotes>;
/**
* Send a gate photo for the door one of your names points at: the entrance as a guest sees it (API 0.60.0). One
* JPEG as the camera saved it, up to 2 MB, 320 to 8000 px a side. The API removes its metadata (GPS, the phone,
* the time) before it stores it. A person reviews it and refuses one with a face, a car plate or a phone number.
* Sending again replaces your waiting photo. Needs names:write and the hotel.notes feature (403 feature_off). 400
* not_jpeg, invalid_photo, photo_too_small, photo_too_big; 413 too_large; 404 not_found as for writeDoorNotes.
*/
sendDoorPhoto(name: string, jpeg: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<DoorPhotoReceipt>;
/**
* The gate photo a person accepted last for the door one of your names points at, for your app to show your guests
* (API 0.61.0). Never a waiting or refused photo, never another organization's. Show it inside your app only, never
* on a public page. Pass the sha256 you hold: when it is still the photo, jpeg is null and no bytes travel. Needs
* names:write and the hotel.notes feature. 404 not_found also when no photo of yours was accepted.
*/
doorPhoto(name: string, options?: CallOptions & {
sha256?: string;
}): Promise<DoorPhoto>;
/**
* Route between two ends, each a building code or [lon, lat]. A code routes to the street side of its building or the gate of its yard; `to.gap_m` is the
* walk left from the road. Needs route:read, and door:read for a code. 422 no_route, end_in_closure (an end
* inside a closure active today; closureAt checks offline), closures_over_limit, 404 not_found. departAt (API 0.50.0):
* a driving route's measured reads the speeds of its hour band; a Date is sent in UTC, a string as written (ISO 8601
* with an offset). 400 invalid_depart_at. via (API 0.67.0): up to ROUTE_VIA_MAX ends passed on the way, in order,
* each a code or [lon, lat]; the answer has one leg per hop and each via end where the line passes it. An empty list
* is none. A long list, or a code holding ";", is refused here with the API's 400 invalid_via: no request. 404
* not_found names a via end by its place ("via[1]: ...").
*/
route(from: string | LonLat, to: string | LonLat, options?: CallOptions & {
mode?: RouteMode;
departAt?: Date | string;
via?: (string | LonLat)[];
}): Promise<Route>;
/**
* Free-flow time and distance from every end in `from` to every end in `to`, in one call (API 0.63.0): up to
* MATRIX_MAX by MATRIX_MAX, each end a building code or [lon, lat] as in route. `durations_s[i][j]` goes from `from[i]` to
* `to[j]`; null where no path joins them. Needs route:read, and door:read for a code. An empty or long list, or a
* code holding ";", is refused here with the API's 400 invalid_from or invalid_to: no request. 404 not_found names
* the end by its place ("to[1]: ..."); 422 no_route, end_in_closure, closures_over_limit as for route.
*/
matrix(from: (string | LonLat)[], to: (string | LonLat)[], options?: CallOptions & {
mode?: RouteMode;
}): Promise<Matrix>;
/**
* The area reached from one end within each of `contoursMinutes` (API 0.64.0), as a GeoJSON FeatureCollection: one
* Polygon or MultiPolygon per contour, the largest first. A map takes it as a source as it is. `from` is a building code
* (from its street side or gate) or [lon, lat] as in route. 1 to ISOCHRONE_CONTOURS_MAX whole minutes from 1 to
* ISOCHRONE_MINUTES_MAX, increasing; anything else is refused here with the API's 400 invalid_contours: no request.
* Needs route:read, and door:read for a code. 404 not_found; 422 no_route (no road near from), end_in_closure,
* closures_over_limit, not_accepted as for route. Free-flow times: no departAt.
*/
isochrone(from: string | LonLat, contoursMinutes: number[], options?: CallOptions & {
mode?: RouteMode;
}): Promise<Isochrone>;
/**
* A GPS trace matched to the road network (API 0.65.0): the roads it went along, one matching per unbroken piece, and
* one tracepoint per point, null where no road was found or the point is in a restricted site. `coordinates` is 2 to
* MATCHING_MAX [lon, lat] in the order recorded; `timestamps`, optional, one whole Unix second per point, never going
* back. Anything else is refused here with the API's 400 invalid_coordinates or invalid_timestamps: no request. Needs
* route:read. 422 no_match (no road near the trace), not_accepted (fewer than 2 points outside restricted sites).
* Closures are not avoided. The API neither stores nor logs the trace; to share a trip, use startTrace.
*/
matching(coordinates: LonLat[], options?: CallOptions & {
timestamps?: number[];
mode?: RouteMode;
}): Promise<Match>;
/**
* The order to visit stops in with the least time on the road, and the trip in that order (API 0.66.0). `stops` is 2
* to OPTIMIZED_TRIPS_MAX ends, each a building code (to its street side or gate) or [lon, lat] as in route; the trip starts at the
* first. `roundtrip` true, the API's default, goes back to it; false ends at the last sent. `waypoints` are the stops
* in the order sent, each with `waypoint_index`, its place in the trip. A short or long list, or a code holding ";", is
* refused here with the API's 400 invalid_coordinates: no request. Needs route:read, and door:read for a code. 404
* not_found names the stop by its place ("coordinates[1]: ..."); 422 no_route (a stop no road reaches), not_accepted,
* end_in_closure, closures_over_limit as for route. Free-flow times, no time at the stops.
*/
optimizedTrips(stops: (string | LonLat)[], options?: CallOptions & {
roundtrip?: boolean;
mode?: RouteMode;
}): Promise<OptimizedTrip>;
/**
* The buildings and entrances at or near a point (API 0.68.0), as Mapbox's Tilequery API: what a tap on the map is
* on. `sources` is building, entrance or both (the default), each once. `radius` in metres, 0 to
* TILEQUERY_RADIUS_MAX_M, sent to the centimetre; 0, the API's default, finds the building the point is inside, and an
* entrance needs a radius. `limit` 1 to TILEQUERY_LIMIT_MAX, the API's default 5. `geometry` keeps one type. GeoJSON,
* nearest first: each feature the Point of it nearest the point sent, its tile fields and `tilequery` (distance in
* metres, geometry, layer). No building codes: `door` takes a code. Anything the API would refuse is refused here with its
* error: 404 not_found (a source), 400 invalid_point, invalid_radius, invalid_limit, invalid_geometry; no request.
* Needs tiles:read; counts against rate_limit and daily_quota. Coverage: Ulaanbaatar.
*/
tilequery(point: LonLat, options?: CallOptions & {
sources?: TilequerySource[];
radius?: number;
limit?: number;
geometry?: TilequeryGeometry;
}): Promise<Tilequery>;
/**
* Road closures not over today (PLAN.md 20.14), each with the ring Valhalla avoids. No partner name, no note. Keep
* the answer for offline use; excludePolygons(closures, day) picks the rings for a route day. ids_sha256 changes
* when a closure is added or lifted. Needs route:read.
*/
closures(options?: CallOptions): Promise<ClosuresResponse>;
/**
* Report a stretch or a crossing that cannot be driven (PLAN.md 20.14). Kept forever: never edited or deleted, lift
* it when the road opens. Needs closures:write. Pass `idempotencyKey` (a uuid fits), and the same key and report when
* you retry after a timeout: the API answers the first report and stores nothing twice; the same key with another
* report is 422 idempotency_key_reused. A key the API would refuse is refused here with its 400 bad_idempotency_key:
* no request. 400 note_has_phone, outside_mongolia, line_too_long, restricted and the rest come from the API.
*/
reportClosure(report: ClosureReport, options?: CallOptions & {
idempotencyKey?: string;
}): Promise<Closure>;
/** One of your organization's closure reports as stored, note included: your export. Needs closures:write. 404 not_found for others' ids. */
closure(id: string, options?: CallOptions): Promise<Closure>;
/**
* The road is open again: lift one of your reports from `on` (YYYY-MM-DD Ulaanbaatar), today by default, never a
* later day (400 lift_in_future) nor before its from (400 lift_before_from). Lifting twice keeps the first day.
* Needs closures:write.
*/
liftClosure(id: string, options?: CallOptions & {
on?: string;
}): Promise<Closure>;
/** Tell dock a door is wrong. A person reviews it; door data does not change until then. Needs door:read. */
reportDoor(report: DoorReport, options?: CallOptions): Promise<DoorReportReceipt>;
/**
* One place again, by the id geocode, reverse or changes gave: the place fields plus `parents`. Needs geocode:read.
* 410 retired (the id is never reused), 404 not_found (unknown, restricted, or added since the last index refresh).
*/
place(id: string, options?: CallOptions): Promise<Address>;
/**
* A building's entrances in one call, for its card: each with its code once a door is minted and its level
* (matched, partial, none), numbered first (API 0.84.0). The id is a building's, from geocode, reverse or the
* building tile. Needs geocode:read. 410 retired, 404 not_found (unknown, not a building, or restricted).
*/
entrances(buildingId: string, options?: CallOptions): Promise<BuildingEntrances>;
/**
* The organizations registered in a building, by floor, for "Энд байгаа байгууллагууд" on an office building or
* mall card. The id is a building's, from geocode, reverse or the building tile, or its unit code (a хашаа code
* answers an empty list). Only organizations matched to an
* official registration are listed; a floor or unit the register does not give is null. Needs geocode:read.
* 410 retired, 404 not_found (unknown, not a building, or restricted).
*/
directory(buildingId: string, options?: CallOptions): Promise<BuildingDirectory>;
/**
* The building entrances nearest a point, nearest first, each as entrances() lists it plus its building and
* distance_m (API 0.85.0): for a long-press on the map or "Энд байгаа хаалга" from the user's position. Generated
* entrances count (source generated, confidence 0: show them as таамаг). radius in metres, 50 when absent, at most
* 1000; limit 1 when absent, at most 20. Needs geocode:read. `point` is [lon, lat], longitude first: a Leaflet
* latlng goes in as [lng, lat]. Sent as lon; the API reads lng as lon too, for a hand-written request.
*/
entrancesNear(point: LonLat, options?: CallOptions & {
radius?: number;
limit?: number;
}): Promise<NearEntrances>;
/**
* Indoor maps at a map position (API 0.105.0): venues whose outline holds the point or lies within 200 m, nearest
* first, at most 10, each with its id, name, category, distance and floors. latlng goes in as [lng, lat]. Needs door:read.
*/
venuesNear(point: LonLat, options?: CallOptions): Promise<NearVenues>;
/**
* A building with an indoor map (API 0.100.0): its name, outline and floors, lowest first. The id is the
* `indoor.venue_id` a geocode row carries. Needs door:read. 404 not_found for an unknown id.
*/
venue(venueId: string, options?: CallOptions): Promise<Venue>;
/**
* One floor of a venue as GeoJSON: its units, openings and amenities, each with source and confidence. `level` is
* the floor number (0 ground, -1 basement). Needs door:read. 404 not_found for an unknown venue or floor.
*/
venueLevel(venueId: string, level: number, options?: CallOptions): Promise<VenueLevel>;
/**
* What a printed plate for one unit says: venue, floor, the unit's name or number, a point, and the `link` its QR
* holds. `unitId` is a unit's id from venueLevel. Needs door:read. 404 not_found for an unknown venue or unit.
*/
venueSpot(venueId: string, unitId: string, options?: CallOptions): Promise<VenueSpot>;
/**
* The walk inside a venue to one unit, floor by floor, with steps in Mongolian and the floor on each. `from` is
* [lon, lat] on floor `fromLevel` (0 when absent): the entrance a road route used, or a tapped spot. `unitId` is a
* unit's id from venueLevel. Stairs, escalators and lifts join the floors. Needs door:read. 404 not_found for an
* unknown venue or unit; 422 no_route where no drawn corridor joins the start and the unit (nothing is invented).
*/
venueRoute(venueId: string, from: LonLat, unitId: string, options?: CallOptions & {
fromLevel?: number;
}): Promise<VenueRoute>;
/**
* The whole way to a unit in one answer: the road to the right street entrance, then the walk inside. `from` is
* [lon, lat]; `mode` is driving when absent. Distance and duration are road plus walk. Needs route:read. 404
* not_found; 422 no_entrance when no street entrance is drawn, 422 no_route when no road or no corridor reaches it.
*/
venueDirections(venueId: string, from: LonLat, unitId: string, options?: CallOptions & {
mode?: RouteMode;
}): Promise<VenueDirections>;
/**
* Ids of places changed in [since, until), oldest first, one page. Needs changes:read (partner keys). Pass the
* page's `next` as `cursor` until it is null, then poll again with since = until. First sync: since
* '1970-01-01T00:00:00Z'. A string needs a zone (Z or +08:00); a Date is sent as UTC. limit 1 to 1000, default 100,
* on every call: the cursor does not keep it.
*/
changes(since: string | Date | null, options?: CallOptions & {
limit?: number;
cursor?: string | null;
}): Promise<ChangesResponse>;
/**
* A driver on site says a door is there (confirmed), elsewhere (wrong_location) or gone (missing). Needs
* confirm:write (partner keys). Map data does not change at once. 422 implausible when device is over 40 m from
* the tap, too_far when the tap is over 500 m from the entrance: ask the driver to tap again on site.
*/
confirm(confirmation: Confirmation, options?: CallOptions): Promise<ConfirmReceipt>;
/**
* The photo of the door the driver took with a confirmation, sent after confirm() answered, with its id (API
* 0.71.0). One JPEG as the camera saved it, up to 2 MB, 320 to 8000 px a side; the API removes its metadata. One
* photo per confirmation: the same photo again is unchanged, another is 409 photo_exists. 404 not_found when the id
* is not one of your confirmations. Needs confirm:write.
*/
sendConfirmPhoto(id: string, jpeg: Uint8Array | ArrayBuffer | Blob, options?: CallOptions): Promise<ConfirmPhotoReceipt>;
/**
* Your drivers' confirmations and door reports in a month: your dashboard (PLAN.md 18). `month` is YYYY-MM
* (Ulaanbaatar). Counts by result for the month and each day on site, different drivers (never the ids), and up to
* 100 failure points. The month you are in is answered too; `closed` is true from 8 days after it ends. No other
* organization is read. Needs confirm:read, a read-only scope: confirm:write does not read it.
*/
ownConfirmations(month: string, options?: CallOptions): Promise<OwnConfirmations>;
/** This key's organization: caps and the last `days` days (1 to 90). Free, answers after the quota. */
usage(options?: CallOptions & {
days?: number;
}): Promise<UsageResponse>;
/** TileJSON URL of a tile source (building, entrance), without the key. */
tileJsonUrl(source: string): string;
/**
* MapLibre transformRequest: adds the key to requests for this API only. Other hosts (a partner basemap,
* fonts) get nothing.
*/
readonly transformRequest: (url: string) => RequestParameters;
/** The key never shows up in console.log, JSON.stringify or error reports. */
toJSON(): {
baseUrl: string;
};
}code.ts
ts
/**
* Building codes on the device, the API's rules (api/src/modules/door/door-code.ts, PLAN.md 4c). Five characters,
* digits and consonants, no vowels and no I, L, O, U. sdk/testdata/door-code.json holds the cases the API and
* the three SDKs agree on.
*/
export const DOOR_CODE_ALPHABET = "0123456789BCDFGHJKMNPQRSTVWXYZ";
/** Characters in a building code. */
export const DOOR_CODE_LENGTH = 5;
/**
* What parseDoorCode read. `unit` is whatever follows the code (flat number, household name), as typed.
* `error` says why not: empty, length (not 5 characters) or letters (a character no code uses). `reason` is the
* API's invalid_code message, in English; show the person your own words for `error`.
*/
export type DoorCodeInput = {
ok: true;
code: string;
unit: string | null;
} | {
ok: false;
error: 'empty' | 'length' | 'letters';
reason: string;
};
/**
* Reads a typed or pasted building code as the API would: "7QSVX", "7qsvx 17", "7QSVХ Бат". No request. Use it to
* check a field as the person types, and send only `ok` codes to door(): a code that finds no door counts
* against the daily miss cap; one that cannot be a code is caught here first.
*/
export function parseDoorCode(text: string): DoorCodeInput;
/**
* A door link (PLAN.md 4a: "send the code, a link, or a QR"; PLAN 18 entry paths 1 and 4): this base, the code, and
* `?u=` with the unit when there is one. sdk/testdata/door-link.json holds the cases the three SDKs agree on.
*/
export const DOOR_LINK_BASE = "https://map.dock.mn/d/";
/**
* The link to a door, for a share sheet, a message or a QR: "https://map.dock.mn/d/7QSVX?u=17". `text` and `unit`
* are read as parseDoorCode reads `text unit`, so the code may be typed in Cyrillic lookalikes and the unit (flat
* number, household name) goes in percent-encoded. null when the text cannot be a code. No request: the link says
* nothing about whether a door has this code.
*/
export function doorLink(text: string, unit?: string | null): string | null;
/**
* Reads the first door link in a text: a pasted link, a share message with a link in it, or the URL a deep link
* opened the app with. http or https or none, the host in any case, `?u=` for the unit, other parameters and `#`
* ignored. null when the text has no door link; otherwise what parseDoorCode reads from the code and the unit,
* so a link with a broken code is `{ ok: false }` with the same reasons. No request.
*/
export function parseDoorLink(text: string): DoorCodeInput | null;qr.ts
ts
/**
* A QR as rows of modules: `modules[y][x]` is true for a dark module. `size` modules a side (21 to 177), no quiet
* zone: leave 4 light modules around it when drawing. `link` is the text it holds.
*/
export interface DoorQr {
link: string;
version: number;
size: number;
modules: boolean[][];
}
/** doorQrSvg drawing: `px` per module (default 4), `margin` light modules around the code (default 4, the spec's
* minimum quiet zone). */
export interface DoorQrSvgOptions {
px?: number;
margin?: number;
}
/**
* The QR of a door: the modules of doorLink(text, unit). null when the text cannot be a code. A link longer than
* level M holds (2,331 bytes, a unit of about 2,300 characters) is also null.
*/
export function doorQr(text: string, unit?: string | null): DoorQr | null;
/**
* The QR of a door as an SVG document, black on white, for an <img>, a share sheet or print. null as doorQr. Each
* module is `px` wide; the SVG is (size + 2 * margin) * px a side.
*/
export function doorQrSvg(text: string, unit?: string | null, options?: DoorQrSvgOptions): string | null;basemap.ts
ts
/** The base map's languages in the pack: names as name:mn and name:en. */
export type BasemapLang = 'mn' | 'en';
/** An offline pack's manifest block `basemap` (null in packs built before 2026-10-03, or with no base map). */
export interface PackBasemap {
method: string;
file: string;
format: string;
schema: string;
schema_version: string;
tile_type: string;
tiles: number;
minzoom: number;
maxzoom: number;
bounds: [number, number, number, number];
layers: string[];
excluded_layers: string[];
languages: string[];
osm_time: string | null;
/** Show on the map: the source carries it, so MapLibre's attribution control shows it. */
attribution: string;
rule: string;
rule_mn: string;
}
/** What basemapStyle() takes. */
export interface BasemapStyleOptions {
/** Label language, one of the block's languages. Default 'mn'. A name missing in it falls back to OSM's name. */
lang?: BasemapLang;
/** A glyphs URL ({fontstack}, {range}). The pack carries no fonts: without glyphs the style has no labels. */
glyphs?: string;
/** Font stack on that glyphs URL. Default ['Noto Sans Regular']. */
fonts?: string[];
/** Background color. Default the dock style's paper tone. */
background?: string;
}
/** Layers the base map must not carry: business names (shown only when registered) and OSM house numbers. */
export const BASEMAP_EXCLUDED: readonly ["poi", "housenumber"];
/**
* A whole style for an offline pack's base map: land, water, tracks and roads, boundaries, buildings, and with
* `glyphs` also place, water and road names. `url` is where the app keeps basemap.pmtiles (a file:// or http URL);
* the source asks for it as pmtiles://. MapLibre Native reads pmtiles:// itself; MapLibre GL JS needs the pmtiles
* protocol added. Throws TypeError for no block, RangeError for a block that is not the pack's base map or carries
* a poi or housenumber layer, a lang the block lacks, or an empty url.
*/
export function basemapStyle(basemap: PackBasemap | null | undefined, url: string, options?: BasemapStyleOptions): Json;closures.ts
ts
/** What to send Valhalla for one route day: the rings of the closures active that day. */
export interface ExcludePolygons {
/** YYYY-MM-DD, Ulaanbaatar. */
day: string;
/** The closures avoided, in list order. Show them with the route. */
ids: string[];
/** Valhalla's exclude_polygons. */
rings: LonLat[][];
perimeter_m: number;
vertices: number;
}
/**
* The closures active on the route day are over Valhalla's limits for one request. Refuse the route: never drop a
* closure to fit.
*/
export class ClosuresOverLimitError extends Error {
/** The route day, YYYY-MM-DD Ulaanbaatar. */
readonly day: string;
/** The day's closure rings, metres of perimeter in all. */
readonly perimeter_m: number;
/** The day's closure rings, vertices in all. */
readonly vertices: number;
constructor(day: string, count: number, perimeterM: number, vertices: number, limits: ClosuresResponse['valhalla_limits']);
}
/** Today in Ulaanbaatar (UTC+8, no daylight saving), YYYY-MM-DD. */
export function ubToday(now?: number): string;
/** from <= day, and day <= to when to is set (the list's active_when). Days compare as YYYY-MM-DD strings. */
export function isActive(item: Pick<ClosureItem, 'from' | 'to'>, day: string): boolean;
/**
* Offline: the rings to send Valhalla as exclude_polygons for a route on `day` (default today, Ulaanbaatar), from a
* GET /v1/closures answer or an offline pack's closures block. Throws RangeError for a bad day or a day before the
* list's as_of (closures that ended before it are not in it: fetch again), and ClosuresOverLimitError over Valhalla's
* limits.
*/
export function excludePolygons(closures: ClosuresResponse, day?: string): ExcludePolygons;
/**
* Offline: the id of the first closure in `avoid` whose ring holds `point`, or null. Valhalla drops every road inside
* an exclude ring, so a route from or to that point has no road to start from: it answers "No path could be found
* for input". Check both ends before asking your own Valhalla, as GET /v1/route does (422 end_in_closure).
*/
export function closureAt(avoid: Pick<ExcludePolygons, 'ids' | 'rings'>, point: LonLat): string | null;crossings.ts
ts
/** Modelled monthly flow at a river crossing (lane D's dock_steppe.rivers). Rivers only: streams have none. */
export interface CrossingFlow {
river_id: number;
/** 1991 to 2020 monthly normals, January to December, m3/s. */
normal_m3s: number[];
/** Months 1 to 12 whose normal is at least 1.5 times the mean of the 12. */
high_months: number[];
/** agrees, doubtful (the model reach is 10x below its OSM river's), unchecked. */
reach: 'agrees' | 'doubtful' | 'unchecked';
/** 0.4, or LOW_FLOW_CONFIDENCE (0.1) on a doubtful reach and on snap none_fits or main_stem. */
confidence: number;
/** How the reach was picked. matched: it follows the OSM river. none_fits: none does, the nearest is kept.
* main_stem: the channel follows only a small reach, the river's main stem off_m away is taken. nearest: the
* nearest, no lines to match on. Absent in packs built before the match. */
snap?: 'matched' | 'none_fits' | 'main_stem' | 'nearest';
}
/** A delta channel: the river splits into a lake there and no model reach says what share a channel carries. The
* crossing has flow null. Rule text in the pack's flow.delta.rule and rule_mn. */
export interface CrossingDelta {
/** The lake's OSM name, folded to one script when it mixed Latin and Cyrillic in a word. */
lake: string | null;
/** The lake's OSM name as written, when lake is folded. Absent in the pack when lake is the OSM text, and in packs
* before national crossings v11; null on a warning then. Show lake. */
lake_name_osm?: string | null;
/** The lake's OSM element, as relation/114846. */
lake_osm: string;
/** The river whose mouths form the delta. */
river: string;
}
/** An unnamed river or stream whose two ends lie on one named OSM river: a channel of it. From the OSM drawing, not
* checked on the ground. Rule text in the pack's channel_rule and channel_rule_mn. */
export interface CrossingChannelOf {
/** The river's OSM name, as on its river_way_id way. */
name: string;
name_en: string | null;
/** The OSM way the name was read from. */
river_way_id: number;
}
/** An unnamed river or stream, not a channel, and the named OSM river its water reaches downstream by the direction
* the ways are drawn. From the OSM drawing, not checked on the ground. Rule text in the pack's tributary_rule and
* tributary_rule_mn. */
export interface CrossingFlowsInto {
/** The river's OSM name, as on its river_way_id way. */
name: string;
name_en: string | null;
/** The OSM way the name was read from. */
river_way_id: number;
/** The fewest unnamed ways from this crossing's way to the river, its own included. */
ways: number;
}
/** The OSM tags of the route ways at a crossing. Rule text in the pack's road_rule and road_rule_mn. */
export interface CrossingRoad {
/** The ways' OSM highway tags, as track or trunk. */
highway: string[];
/** The ways' OSM surface tags; empty when none has one. */
surface: string[];
/** true: a way has a paved surface. false: every way has an unpaved one. null: a way has no surface tag. A none
* crossing on a paved road may be a bridge or culvert OSM does not tag; not checked. */
paved: boolean | null;
}
/** At or under this flow confidence the model reach is not sure to be this river's: show the months, not the m3/s. */
export const LOW_FLOW_CONFIDENCE = 0.1;
/** One crossing point in an offline pack's crossings.geojson (files[1]). */
export interface CrossingFeature {
type: 'Feature';
geometry: {
type: 'Point';
coordinates: LonLat;
};
properties: {
/** ford: OSM says ford. none: OSM has no bridge or ford there, not that there is none. */
kind: 'ford' | 'none';
/** river, stream, canal, or unknown on a ford node with no waterway line (ford_node_id set). */
waterway: string;
name: string | null;
/** The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. Absent
* when name is the OSM text. Show name. Packs before offline v13 lack it. */
name_osm?: string;
name_en: string | null;
intermittent: boolean;
flow?: CrossingFlow | null;
/** Set on a delta channel, with flow null. Absent in packs built before the delta rule. */
delta?: CrossingDelta | null;
/** Set on an unnamed channel of a named river. Absent in packs built before the channel rule. */
channel_of?: CrossingChannelOf | null;
/** Set on an unnamed river or stream that reaches one named river. Absent in packs built before the tributary
* rule. */
flows_into?: CrossingFlowsInto | null;
/** Null on a new track alone (no OSM way). Absent in packs built before the road rule. */
road?: CrossingRoad | null;
/** The OSM ford node, on a ford whose water OSM does not draw as a line; waterway is unknown. Null on line
* crossings, absent in packs built before the ford node rule. */
ford_node_id?: number | null;
};
}
/** An offline pack's crossings.geojson. Check its sha256 against the manifest's files[1] before use. */
export interface PackCrossings {
type: 'FeatureCollection';
/** A route passes a crossing when its line comes this close, m. */
match_m: number;
note: string;
note_mn: string;
/** Null in packs built before the flow block. */
flow?: {
attribution: string;
} | null;
/** Crossings with road.paved true. Absent in packs built before the road rule, as the two rule texts. */
on_paved?: number;
road_rule?: string;
road_rule_mn?: string;
/** None crossings that became the crossing of a bridge drawn just off the water, so left out of the pack. Absent in
* packs built before the bridge rule, as the two rule texts. */
near_bridge?: number;
bridge_rule?: string;
bridge_rule_mn?: string;
/** Crossings joined into the one beside them because their water way is split into two ways at the road. Absent in
* packs built before the split rule, as the two rule texts. */
one_point?: number;
split_rule?: string;
split_rule_mn?: string;
/** Crossings marked intermittent from OSM seasonal=* alone, no intermittent tag. Absent in packs built before that
* rule, as the two rule texts. */
from_seasonal?: number;
intermittent_rule?: string;
intermittent_rule_mn?: string;
/** Fords from an OSM ford node alone, no waterway line drawn. Absent in packs built before that rule, as the two
* rule texts. */
ford_nodes?: number;
ford_node_rule?: string;
ford_node_rule_mn?: string;
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. Packs before offline v13 lack
* them. */
names_folded?: number;
name_rule?: string;
name_rule_mn?: string;
features: CrossingFeature[];
}
/** A ford or unbridged crossing the route passes. */
export interface CrossingWarning {
kind: 'ford' | 'none';
point: LonLat;
waterway: string;
name: string | null;
/** The OSM text when name was folded to one script, else null. Show name. */
name_osm: string | null;
name_en: string | null;
intermittent: boolean;
/** Along the route to its point nearest the crossing, m, rounded. A tie goes to the earlier pass. */
along_m: number;
/** From the route line, m, one decimal. */
off_m: number;
/** The month is one of high_months. Null when the crossing has no flow (streams, canals). */
high_water: boolean | null;
high_months: number[];
/** The month's normal, m3/s. Null with no flow, on a doubtful reach, and at LOW_FLOW_CONFIDENCE or under (snap
* none_fits, main_stem): show the months, not the number. */
month_m3s: number | null;
reach: CrossingFlow['reach'] | null;
confidence: number | null;
/** The flow's snap. Null with no flow or in a pack built before the match. */
snap: CrossingFlow['snap'] | null;
/** A delta channel: say "delta channel, flow unknown". Null elsewhere and in packs built before the delta rule. */
delta: CrossingDelta | null;
/** An unnamed channel of a named river: say "unnamed channel of <name>". Null elsewhere and in packs built before
* the channel rule. */
channel_of: CrossingChannelOf | null;
/** An unnamed river or stream that reaches a named river: say "unnamed, flows into <name>". channel_of wins when
* both are set. Null elsewhere and in packs built before the tributary rule. */
flows_into: CrossingFlowsInto | null;
/** The road the route crosses on. With kind none and paved true, say "paved road, OSM tags no bridge here". Null
* on a new track alone and in packs built before the road rule. */
road: CrossingRoad | null;
/** The OSM ford node of a ford whose water OSM does not draw as a line, waterway unknown: say "OSM ford, water not
* drawn". Null on line crossings and in packs built before the ford node rule. */
ford_node_id: number | null;
}
/** What crossingsOnRoute() returns: the warnings in route order, with the pack's note. */
export interface RouteCrossings {
/** 1 to 12. */
month: number;
match_m: number;
/** In route order. */
warnings: CrossingWarning[];
/** Show with the warnings: from OSM tags, not field checked. */
note: string;
note_mn: string;
/** Show when a warning has flow; else null. */
flow_attribution: string | null;
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. Null when the pack has no rule. */
name_rule: string | null;
name_rule_mn: string | null;
}
/**
* Offline: the ford and unbridged crossings a route line passes, from an offline pack's crossings.geojson, in route
* order. `month` (1 to 12, default this month in Ulaanbaatar) sets high_water. Throws RangeError for a line under 2
* points or a bad month.
*/
export function crossingsOnRoute(crossings: PackCrossings, line: LonLat[], month?: number): RouteCrossings;pack.ts
ts
/** One row of an offline pack manifest's `files`: a file in the pack folder, its size and sha256. */
export interface PackFile {
name: string;
bytes: number;
/** Lower case hex. */
sha256: string;
}
/** The part of an offline pack's manifest.json checkPack reads; pass the whole parsed manifest. */
export interface PackManifestFiles {
files: PackFile[];
}
/** ok: size and sha256 match. missing: no such file. bytes: another size, not hashed. sha256: same size, other bytes. */
export type PackFileStatus = 'ok' | 'missing' | 'bytes' | 'sha256';
/** One file of a pack manifest as checkPack() found it. */
export interface PackFileCheck {
name: string;
status: PackFileStatus;
/** The file's size, null when missing. */
bytes: number | null;
expected_bytes: number;
/** The file's sha256, null when missing or another size. */
sha256: string | null;
expected_sha256: string;
}
/** What checkPack() returns. */
export interface PackCheck {
/** Every listed file is there with its size and sha256. Use no file from the pack otherwise. */
ok: boolean;
/** In manifest order. */
files: PackFileCheck[];
/** Names of the files not ok, in manifest order. Download them again. */
bad: string[];
}
/** A file's bytes by its name in the pack folder; null or undefined when it is not there. */
export type PackFileReader = (name: string) => Promise<Uint8Array | ArrayBuffer | null | undefined>;
/**
* Offline: checks each file the manifest lists, in its order, by size and then sha256 (Web Crypto). Files the manifest
* does not list are not read. Throws RangeError for a manifest with no files, a name that is not a plain file name, a
* name listed twice, a bytes value that is not a whole number from 0, or a sha256 that is not 64 lower case hex.
* An error thrown by `read` comes through.
*/
export function checkPack(manifest: PackManifestFiles, read: PackFileReader): Promise<PackCheck>;packroute.ts
ts
/** The part of an offline pack's manifest.json packRoute reads; pass the whole parsed manifest. */
export interface PackRouteManifest {
id: string;
/** [west, south, east, north]. */
bbox: [number, number, number, number];
/** What every route on the pack sends: costing auto, costing_options.auto.use_tracks 0.5 today. */
request: {
costing: string;
costing_options?: Record<string, unknown>;
};
/** scored, or osm_only: no trace scores yet. */
confidence: string;
/** What confidence means, for the driver, en and mn. Packs from offline v10; absent before. */
confidence_note?: string | null;
confidence_note_mn?: string | null;
attribution: string;
note: string;
closures: ClosuresResponse;
}
/** What packRoute() takes. */
export interface PackRouteOptions {
/** Route day, YYYY-MM-DD Ulaanbaatar. Default today. */
day?: string;
/** A newer GET /v1/closures answer, in place of the pack's closures block. */
closures?: ClosuresResponse;
}
/** POST it to /route on your own Valhalla, the pack's tiles.tar as mjolnir.tile_extract. */
export interface PackRouteBody {
locations: {
lon: number;
lat: number;
}[];
costing: string;
costing_options: Record<string, unknown>;
exclude_polygons: LonLat[][];
units: 'km';
}
/** What packRoute() returns: the Valhalla request and what to show with the route. */
export interface PackRoute {
body: PackRouteBody;
/** The manifest's id. */
pack: string;
day: string;
/** The closures the body avoids, in list order. Show them with the route. */
closures: string[];
/** The pack's route costs come from trace scores. */
scored: boolean;
confidence: string;
/**
* What confidence means, for the driver: the manifest's confidence_note and confidence_note_mn when it has both;
* else PACK_TRACE_NOTE and PACK_TRACE_NOTE_MN when the pack is not scored; else null.
*/
confidence_note: string | null;
confidence_note_mn: string | null;
/** Show it with every route. */
attribution: string;
note: string;
/** Say it to the driver when the pack has no trace scores: confidence_note, en and mn; null when scored. */
trace_note: string | null;
trace_note_mn: string | null;
}
/** A stop inside a closure ring: Valhalla has no road there. Refuse the route, as GET /v1/route does (422 end_in_closure). */
export class EndInClosureError extends Error {
/** The stop's index in `stops`. */
readonly stop: number;
/** The closure's id. */
readonly closure: string;
constructor(stop: number, closure: string);
}
/** What to say when a pack has no trace scores (confidence osm_only), in English. */
export const PACK_TRACE_NOTE = "No trace scores yet: the route follows OSM roads and tracks, not checked by drivers.";
/** The same in Mongolian. */
export const PACK_TRACE_NOTE_MN = "\u0410\u044F\u043B\u043B\u044B\u043D \u043C\u044D\u0434\u044D\u044D\u043B\u044D\u043B \u0445\u0430\u0440\u0430\u0430\u0445\u0430\u043D \u0430\u043B\u0433\u0430: \u043C\u0430\u0440\u0448\u0440\u0443\u0442 OSM-\u0438\u0439\u043D \u0437\u0430\u043C\u0430\u0430\u0440, \u0436\u043E\u043B\u043E\u043E\u0447 \u0448\u0430\u043B\u0433\u0430\u0430\u0433\u04AF\u0439.";
/**
* Offline: the Valhalla request for a route through `stops` (2 or more [lon, lat]) on the pack: the manifest's
* costing and costing_options (copied), the route day's closure rings as exclude_polygons, units km. Throws
* RangeError for a manifest with no request costing or bbox, fewer than 2 stops, a stop that is not [lon, lat] or is
* outside the pack's bbox; excludePolygons' errors for the day; EndInClosureError for a stop inside an avoided ring.
*/
export function packRoute(manifest: PackRouteManifest, stops: LonLat[], options?: PackRouteOptions): PackRoute;pois.ts
ts
/** A fuel station or a ger camp. */
export type PoiKind = 'fuel' | 'ger_camp';
/** One registered place in an offline pack's pois.geojson. Unregistered places are never in the file. */
export interface PoiFeature {
type: 'Feature';
geometry: {
type: 'Point';
coordinates: LonLat;
};
properties: {
kind: PoiKind;
/** The OSM element, N or W and the id, as N123. */
osm: string;
/** Clean name from the state registry match. */
name: string;
name_en: string | null;
/** State registration number. */
reg_number: string;
};
}
/** Per kind, all Mongolia. */
export type PoiCounts = Record<PoiKind, {
registered: number;
unregistered: number;
}>;
/** An offline pack's pois.geojson (files[2]; index.json pois_sha256). Check its sha256 against the manifest before use. */
export interface PackPois {
type: 'FeatureCollection';
/** False: the registry could not be read when the pack was built, so no place is listed. */
registry_checked: boolean;
counts: PoiCounts;
written: number;
rule: string;
rule_mn: string;
source: string;
note: string;
note_mn: string;
features: PoiFeature[];
}
/** A registered fuel station or ger camp near the route. */
export interface PoiOnRoute {
kind: PoiKind;
point: LonLat;
osm: string;
name: string;
name_en: string | null;
reg_number: string;
/** Along the route to its point nearest the place, m, rounded. A tie goes to the earlier pass. */
along_m: number;
/** From the route line, m, rounded. */
off_m: number;
}
/** What poisOnRoute() returns. */
export interface RoutePois {
near_m: number;
/** Registered places only, in route order. */
pois: PoiOnRoute[];
/** False: no place was listed because the registry was not read; say so, do not say "none". */
registry_checked: boolean;
/** Unregistered places in the pack, all Mongolia, not near this route: the pack has no place for them. An empty
* list is not "no fuel on the route" while these are above 0. */
unlisted: Record<PoiKind, number>;
/** Show with the places: from OSM, not field checked; unregistered places are counted, not shown. */
note: string;
note_mn: string;
}
/** lane D's route pages: fuel and ger camps within 2 km of the line. */
export const POI_NEAR_M = 2000;
/**
* Offline: the registered fuel stations and ger camps within `nearM` (default 2000) of a route line, from an offline
* pack's pois.geojson, in route order. Throws RangeError for a line under 2 points or a nearM not above 0.
*/
export function poisOnRoute(pois: PackPois, line: LonLat[], nearM?: number): RoutePois;passes.ts
ts
/** OSM mountain_pass=yes or natural=saddle. */
export type PassKind = 'mountain_pass' | 'saddle';
/** One pass in an offline pack's passes.geojson: an OSM mountain_pass=yes or natural=saddle node of a car way. */
export interface PassFeature {
type: 'Feature';
geometry: {
type: 'Point';
coordinates: LonLat;
};
properties: {
/** mountain_pass when OSM has that tag, else saddle. */
kind: PassKind;
name: string | null;
/** The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. Absent
* when name is the OSM text. Show name. Packs before offline v12 lack it. */
name_osm?: string;
name_en: string | null;
/** The OSM ele tag read as metres. Null when OSM has none, or only other text, as "7000 ft": see ele_osm. */
ele_m: number | null;
/** The OSM ele tag as written. */
ele_osm: string | null;
/** The OSM node id. */
node_id: number;
/** The Copernicus GLO-30 DEM height of the cell the node falls in, whole metres: a second source beside ele_m,
* never mixed into it. Null when the pack had no DEM tile there; packs built before 2026-10-03 lack it. */
dem_m?: number | null;
/** True when ele_m and dem_m are both there and more than dem.differ_m (50 m) apart, false within, null when
* either is missing. True says one of the two is wrong, not which. Packs built before passes v3 lack it. */
heights_differ?: boolean | null;
/** The OSM car ways through the node. Empty on a new track alone. */
route_way_ids: number[];
new_track: boolean;
/** The ways' OSM highway and surface tags, as on crossings. Null on a new track alone. */
road: CrossingRoad | null;
};
}
/** The pack's DEM block: where dem_m comes from. Show attribution wherever a DEM height shows. */
export interface PassDem {
source: string;
attribution: string;
rule: string;
rule_mn: string;
note: string;
note_mn: string;
/** DEM tiles read. */
tiles: number;
/** Tiles the build had no copy of: passes there have dem_m null. */
missing: string[];
/** Heights further apart than this, m, set heights_differ. Packs before passes v3 lack it and the three below. */
differ_m?: number;
differ_rule?: string;
differ_rule_mn?: string;
/** Passes with heights_differ true, all Mongolia. */
differ?: number;
}
/** An offline pack's passes.geojson (manifest files row and passes block; index.json passes_sha256). Check its sha256
* against the manifest before use. Packs built before 2026-10-03 have none. */
export interface PackPasses {
type: 'FeatureCollection';
/** Passes in the file, all Mongolia. */
written: number;
/** Per kind, all Mongolia. */
counts: Partial<Record<PassKind, number>>;
named: number;
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. Packs before offline v12 lack
* them. */
names_folded?: number;
name_rule?: string;
name_rule_mn?: string;
/** Passes with a height in metres. */
with_ele: number;
/** Passes with a DEM height. Packs built before 2026-10-03 lack it. */
with_dem?: number;
/** Packs built before 2026-10-03 lack it. */
dem?: PassDem;
/** A route goes over a pass when its line comes this close, m. */
match_m: number;
rule: string;
rule_mn: string;
source: string;
note: string;
note_mn: string;
features: PassFeature[];
}
/** A pass the route goes over. */
export interface PassOnRoute {
kind: PassKind;
point: LonLat;
name: string | null;
/** The OSM text when name was folded to one script, else null. Show name. */
name_osm: string | null;
name_en: string | null;
ele_m: number | null;
ele_osm: string | null;
node_id: number;
/** The DEM height, whole metres, beside ele_m. Null when the pack has none for this pass. Label it DEM. */
dem_m: number | null;
/** The pack's flag: true when ele_m and dem_m disagree by more than dem.differ_m. Null when either is missing or
* the pack has no flag. Show both heights and the dem differ_rule when true. */
heights_differ: boolean | null;
new_track: boolean;
road: CrossingRoad | null;
/** Along the route to its point nearest the pass, m, rounded. A tie goes to the earlier pass. */
along_m: number;
/** From the route line, m, rounded to 0.1. */
off_m: number;
}
/** What passesOnRoute() returns. */
export interface RoutePasses {
match_m: number;
/** In route order. */
passes: PassOnRoute[];
/** The highest ele_m of these passes, m. Null when none has a height: not "flat", OSM has no number. */
top_m: number | null;
/** The highest dem_m of these passes, m. Null when none has one. Not mixed with top_m. */
top_dem_m: number | null;
/** How many of these passes have heights_differ true. Null when the pack has no flag (no dem.differ_rule): not
* checked, not 0. */
differ: number | null;
/** The pack's DEM block, null when the pack has none. Show its attribution with a DEM height. */
dem: PassDem | null;
/** Show with the passes: from OSM tags, not field checked. */
note: string;
note_mn: string;
rule: string;
rule_mn: string;
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. Null when the pack has no rule. */
name_rule: string | null;
name_rule_mn: string | null;
}
/**
* Offline: the OSM mountain passes a route line goes over, from an offline pack's passes.geojson, in route order. A
* route goes over a pass when its line comes within the pack's match_m (5 m) of the pass node, as lane D's route
* pages. Throws RangeError for a line under 2 points.
*/
export function passesOnRoute(passes: PackPasses, line: LonLat[]): RoutePasses;places.ts
ts
/** Default number of places kept. The least picked, then the longest unpicked, go first. */
export const PLACES_MAX = 100;
/** Key in the partner's store. A new format gets a new key. */
export const PLACES_KEY = "dock.places.v1";
/** localStorage or anything with its three calls (a native key-value bridge, a test map). */
export interface PlaceStore {
getItem(key: string): string | null;
setItem(key: string, value: string): void;
removeItem(key: string): void;
}
/** ClientOptions.placeCache: where picked places are kept, and how many. */
export interface PlaceCacheOptions {
/** 1 to 1000. Default 100. */
max?: number;
/** Keeps the places across page loads. Default none: memory only, gone with the page. */
store?: PlaceStore;
}
/** A geocode answer. `offline` is true only when it came from this device's picked places, not the API. */
export type GeocodeAnswer = GeocodeResponse & {
offline?: true;
};map.ts
ts
/** A saved style id: "st_" and 10 characters of the key alphabet. */
export const STYLE_ID_RE: RegExp;
/**
* What createMap needs from maplibre-gl 6.4.1 or later (earlier versions carry XSS GHSA-jrc7-96c5-q579). Pass the
* module: `import * as maplibregl from 'maplibre-gl'` (6 has no default export). Under a bundler it also needs
* `maplibregl.setWorkerUrl(...)`: see the SDK page, Bundlers.
*/
export interface MapLibreLike {
Map: new (options: any) => any;
}
/** What createMap() takes. Any option not named here goes to the maplibre-gl Map as is. */
export interface MapOptions extends StyleOptions {
container: string | HTMLElement;
center?: [number, number];
zoom?: number;
/** A full style to use instead of dockStyle. Add dockSources and dockLayers to it yourself. */
style?: unknown;
/** A style saved with POST /v1/styles or the style editor ("st_" and 10 characters). Loaded from the API. */
styleId?: string;
/** Runs after the key is added, for your own hosts. */
transformRequest?: (url: string, resourceType?: string) => RequestParameters | undefined;
/** Keep the two finger twist that turns the map. Off by default: a pinch must not turn it. */
rotate?: boolean;
/** Anything else goes to the maplibre-gl Map constructor as is. */
[option: string]: unknown;
}
/** A MapLibre map on dock tiles, keyed through the client. Attribution comes from the TileJSON. */
export function createMap(maplibregl: MapLibreLike, client: Client, options: MapOptions): any;style.ts
ts
/** Ulaanbaatar, Sükhbaatar Square. */
export const UB_CENTER: [number, number];
/** Colors and extrusion for dockStyle(), dockLayers() and createMap(). */
export interface StyleOptions {
/** Background color. Default a light paper tone. */
background?: string;
/** Building fill (or extrusion) color. Default a warm grey. */
building?: string;
/** Building outline color. */
buildingOutline?: string;
/** Extrude buildings by height_m (or levels x 3 m). Default false: flat 2D. */
extrude?: boolean;
}
/** Sources named dock-building and dock-entrance, TileJSON from the API. The key is added by transformRequest. */
export function dockSources(baseUrl: string): Record<string, Json>;
/**
* Building fill and outline, entrance dots by confidence. Add on top of any basemap style.
* Entrance colors are fixed: confidence reads the same on every map (PLAN.md 5). The API draws saved styles with the
* same layers (api/src/modules/styles/style-doc.ts); a test keeps them equal.
*/
export function dockLayers(options?: StyleOptions): Json[];
/** A whole style: background, dock buildings, dock entrances. */
export function dockStyle(baseUrl: string, options?: StyleOptions): Json;isochrone.ts
ts
/** The source id isochroneLayers() draws by default. */
export const ISOCHRONE_SOURCE = "dock-isochrone";
/** Which source to draw and how. */
export interface IsochroneLayerOptions {
/** The source id given to map.addSource. Layer ids are this with "-fill" and "-line". Default "dock-isochrone". */
source?: string;
/** Fill and outline color. Default a dark teal. */
color?: string;
/** Fill opacity of one zone, 0 to 1. Zones overlap, so the nearest is the darkest. Default 0.2. */
opacity?: number;
}
/**
* A GeoJSON source from isochrone()'s answer: the features only, largest contour first, so each nearer zone is drawn
* on top. Attribution is the answer's source (Valhalla, OpenStreetMap contributors).
*/
export function isochroneSource(isochrone: Isochrone): Json;
/** A fill and an outline for an isochroneSource(), drawn in feature order. Add them above dockLayers(). */
export function isochroneLayers(options?: IsochroneLayerOptions): Json[];matching.ts
ts
/** The source id matchingLayers() draws by default. */
export const MATCHING_SOURCE = "dock-matching";
/** Which source to draw and how. */
export interface MatchingLayerOptions {
/** The source id given to map.addSource. Layer ids are this with "-line" and "-point". Default "dock-matching". */
source?: string;
/** Line color, and the ring around each matched point. Default a blue. */
color?: string;
/** Line width in pixels, more than 0. Default 4. */
width?: number;
}
/**
* A GeoJSON source from matching()'s answer: one LineString per matching (matchings_index, distance_m, duration_s),
* then one Point per tracepoint with a road (point, its index in the trace sent; matchings_index; gap_m). A null
* tracepoint has no place on the road and is left out. Attribution is the answer's source.
*/
export function matchingSource(match: Match): Json;
/** A line for the matched roads and a dot for each matched point, on one matchingSource(). Add them above dockLayers(). */
export function matchingLayers(options?: MatchingLayerOptions): Json[];trip.ts
ts
/** The source id optimizedTripLayers() draws by default. */
export const OPTIMIZED_TRIP_SOURCE = "dock-optimized-trip";
/** Which source to draw and how. */
export interface OptimizedTripLayerOptions {
/** The source id given to map.addSource. Layer ids are this with "-line", "-stop" and "-number". Default "dock-optimized-trip". */
source?: string;
/** Line color, the ring around each stop and its number. Default a blue. */
color?: string;
/** Line width in pixels, more than 0. Default 4. */
width?: number;
/**
* A font stack on the style's glyphs. Given: each stop shows its number in the order to drive, 1 first. Not given:
* rings only, as dockStyle() has no glyphs.
*/
font?: string[];
}
/**
* A GeoJSON source from optimizedTrips()'s answer: one LineString per trip (trips_index, distance_m, duration_s), then
* one Point per stop at its snapped place, in the order to drive (number, its place in the trip from 1; stop, its index
* in the list sent; gap_m). Attribution is the answer's source. Not such an answer: TypeError. waypoint_index not 0 to
* n - 1 once each: RangeError, as the numbers would be wrong.
*/
export function optimizedTripSource(round: OptimizedTrip): Json;
/** A line for the trip and a ring for each stop on one optimizedTripSource(), with `font` its number too. Add them above dockLayers(). */
export function optimizedTripLayers(options?: OptimizedTripLayerOptions): Json[];nav.ts
ts
/** Off the line by more than this, NAV_OFF_FIXES fixes in a row: a new route from where the car is. */
export const NAV_OFF_M = 40;
/** Fixes in a row off the line before a reroute: one bad GPS fix never reroutes. */
export const NAV_OFF_FIXES = 3;
/** Within this of the road's end: arrived. */
export const NAV_ARRIVE_M = 20;
/** Where the car is on the route after one fix. */
export interface NavState {
/** The step being driven (the last turn passed), by place in steps. */
step: number;
/** The next turn, or null after the last. */
next: RouteStep | null;
/** Metres along the road to the next turn. */
toNext_m: number;
/** Metres and seconds along the road to the end, the seconds at the route's own pace (expected_duration_s when the API gave it, else free-flow). */
left_m: number;
left_s: number;
/** Metres off the line at this fix. */
off_m: number;
/** Rerouting: off the line for NAV_OFF_FIXES fixes, a new route asked for. */
rerouting: boolean;
arrived: boolean;
}
/** What a Navigator says and how it gets a new route. */
export interface NavigatorOptions {
/** Phrase ids to say, in order (VoicePlayer.say). */
onSay?: (ids: string[]) => void;
/** A new route from `from` to the same end. Left out: no rerouting, the state says off_m. */
reroute?: (from: LonLat) => Promise<Route>;
/** Called with each new route after a reroute. */
onRoute?: (route: Route) => void;
}
/** The phrase id for a distance ahead: the largest bucket at or under it. */
export function aheadPhrase(m: number): string | null;
/** Follows one route fix by fix. update() is cheap; call it with every position. */
export class Navigator {
/** The route being driven; a new one after each reroute. */
route: Route;
/** Its steps, every leg's in order. */
steps: RouteStep[];
constructor(route: Route, opts?: NavigatorOptions);
/** One position fix, [lon, lat]. */
update(pos: LonLat): NavState;
}
/** Plays phrase ids from a phrase set (PWA /voice/mn/phrases.json): one after another, never over each other. On by default. */
export class VoicePlayer {
/** Voice on by default (Gantulga, 2026-10-12); false keeps it quiet. */
enabled: boolean;
/** `base` ends with / and holds phrases.json and the audio; `play` plays one URL to its end (an Audio element in a browser). */
constructor(base: string, play?: (url: string) => Promise<void>);
/** Reads phrases.json. Ids it does not have are skipped when said. */
load(fetcher?: (url: string) => Promise<{
json(): Promise<unknown>;
}>): Promise<string[]>;
/** Queues phrase ids to play after what is playing; nothing when off. */
say(ids: string[]): void;
}routemap.ts
ts
/** The source id routeLayers() draws by default. */
export const ROUTE_SOURCE = "dock-route";
/** Which source to draw and how. */
export interface RouteLayerOptions {
/** The source id given to map.addSource. Layer ids are this with "-line", "-end" and "-number". Default "dock-route". */
source?: string;
/** Line color, the ring around each end and its number. Default a blue. */
color?: string;
/** Line width in pixels, more than 0. Default 4. */
width?: number;
/**
* A font stack on the style's glyphs. Given: each end shows its number in the order passed, from 1. Not given:
* rings only, as dockStyle() has no glyphs.
*/
font?: string[];
}
/**
* A GeoJSON source from route()'s answer: the line (mode, distance_m, duration_s), then one Point per end at its
* snapped place, in the order passed: from, each via, to (number, from 1; role, "from", "via" or "to"; via, the via
* end's index in the list sent, null for from and to; gap_m). An answer from an API before 0.67.0 has no via: from
* and to only. Attribution is the answer's source. Not such an answer: TypeError.
*/
export function routeSource(route: Route): Json;
/** A line for the route and a ring for each end on one routeSource(), with `font` its number too. Add them above dockLayers(). */
export function routeLayers(options?: RouteLayerOptions): Json[];format.ts
ts
/** Mongolian or English. */
export type Lang = 'mn' | 'en';
/**
* What to show a person for a door or a place: the spoken form, then the name, then the long form, then the
* code, each in `lang` first. Never the id. Anything without a matched official address ends in
* "(албан бус)" / "(unofficial)": VISION rule 1, PLAN 4d.
*/
export function label(item: Door | Place | RouteDoor, lang?: Lang): string;suggest.ts
ts
/** PLAN 18: "suggestions from the third character". */
export const SUGGEST_MIN_CHARS = 3;
/** Wait after the last key before asking. Under the 250 ms people notice, over most gaps between keys. */
export const SUGGEST_DELAY_MS = 200;
/** A door read from a typed code, or a geocode result. Doors have `entrance`; geocode results have `kind`. */
export type Suggestion = Door | GeocodeResult;
/** The calls the widget makes. A Client has them. */
export interface SuggestClient {
geocode(q: string, options: {
limit?: number;
proximity?: LonLat;
types?: PlaceKind[];
signal?: AbortSignal;
}): Promise<{
results: GeocodeResult[];
}>;
door(code: string, options: {
unit?: string;
signal?: AbortSignal;
}): Promise<Door>;
pick(result: GeocodeResult): void;
}
/** What a Suggester takes. */
export interface SuggestOptions {
/** Geocode results per list, 1 to 20. Default 5. A door from a typed code comes on top of them. */
limit?: number;
/** [lon, lat] of the person or the map centre, or a function asked on each request. Nearer places first. */
proximity?: LonLat | (() => LonLat | null | undefined);
/**
* Only these kinds, as geocode types: a street picker asks ['street']. Absent or empty: every kind. A
* typed code or a pasted door link is looked up as a door only when types has door. Another kind is refused by the
* constructor with the API's 400 invalid_types: no request.
*/
types?: PlaceKind[];
/** Default 3. */
minChars?: number;
/** Default 200 ms. */
delayMs?: number;
}
/**
* Text in, suggestions out, with no DOM. `input(text)` on every change; `show` gets the list for the text as it
* stands, never an older one. Under minChars the list is emptied at once and nothing is sent. A text that reads as
* a building code (parseDoorCode) is looked up as a door first; a code with no door falls through to the words. A
* pasted door link (parseDoorLink) is looked up as a door only.
*/
export class Suggester {
constructor(client: SuggestClient, options: SuggestOptions, show: (items: Suggestion[], q: string) => void, fail: (e: unknown) => void);
/** The input now reads text. From minChars characters on, asks delayMs after the last call; under them, empties the list. */
input(text: string): void;
/** Stops the wait and the request in flight. Their answers are dropped. */
cancel(): void;
}
/** What attachSuggest() takes. */
export interface AttachSuggestOptions extends SuggestOptions {
/** The person chose a suggestion. The input already shows its label. A building code lookup gives a Door. */
onPick: (item: Suggestion) => void;
/** A DockError or a network failure. Default: the list is emptied. */
onError?: (e: unknown) => void;
/** Labels and the no-match line. Default 'mn'. */
lang?: Lang;
/** Where the options go. Default: a new <ul> right after the input. */
list?: HTMLElement;
}
/** What attachSuggest() returns. */
export interface SuggestBox {
readonly list: HTMLElement;
/** Empties the list and stops any request. */
clear(): void;
/** Removes the listeners, and the list if attachSuggest made it. */
destroy(): void;
}
/**
* One address input for a checkout: as the person types, places and doors appear under it; arrows and Enter or a
* click choose one. The chosen GeocodeResult goes to client.pick (offline places, opted-in telemetry), then onPick.
* Each option reads label(item, lang), so anything without a matched official address says so. No styles: the list
* has class dock-suggest, the empty line dock-suggest-empty, the active option aria-selected="true".
*/
export function attachSuggest(input: HTMLInputElement, client: SuggestClient, options: AttachSuggestOptions): SuggestBox;searchbox.ts
ts
/** The pick's mark on the map: a source and a circle layer of this id. */
export const SEARCH_SOURCE = "dock-search";
/** What a SearchBox takes. */
export interface SearchBoxOptions {
/** Labels, card and placeholder in 'mn' (default) or 'en'. */
lang?: Lang;
placeholder?: string;
/** Called with each pick, after the map moved. */
onPick?: (item: Suggestion) => void;
/** Zoom the map flies to on a pick. Default 17. */
zoom?: number;
/** Mark colour. */
color?: string;
}
/** The map as the control uses it: maplibre-gl's Map, or anything with these. */
export interface SearchBoxMap {
getCenter(): {
toArray(): number[];
};
flyTo(o: {
center: [number, number];
zoom: number;
}): unknown;
getSource(id: string): unknown;
addSource(id: string, s: unknown): unknown;
addLayer(l: unknown): unknown;
getLayer(id: string): unknown;
removeLayer(id: string): unknown;
removeSource(id: string): unknown;
isStyleLoaded?(): boolean | void;
once?(event: string, f: () => void): unknown;
}
/** A MapLibre control (IControl): map.addControl(new SearchBox(client, options), 'top-left'). */
export class SearchBox {
/** The control's element, once added. */
container: HTMLElement | null;
/** The last pick, or null. */
picked: Suggestion | null;
constructor(client: SuggestClient, options?: SearchBoxOptions);
/** maplibre-gl calls it on map.addControl: builds the field, its list and the card. */
onAdd(map: SearchBoxMap): HTMLElement;
/** maplibre-gl calls it on map.removeControl: the mark, the card and the listeners go. */
onRemove(): void;
/** Takes the card and the mark away. */
clear(): void;
}telemetry.ts
ts
/** One search as the API takes it. Text, count and pick only: no time, point, device or session. */
export interface SearchEvent {
q: string;
results: number;
picked?: {
kind: PlaceKind;
id: string;
rank: number;
};
}trace.ts
ts
/** API limits (api/src/modules/traces/trace-input.ts). */
export const TRACE_FORMAT = "dock.trace.v1";
/** Points per upload. stopTrace() splits a longer trip. */
export const TRACE_MAX_POINTS = 20000;
/** [lon, lat, t]: degrees WGS84 and whole seconds UTC. Nothing else per point. */
export type TracePoint = [number, number, number];
/** The part of a browser GeolocationPosition the recorder reads. */
export interface PositionFix {
coords: {
longitude: number;
latitude: number;
accuracy?: number | null;
};
timestamp: number;
}
/** navigator.geolocation, or anything with the same two calls (a native bridge, a test). */
export interface PositionSource {
watchPosition(success: (fix: PositionFix) => void, error?: ((err: unknown) => void) | null, options?: PositionOptions): number;
clearWatch(id: number): void;
}probe.ts
ts
/** API limits (api/src/modules/traces/traffic.ts). */
export const PROBE_FORMAT = "dock.probe.v1";
/** Points per batch. Older points are dropped first when a send keeps failing. */
export const PROBE_MAX_POINTS = 900;
/** Seconds between sends. */
export const PROBE_SEND_S = 30;
/** The API's window: the vehicle token is made anew in each. */
export const PROBE_WINDOW_S = 900;
/** 24 random characters: a vehicle token. Never a plate, phone or device id. */
export function probeToken(): string;
/** One batch for POST /v1/traffic/probes: the format, the window's vehicle token and the points. Nothing else. */
export interface ProbeBody {
format: typeof PROBE_FORMAT;
vehicle: string;
points: TracePoint[];
}
/**
* One vehicle's points since the last send, in memory only. One point per second at most; a fix that goes back in
* time, is outside Mongolia, or is less accurate than `maxAccuracyM` is left out. The last point sent opens the next
* batch, so the segment across a send is counted once. The token changes with the 15-minute window.
*/
export class ProbeBuffer {
constructor(maxAccuracyM?: number, token?: () => string);
/** Points waiting to be sent. */
get size(): number;
/** true when the fix was kept. */
add(fix: PositionFix): boolean;
/** The batch to send now, or null under 2 points. The points leave the buffer; the last one opens the next batch. */
take(): ProbeBody | null;
/** Drops every point held, the carried one too. */
clear(): void;
}event.ts
ts
/** API limits (api/src/modules/traces/events.ts). */
export const EVENT_POSITION_FORMAT = "dock.position.v1";
/** Points per batch. Older points are dropped first when a send keeps failing. */
export const EVENT_MAX_POINTS = 900;
/** Seconds between sends: the live page shows points of the last 2 minutes. */
export const EVENT_SEND_S = 10;
/** One batch for POST /v1/events/{id}/positions: the format, consent, the participant token and the points. Nothing else. */
export interface EventPositionBody {
format: typeof EVENT_POSITION_FORMAT;
consent: 'opt_in';
participant: string;
points: TracePoint[];
}
/**
* One participant's points since the last send, in memory only. One point per second at most; a fix that goes back
* in time, is outside Mongolia, or is less accurate than `maxAccuracyM` is left out. The participant token is random,
* made once per buffer: never a name, bib, phone or device id.
*/
export class EventBuffer {
/** The random participant token sent with every batch of this run. */
readonly participant: string;
constructor(maxAccuracyM?: number, token?: () => string);
/** Points waiting to be sent. */
get size(): number;
/** true when the fix was kept. */
add(fix: PositionFix): boolean;
/** The batch to send now, or null when empty. The points leave the buffer. */
take(): EventPositionBody | null;
}sand.ts
ts
/** One stretch in an offline pack's sand.geojson: the part of one car way inside an OSM natural=sand area. */
export interface SandFeature {
type: 'Feature';
geometry: {
type: 'LineString';
coordinates: LonLat[];
};
properties: {
/** The area's OSM element, as way/123 or relation/123. */
area: string;
/** The area's OSM name. */
name: string | null;
/** The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. Absent
* when name is the OSM text. Show name. Packs before offline v11 lack it. */
name_osm?: string;
/** The stretch's length, km. */
km: number;
/** The car way's OSM id. Null on a new track alone. */
way_id: number | null;
new_track: boolean;
/** The way's OSM highway and surface tags, as on crossings. Null on a new track alone. */
road: CrossingRoad | null;
};
}
/** An offline pack's sand.geojson (manifest files row and sand block; index.json sand_sha256). Check its sha256
* against the manifest before use. Packs built before 2026-10-03 have none. */
export interface PackSand {
type: 'FeatureCollection';
/** Stretches in the file, all Mongolia. */
written: number;
km: number;
/** OSM sand areas the stretches lie in. */
areas: number;
/** Stretches in a named area. */
named: number;
/** Stretches on a paved road (road.paved true), as town streets drawn inside sand. */
on_paved: number;
/** A route runs on a stretch where its line comes this close, m. */
match_m: number;
rule: string;
rule_mn: string;
source: string;
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. Packs before offline v11
* lack them. */
names_folded?: number;
name_rule?: string;
name_rule_mn?: string;
note: string;
note_mn: string;
features: SandFeature[];
}
/** A stretch through OSM sand the route runs on. */
export interface SandOnRoute {
area: string;
name: string | null;
/** The OSM text when name was folded to one script, else null. Show name. */
name_osm: string | null;
/** The whole stretch, km, from the pack. The route may run on part of it: on_m. */
km: number;
way_id: number | null;
new_track: boolean;
road: CrossingRoad | null;
/** Along the route to where it meets the stretch, m, rounded. */
along_m: number;
/** Metres of the stretch the route runs on, rounded. */
on_m: number;
/** The stretch's first point the route meets, in route order. */
point: LonLat;
}
/** What sandOnRoute() returns. */
export interface RouteSand {
match_m: number;
min_on_m: number;
/** In route order. */
stretches: SandOnRoute[];
/** Sum of on_m, m. */
on_m: number;
/** Show with the stretches: from OSM tags, not field checked. */
note: string;
note_mn: string;
rule: string;
rule_mn: string;
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. Null when the pack has no rule. */
name_rule: string | null;
name_rule_mn: string | null;
}
/** A route that runs on less of a stretch than this, m, only crosses it, unless it runs on all of the stretch. */
export const SAND_MIN_ON_M = 20;
/**
* Offline: the stretches through OSM sand a route line runs on, from an offline pack's sand.geojson, in route order.
* The same rule as wetlandsOnRoute: the stretch is cut in pieces of at most match_m; a piece is on the route when both
* its ends lie within match_m of the line. A stretch is on the route when its pieces on the route make at least
* SAND_MIN_ON_M, or when all of it is on the route. Throws RangeError for a line under 2 points.
*/
export function sandOnRoute(sand: PackSand, line: LonLat[]): RouteSand;water.ts
ts
/** The OSM tag the water point carries. */
export type WaterKind = 'drinking_water' | 'water_point' | 'water_tap' | 'water_well' | 'spring';
/** One water point in an offline pack's water.geojson: an OSM node or way, a way at a point on its surface. */
export interface WaterFeature {
type: 'Feature';
geometry: {
type: 'Point';
coordinates: LonLat;
};
properties: {
/** The first of drinking_water, water_point, water_tap, water_well, spring the OSM element carries. */
kind: WaterKind;
name: string | null;
/** The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. Absent
* when name is the OSM text. Show name. Packs before offline v12 lack it. */
name_osm?: string;
name_en: string | null;
/** Only what OSM says: true for drinking_water=yes (or amenity=drinking_water with no such tag), false for
* drinking_water=no, null when OSM says nothing. Never guessed from kind. */
drinkable: boolean | null;
/** The OSM drinking_water tag as written. */
drinking_water: string | null;
/** The OSM pump tag as written. */
pump: string | null;
/** The OSM element, as node/1 or way/1. */
osm: string;
};
}
/** Water points per drinkable value. */
export interface WaterDrinkable {
yes: number;
no: number;
/** OSM says nothing. */
unknown: number;
}
/** An offline pack's water.geojson (manifest files row and water block; index.json water_sha256). Check its sha256
* against the manifest before use. Packs built before 2026-10-03 have none. */
export interface PackWater {
type: 'FeatureCollection';
/** Water points in the file, all Mongolia. */
written: number;
/** Per kind, all Mongolia. */
counts: Partial<Record<WaterKind, number>>;
named: number;
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. Packs before offline v12 lack
* them. */
names_folded?: number;
name_rule?: string;
name_rule_mn?: string;
/** All Mongolia. */
drinkable: WaterDrinkable;
/** A water point is near a route within this many metres of its line, as fuel and ger camps. */
near_m: number;
rule: string;
rule_mn: string;
near_rule: string;
near_rule_mn: string;
source: string;
note: string;
note_mn: string;
features: WaterFeature[];
}
/** A water point near the route. */
export interface WaterOnRoute {
kind: WaterKind;
point: LonLat;
name: string | null;
/** The OSM text when name was folded to one script, else null. Show name. */
name_osm: string | null;
name_en: string | null;
drinkable: boolean | null;
drinking_water: string | null;
pump: string | null;
osm: string;
/** Along the route to its point nearest the water, m, rounded. A tie goes to the earlier pass. */
along_m: number;
/** From the route line, m, rounded. */
off_m: number;
}
/** What waterOnRoute() returns. */
export interface RouteWater {
near_m: number;
/** In route order. */
water: WaterOnRoute[];
/** Of these points. unknown: OSM says nothing, not "not drinkable". */
drinkable: WaterDrinkable;
/** Show with the points: from OSM tags, not field checked; a well may be dry, broken or locked. */
note: string;
note_mn: string;
rule: string;
rule_mn: string;
near_rule: string;
near_rule_mn: string;
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. Null when the pack has no rule. */
name_rule: string | null;
name_rule_mn: string | null;
}
/**
* Offline: the OSM water points (wells, springs, taps, drinking water) within `nearM` of a route line, from an
* offline pack's water.geojson, in route order. nearM defaults to the pack's near_m (2000), lane D's route pages.
* Throws RangeError for a line under 2 points or a nearM not above 0.
*/
export function waterOnRoute(water: PackWater, line: LonLat[], nearM?: number): RouteWater;wetlands.ts
ts
/** One stretch in an offline pack's wetlands.geojson: the part of one car way inside an OSM natural=wetland area. */
export interface WetlandFeature {
type: 'Feature';
geometry: {
type: 'LineString';
coordinates: LonLat[];
};
properties: {
/** The area's OSM wetland tag, as marsh, bog, wet_meadow or fen; unknown when the area has none. */
wetland: string;
/** The area's OSM element, as way/123 or relation/123. */
area: string;
/** The area's OSM name. */
name: string | null;
/** The OSM name as written, when it mixed Latin and Cyrillic in a word and name is folded to one script. Absent
* when name is the OSM text. Show name. Packs before offline v12 lack it. */
name_osm?: string;
/** The stretch's length, km. */
km: number;
/** The car way's OSM id. Null on a new track alone. */
way_id: number | null;
new_track: boolean;
/** The way's OSM highway and surface tags, as on crossings. Null on a new track alone. */
road: CrossingRoad | null;
};
}
/** An offline pack's wetlands.geojson (manifest files row and wetlands block; index.json wetlands_sha256). Check its
* sha256 against the manifest before use. Packs built before 2026-10-03 have none. */
export interface PackWetlands {
type: 'FeatureCollection';
/** Stretches in the file, all Mongolia. */
written: number;
km: number;
/** OSM wetland areas the stretches lie in. */
areas: number;
/** Per wetland kind, all Mongolia. */
counts: Record<string, {
stretches: number;
km: number;
}>;
/** A route runs on a stretch where its line comes this close, m. */
match_m: number;
rule: string;
rule_mn: string;
source: string;
/** Names in the file folded to one script, all Mongolia, and the rule, en and mn. Packs before offline v12
* lack them. */
names_folded?: number;
name_rule?: string;
name_rule_mn?: string;
note: string;
note_mn: string;
features: WetlandFeature[];
}
/** A stretch through OSM wetland the route runs on. */
export interface WetlandOnRoute {
wetland: string;
area: string;
name: string | null;
/** The OSM text when name was folded to one script, else null. Show name. */
name_osm: string | null;
/** The whole stretch, km, from the pack. The route may run on part of it: on_m. */
km: number;
way_id: number | null;
new_track: boolean;
road: CrossingRoad | null;
/** Along the route to where it meets the stretch, m, rounded. */
along_m: number;
/** Metres of the stretch the route runs on, rounded. */
on_m: number;
/** The stretch's first point the route meets, in route order. */
point: LonLat;
}
/** What wetlandsOnRoute() returns. */
export interface RouteWetlands {
match_m: number;
min_on_m: number;
/** In route order. */
stretches: WetlandOnRoute[];
/** Sum of on_m, m. */
on_m: number;
/** Show with the stretches: from OSM tags, not field checked. */
note: string;
note_mn: string;
rule: string;
rule_mn: string;
/** How the pack folds a name that mixes Latin and Cyrillic, en and mn. Null when the pack has no rule. */
name_rule: string | null;
name_rule_mn: string | null;
}
/** A route that runs on less of a stretch than this, m, only crosses it, unless it runs on all of the stretch. */
export const WETLAND_MIN_ON_M = 20;
/**
* Offline: the stretches through OSM wetland a route line runs on, from an offline pack's wetlands.geojson, in route
* order. The stretch is cut in pieces of at most match_m; a piece is on the route when both its ends lie within
* match_m of the line. A stretch is on the route when its pieces on the route make at least WETLAND_MIN_ON_M, or
* when all of it is on the route. Throws RangeError for a line under 2 points.
*/
export function wetlandsOnRoute(wetlands: PackWetlands, line: LonLat[]): RouteWetlands;types.ts
ts
/** [lon, lat], WGS84. */
export type LonLat = [number, number];
/** How far the place matched an official state address: fully, in part, or not at all (unofficial). */
export type OfficialStatus = 'matched' | 'partial' | 'none';
/** National format, as written by the source. */
export interface OfficialAddress {
country: string;
city_or_aimag: string;
district_or_soum: string;
khoroo_or_bag?: string;
street_or_microdistrict?: string;
building_or_khashaa?: string;
entrance?: string;
floor?: string;
unit?: string;
/** Unified postal code from the state register, "13381-0017". */
postal_code?: string;
/** The building's own name from the state register, when it has one. */
building_name?: string;
}
/** The entrance a building code points to. */
export interface Entrance {
kind: 'door' | 'gate' | 'vehicle_gate' | 'stairwell';
/** Entrance or gate number as people say it. */
letter: string | null;
floors_served: number[] | null;
}
/** A flat, office or shop behind a door, when the code was given with a unit. */
export interface Unit {
number: string;
floor: number | null;
kind: 'flat' | 'office' | 'shop' | 'stall' | 'kiosk';
}
/** A door. Official address first, code after it. No owner, no phone. */
export interface Door {
official_address: OfficialAddress | null;
official_status: OfficialStatus;
/** true unless official_status is matched. Show "unofficial" when true. */
unofficial: boolean;
official_source: string | null;
official_date: string | null;
/** The date of the door or unit row behind the code, YYYY-MM-DD. null: not known. Absent before API 0.87.0. */
source_date?: string | null;
code: string;
natural_mn: string | null;
natural_en: string | null;
long_mn: string | null;
long_en: string | null;
point: LonLat;
entrance: Entrance;
unit: Unit | null;
/** PLAN 5 level, 0 to 4. null means unknown. */
confidence: number | null;
/** Confirmed driver taps for this door since its point last moved. 0 when none. */
confirmations?: number;
source: string;
instructions_mn: string | null;
instructions_en: string | null;
aliases: string[];
}
/** What a place is. */
export type PlaceKind = 'door' | 'business' | 'building' | 'district' | 'khoroo' | 'area' | 'street' | 'landmark';
/** A place in geocode, reverse and place(id) answers. Show unofficial unless official_status is matched. */
export interface Place {
official_address: OfficialAddress | null;
official_status: OfficialStatus;
unofficial: boolean;
official_source: string | null;
official_date: string | null;
/** The official address as one Cyrillic line, postal code last. null when nothing is known. */
official_line?: string | null;
/** Building code. null when the place has none. */
code: string | null;
kind: PlaceKind;
/** Stable id. Never show it to people as an address. */
id: string;
name_mn: string | null;
name_en: string | null;
long_mn: string | null;
long_en: string | null;
point: LonLat;
confidence: number | null;
source: string;
/** The date of the row's current version, YYYY-MM-DD, or the register's date for a door. null: not known. Absent before API 0.87.0. */
source_date?: string | null;
/** A business: true when its company has a claim the state register confirmed, false when not or pending; absent for any other kind. Lane U hides the claim link when true. */
claimed?: boolean;
/** A business, landmark or street: the khoroo its point stands in. null when no polygon covers it; absent for any other kind. */
khoroo?: number | null;
/** A business: its point lies inside a restricted site, so hide the claim link and any edit. Absent for any other kind. */
restricted?: boolean;
/** A building with an indoor map: venue_id (GET /v1/venue/{id}), category (airport, mall, ...; API 0.102.0), levels (floor numbers, lowest first) and units, the units the query names with level, number, source and confidence (up to 5). Absent for every other row. */
indoor?: {
venue_id: string;
category: string;
levels: number[];
units: IndoorUnitHit[];
};
}
/** One geocode result. */
export interface GeocodeResult extends Place {
/** Higher is better. Compare only with results of the same layer in one response. */
score: number;
/**
* 1: the words of a name or address matched. 2: the place description matched the meaning; after every layer 1,
* except near rows. 3: near rows reordered by a reranking model on the description (API 0.30.0); score is its, 0 to 1.
*/
layer: 1 | 2 | 3;
/** The landmark a landmark-relative query named ("Хүннү моллын ард"), for a place found around it. null otherwise. */
near: GeocodeNear | null;
/** A bus stop: the city bus lines that call at it ("Ч:41"), as nearby() gives them. null for every other place. */
lines: string[] | null;
/** A business or landmark: its map category, the one nearby() gives ("pharmacy"). null for any other place and when unknown. */
category: string | null;
/** A building: what it is, apartment, ger_house, commercial, public, industrial, garage or other. null when the building has no type yet; absent for any other kind. */
building_type?: string | null;
/** A building: its number of storeys (OSM building:levels), so a card can tell a tower from the podium that shares its name. null when unknown; absent for any other kind. */
building_levels?: number | null;
/** A building: its whole name is only a kind word ("Үсчин"), so show it as a kind, not a title. null when not checked; absent for any other kind. */
name_is_kind_word?: boolean | null;
/** A building: the OSM category of the one POI in its footprint (shop=hairdresser); null when unknown. */
poi_category?: string | null;
/** A building: up to 5 company names registered at it (register match only); [] when none; absent for any other kind. */
poi_names?: string[];
/** A company from a building's directory (API 0.90.0): its floor from the legal address in the state register. null when not said, and for every other place. */
floor?: number | null;
/** A company from a building's directory (API 0.90.0): its flat or office number (N тоот). null when not said. */
flat?: string | null;
}
/** The landmark, building or door a near-me query was read against. */
export interface GeocodeNear {
kind: 'landmark' | 'building' | 'door';
id: string;
name_mn: string | null;
name_en: string | null;
/** A side searches within 55 degrees of it; near and beside the whole circle. */
relation: 'north' | 'east' | 'south' | 'west' | 'near' | 'beside';
/** From the landmark to the place, 150 at most. */
metres: number;
}
/** A unit code found in the query: the unit card as GET /v1/door/{code} answers it, plus the floor and flat spoken after the code. */
export interface GeocodeUnit {
official_address: OfficialAddress | null;
official_status: OfficialStatus;
/** true unless official_status is matched. Show "unofficial" when true. */
unofficial: boolean;
official_source: string | null;
official_date: string | null;
/** The date of the door or unit row behind the code, YYYY-MM-DD. null: not known. Absent before API 0.87.0. */
source_date?: string | null;
code: string;
kind: 'building' | 'khashaa';
/** The official address as one Cyrillic line, postal code last. null when nothing is known. */
official_line: string | null;
/** District and khoroo the building lies in, only when its address is not matched. Never the official address; unofficial stays true. null when matched or not known. */
area_line?: string | null;
/** What the building is: apartment, ger_house, commercial, public, industrial, garage or other. null for a khashaa or when not known yet; never guessed. */
building_type?: string | null;
/** Floors above ground from the building record. null for a khashaa or when not known; never guessed. */
building_levels?: number | null;
/** The OpenStreetMap opening_hours tag of the landmark inside the building, as written; a tag can be old. null for a khashaa or when none. */
opening_hours?: string | null;
hours_source?: string | null;
name_mn: string | null;
name_en: string | null;
/** [lon, lat] of a point inside the footprint. */
point: LonLat;
/** N in "N давхар", null when not said. */
floor: number | null;
/** N in "N тоот", null when not said. */
flat: string | null;
/** The building holds an indoor map: its venue id (GET /v1/venue/{id}). Absent when it holds none. */
venue_id?: string;
}
/** GET /v1/door/at: the address unit at a point, a хашаа first, else the smallest building that holds it. */
export interface UnitAt extends Omit<GeocodeUnit, 'code' | 'floor' | 'flat'> {
/** null while the unit has no code yet. */
code: string | null;
/** Stable id of the unit. Never shown to people as an address. */
id: string;
}
/** A unit inside a building with an indoor map, as a search row lists it (API 0.100.0). */
export interface IndoorUnitHit {
unit_id: string;
level: number;
name_mn: string | null;
number: string | null;
category: string;
source: string;
confidence: number | null;
}
/** One unit the query names, as its own answer (API 0.100.0): the venue, its building code, the floor and a point inside the unit. */
export interface IndoorUnitAnswer {
unit_id: string;
venue_id: string;
venue_name_mn: string | null;
code: string | null;
level: number;
name_mn: string | null;
number: string | null;
category: string;
point: LonLat | null;
source: string;
confidence: number | null;
}
/** One floor of a venue (API 0.100.0). `ordinal` is the floor as people say it: 0 ground, 1 first, -1 basement. */
export interface IndoorLevel {
ordinal: number;
short_name_mn: string;
name_mn: string | null;
outdoor: boolean;
source: string;
confidence: number | null;
}
/** GET /v1/venue/{id}: a building with an indoor map, its outline (GeoJSON Polygon, [lon, lat]) and its current floors, lowest first. */
export interface Venue {
id: string;
name_mn: string;
name_en: string | null;
category: string;
source: string;
confidence: number | null;
/** The building code of the real-map building the venue stands in; null until it has one. */
building_code: string | null;
/** The district and khoroo covering the venue (API 0.101.0), and soum_mn and aimag_mn from the OSM admin polygons (API 0.103.0); null when no polygon covers it. */
admin: {
district_mn: string | null;
district_en: string | null;
khoroo: number | null;
soum_mn: string | null;
aimag_mn: string | null;
} | null;
outline: {
type: 'Polygon';
coordinates: number[][][];
} | Record<string, unknown>;
levels: IndoorLevel[];
}
/** One venue near a point (GET /v1/venue?near=lon,lat, API 0.105.0). `distance_m` is 0 when the point is inside the outline. */
export interface NearVenue {
id: string;
name_mn: string;
category: string;
distance_m: number;
levels: number[];
}
/** GET /v1/venue?near=lon,lat: venues within 200 m, nearest first, at most 10. */
export interface NearVenues {
results: NearVenue[];
}
/** One unit, opening or amenity on a floor. `properties.feature_type` says which; then category, level, source, confidence, and the names, number and area a unit has. */
export interface LevelFeature {
type: 'Feature';
id: string;
geometry: Record<string, unknown>;
properties: Record<string, unknown>;
}
/** GET /v1/venue/{id}/level/{n}: one floor as GeoJSON. */
export interface VenueLevel {
type: 'FeatureCollection';
venue_id: string;
level: number;
features: LevelFeature[];
}
/** GET /v1/venue/{id}/spot: what a printed plate for one unit says. `link` is what the plate's QR holds. */
export interface VenueSpot {
venue_id: string;
venue_name_mn: string;
unit_id: string;
level: number;
level_name_mn: string;
name_mn: string | null;
number: string | null;
label: string;
point: LonLat;
link: string;
source: string;
confidence: number | null;
}
/** One step of a walk inside a venue, in Mongolian, with the floor it is on. `at` is [lon, lat] where the step begins. */
export interface IndoorStep {
floor: number;
text: string;
distance_m: number;
at: LonLat;
}
/** One floor of a walk: its line (GeoJSON LineString, [lon, lat]) and how it ends: `stairs`, `escalator`, `elevator` or `arrive`; `to_floor` is the floor a connector reaches, null on arrival. */
export interface IndoorLeg {
floor: number;
distance_m: number;
geometry: {
type: 'LineString';
coordinates: LonLat[];
} | Record<string, unknown>;
ends_with: 'stairs' | 'escalator' | 'elevator' | 'arrive';
to_floor: number | null;
}
/** GET /v1/venue/{id}/route: the walk inside a venue to one unit, floor by floor. `to` is the unit: level, point, name_mn, number, source, confidence. */
export interface VenueRoute {
venue_id: string;
distance_m: number;
duration_s: number;
legs: IndoorLeg[];
steps: IndoorStep[];
to: Record<string, unknown>;
}
/** GET /v1/venue/{id}/directions: the road to the street entrance, then the walk inside. Distance and duration are road plus walk. `road.entrance` holds level, point and core_entrance_id. */
export interface VenueDirections {
venue_id: string;
distance_m: number;
duration_s: number;
road: {
mode: RouteMode;
distance_m: number;
duration_s: number;
geometry: {
type: 'LineString';
coordinates: LonLat[];
} | Record<string, unknown>;
steps: {
mn: string;
en: string;
distance_m: number;
at: LonLat;
}[];
entrance: {
level: number;
point: LonLat;
core_entrance_id: string | null;
};
};
indoor: VenueRoute;
/** Notes that do not block the walk; code entrance_closed when the entrance used is closed at arrival. */
warnings: Record<string, unknown>[];
}
/** GET /v1/geocode. */
export interface GeocodeResponse {
results: GeocodeResult[];
/** Set when the whole query is a unit code, alone ("7QSVX") or spoken ("7QSVX, 12 давхар, 1204 тоот"). null otherwise and offline. */
unit: GeocodeUnit | null;
/** The units inside indoor-mapped buildings that the query names, each as its own answer; up to 5. Absent when none. */
indoor_units?: IndoorUnitAnswer[];
}
/** One reverse result. */
export interface ReverseResult extends Omit<Place, 'kind'> {
/** What it is. `khashaa` (API 0.93.8): a minted ger plot that holds the point, at distance 0, with its code and official line in long_mn and name_mn null; reverse gives it only when `types` is absent. */
kind: PlaceKind | 'khashaa';
/** Metres from the query point. 0 means the point is inside. */
distance_m: number;
/** A bus stop: the city bus lines that call at it ("Ч:41"), as nearby() gives them. null for every other place. */
lines: string[] | null;
/** A business or landmark: its map category, the one nearby() gives ("pharmacy"). null for any other place and when unknown. */
category: string | null;
/** A building: what it is, apartment, ger_house, commercial, public, industrial, garage or other. null when the building has no type yet; absent for any other kind. */
building_type?: string | null;
/** A building: its number of storeys (OSM building:levels), so a card can tell a tower from the podium that shares its name. null when unknown; absent for any other kind. */
building_levels?: number | null;
/** A building: its whole name is only a kind word ("Үсчин"), so show it as a kind, not a title. null when not checked; absent for any other kind. */
name_is_kind_word?: boolean | null;
/** A building: the OSM category of the one POI in its footprint (shop=hairdresser); null when unknown. */
poi_category?: string | null;
/** A building: up to 5 company names registered at it (register match only); [] when none; absent for any other kind. */
poi_names?: string[];
}
/** A kind reverse can keep (types): district and khoroo always come with the answer. */
export type ReverseType = 'door' | 'business' | 'building' | 'landmark';
/** GET /v1/reverse. */
export interface ReverseResponse {
point: LonLat;
district_mn: string | null;
district_en: string | null;
/** null when no khoroo polygon covers the point. Never guessed. */
khoroo: number | null;
/** The soum and aimag covering the point (OSM boundaries, API 0.104.0); null when no polygon covers it. */
soum_mn?: string | null;
aimag_mn?: string | null;
/** true when the point lies inside a restricted site (PLAN 20.2): results is then empty. */
restricted?: boolean;
results: ReverseResult[];
}
/**
* A category nearby takes: the map's place icons. atm, bank, cafe, car_wash, fuel, hotel, pharmacy, restaurant and shop are registered
* businesses only, so they answer nothing until a business matches the registry: a business through its door, or a
* store of a registered chain (code null).
*/
export type NearbyCategory = 'atm' | 'bank' | 'bridge' | 'cafe' | 'car_wash' | 'culture' | 'embassy' | 'fuel' | 'government' | 'hospital' | 'hotel' | 'kindergarten' | 'mall' | 'market' | 'monument' | 'office' | 'park' | 'pharmacy' | 'religion' | 'restaurant' | 'school' | 'shop' | 'sport' | 'station' | 'stop' | 'university';
/** One nearby result. */
export interface NearbyResult extends Place {
/**
* Unit code of the place: a door, a business through its door, or the building or хашаа a store or company stands
* in. null when none is minted yet: show "код алга" and the official address, which is the building's it stands in,
* or none.
*/
code: string | null;
/** A registered company from a building's directory (API 0.92.1): its floor from the state register. null when not said, and for every other place. */
floor: number | null;
/** A company's flat or office number (N тоот) from the same address. null when not said. */
flat: string | null;
/** The OpenStreetMap opening_hours tag as written; may be old. null when the source has none. */
opening_hours: string | null;
/** Where opening_hours came from. null when opening_hours is null. */
hours_source: string | null;
/** Open now in Ulaanbaatar time, from a plain opening_hours tag (OpenStreetMap, may be old). null when unknown: no tag, or not a plain one. */
open_now: boolean | null;
/** When the place closes today, HH:MM in Ulaanbaatar time, while open_now is true. null when closed, unknown, or open 24/7. */
open_until: string | null;
/** When a closed place opens later today, HH:MM in Ulaanbaatar time. null when open, unknown, or nothing starts later today. */
opens_at: string | null;
/** Only when nearby() was asked for photo: one openly licensed photo of a landmark or building, as address() gives it. null when none. */
photo?: {
url: string;
credit: string;
license: string;
source: string;
author?: string;
} | null;
/** The map category, as the place icon. */
category: NearbyCategory;
/** Metres from the query point. */
distance_m: number;
/**
* The city bus lines that call at this stop, as the bus shows them, letter then number ("Ч:41"). null when the
* capital's public transport data lists none here.
*/
lines: string[] | null;
}
/** GET /v1/nearby. */
export interface NearbyResponse {
point: LonLat;
/** Nearest first, within radius. Empty when none. */
results: NearbyResult[];
/** How many more places of these categories lie within radius past limit. 0 when results holds them all. */
more: number;
/** Only with open=1: how many of the nearest 20 places have no readable hours (open_now null). They are not in results. */
hours_unknown?: number;
}
/** What reportDoor() sends: a door in the wrong place, missing, a duplicate, or not on the map. */
export type DoorReport = {
kind: 'wrong_location';
code: string;
point: LonLat;
note?: string;
} | {
kind: 'missing';
code: string;
note?: string;
} | {
kind: 'duplicate';
code: string;
duplicate_of: string;
note?: string;
} | {
kind: 'unmapped';
point: LonLat;
note?: string;
};
/** POST /v1/door-reports. */
export interface DoorReportReceipt {
id: string;
status: 'queued' | 'already_queued';
kind: DoorReport['kind'];
code: string | null;
target: 'door' | 'building' | 'plot' | null;
moved_m: number | null;
received_at: string;
next: 'review';
}
/** One day in usage(). */
export interface UsageDay {
/** Ulaanbaatar day, YYYY-MM-DD. */
day: string;
calls: number;
tiles: number;
door_misses: number;
keys: number;
}
/** One route of the org over the days asked (API 0.62.0). */
export interface UsageEndpoint {
/** Method and route pattern, e.g. `GET /v1/door/:code`. Never a code, a name or a query. */
endpoint: string;
/** Requests to this route. The tiles route counts tiles. */
calls: number;
/** Of those, lookups that found no door. */
misses: number;
}
/** Your daily caps; null is no cap. */
export interface UsageCaps {
daily_quota: number | null;
tile_daily_quota: number | null;
door_miss_cap: number | null;
}
/** What a free org gets on the partner plan. Prices are not published yet. */
export interface UsageUpgrade {
plan: 'partner';
caps: UsageCaps;
rate_limit: number;
tile_rate_limit: number;
added_scopes: string[];
}
/** GET /v1/usage: the calling key's organization. Caps are null when there is no cap. */
export interface UsageResponse {
org: string;
/** free: made by POST /v1/signup. custom: a partner org. */
plan: 'free' | 'custom';
/** The partner plan for a free org, null for a partner org. */
upgrade: UsageUpgrade | null;
timezone: string;
/** Unix seconds of the next daily reset. */
reset: number;
key: {
prefix: string | null;
scopes: string[];
rate_limit: number;
tile_rate_limit: number;
};
caps: UsageCaps;
/**
* Features open to the org, A to Z, e.g. `cargo.eta` (API 0.51.0). Routes of any other feature answer 403
* `feature_off`. Missing from an API before 0.51.0.
*/
features?: string[];
/** Newest first. */
days: UsageDay[];
/**
* Every route the org called over the same days, most calls first, then A to Z (API 0.62.0). Missing from an API
* before 0.62.0.
*/
endpoints?: UsageEndpoint[];
}
/** How the route is travelled. */
export type RouteMode = 'driving' | 'walking' | 'cycling';
/** The door at one end of a route. Show `unofficial` as "(unofficial)" unless official_status is matched. */
export interface RouteDoor {
official_address: OfficialAddress | null;
official_status: OfficialStatus;
unofficial: boolean;
code: string;
natural_mn: string | null;
natural_en: string | null;
entrance: Entrance;
confidence: number | null;
}
/**
* The entrance at one end of a route, when that end was sent as an entrance id (GET /v1/address/{id}/entrances,
* GET /v1/entrances/near). Show `unofficial` as "(unofficial)" unless official_status is matched; confidence 0 with
* source generated was placed by rule (таамаг).
*/
export interface RouteEntrance {
id: string;
building: string;
/** null: no door minted for this entrance yet. */
code: string | null;
official_address: OfficialAddress | null;
official_status: OfficialStatus;
unofficial: boolean;
kind: 'door' | 'gate' | 'vehicle_gate' | 'stairwell';
letter: string | null;
confidence: number | null;
source: string;
}
/** The address unit (building or khashaa) a route end was given as, by its code. The route ends at its street side. */
export interface RouteUnit {
code: string;
kind: 'building' | 'khashaa';
official_address: OfficialAddress | null;
official_status: OfficialStatus;
unofficial: boolean;
/** true: the end is the gate, or the outline of the building or khashaa at the street, within about 25 m of where the line meets the street. false: the outline is farther than that from the street, the walk is longer. */
street_side: boolean;
}
/** One end of a route. */
export interface RouteEnd {
/** What was asked for: the entrance when a code was given. */
point: LonLat;
/** Where the line starts or ends, on the road network. */
snapped: LonLat;
/** Straight metres from snapped to point. Walked, never drawn as road. */
gap_m: number;
door: RouteDoor | null;
/** The entrance, when this end was sent as an entrance id; else null. Absent from older APIs. */
entrance?: RouteEntrance | null;
/** Present only when this end was sent as a unit code. */
unit?: RouteUnit;
/** The date of the door, unit or entrance behind this end, YYYY-MM-DD; null for a bare point. Absent before API 0.87.0. */
source_date?: string | null;
}
/** The road closures a route avoided (GET /v1/closures, active on as_of). */
export interface RouteClosures {
/** The route day, YYYY-MM-DD, Ulaanbaatar. */
as_of: string;
/** Closure ids, in id order. Each was kept out of the route, whether or not it came near. Show them with it. */
avoided: string[];
}
/** Driving time with all partners' measured moving speed per road (API 0.48.0). */
export interface RouteMeasured {
/** YYYY-MM: the latest month GET /v1/traces/roads publishes. */
month: string;
/** The hour band of departAt whose speeds were read (API 0.50.0); null without departAt: all hours. */
band?: RoadBand | null;
/** Meters of the line on roads with a published speed that month. 0: none, and duration_s is the free-flow one. */
roads_m: number;
/** Seconds with those roads at their measured moving speed, the rest at the router's. A month average of the band. */
duration_s: number;
}
/** GET /v1/route. duration_s is free-flow; measured has partners' speeds. Avoids the road closures active today. */
export interface Route {
mode: RouteMode;
distance_m: number;
/** Seconds at the router's free-flow speeds. */
duration_s: number;
/** Driving only; null when walking or cycling, or when speeds could not be read. Absent from an API before 0.48.0. */
measured?: RouteMeasured | null;
/** Driving: seconds with a typical load by road class, hour and weekday; null when walking, cycling or unreadable (API 0.97.19). */
expected_duration_s?: number | null;
/** Where that load comes from: model (a table), probes (partners' phones, not yet). */
traffic_source?: 'model' | 'probes';
/** The label to show beside expected_duration_s. */
traffic_note?: string;
geometry: {
type: 'LineString';
coordinates: LonLat[];
};
/** One per hop, in order: from to via[0], ..., the last to to; one without via. Absent from an API before 0.67.0. */
legs?: RouteLeg[];
from: RouteEnd;
/** The via ends in the order sent, snapped where the line passes them; empty without. Absent before API 0.67.0. */
via?: RouteEnd[];
to: RouteEnd;
closures: RouteClosures;
source: string;
}
/**
* GET /v1/matrix (API 0.63.0): free-flow travel time and distance from every from to every to in one call. Cell
* [i][j] goes from from[i] to to[j]; null where no path joins them. No measured speeds, no line.
*/
export interface Matrix {
mode: RouteMode;
/** Seconds at the router's free-flow speeds, as duration_s in a route. */
durations_s: (number | null)[][];
/** Metres from from[i].snapped to to[j].snapped; null where durations_s is null. */
distances_m: (number | null)[][];
/** The sources in the order asked, each as a route end. */
from: RouteEnd[];
/** The targets in the order asked, each as a route end. */
to: RouteEnd[];
closures: RouteClosures;
source: string;
}
/** One isochrone contour: the area reached within `properties.contour` minutes, GeoJSON, every ring closed. */
export interface IsochroneFeature {
type: 'Feature';
/** contour in minutes; metric is always 'time', as Mapbox's contours_minutes. */
properties: {
contour: number;
metric: 'time';
};
/** A Polygon (rings) or, where the area is in pieces, a MultiPolygon (polygons of rings), [lon, lat]. */
geometry: {
type: 'Polygon';
coordinates: LonLat[][];
} | {
type: 'MultiPolygon';
coordinates: LonLat[][][];
};
}
/**
* GET /v1/isochrone (API 0.64.0): GeoJSON, one feature per contour, the largest first, so drawn in order each smaller
* one lies on top. Free-flow speeds, closures avoided. mode, from, closures and source sit beside the features.
*/
export interface Isochrone {
type: 'FeatureCollection';
features: IsochroneFeature[];
mode: RouteMode;
/** Where the areas are reached from, as a route end. */
from: RouteEnd;
closures: RouteClosures;
source: string;
}
/** One unbroken piece of a matched trace (GET /v1/matching): the roads it went along, in its order. */
export interface Matching {
distance_m: number;
/** Seconds at the router's free-flow speed on each road, no turn costs; not the time the trace took. */
duration_s: number;
geometry: {
type: 'LineString';
coordinates: LonLat[];
};
}
/** Where one point sent lies on the matched line. */
export interface MatchTracepoint {
/** The matching this point lies on, an index into matchings. */
matchings_index: number;
/** [lon, lat] on the matched line. */
location: LonLat;
/** Straight-line metres from the point sent to location. */
gap_m: number;
}
/**
* GET /v1/matching (API 0.65.0): a GPS trace on the road network. One matching per unbroken piece; one tracepoint per
* point sent, in its order, null where no road was found for it or it is inside a restricted site.
*/
export interface Match {
mode: RouteMode;
matchings: Matching[];
tracepoints: (MatchTracepoint | null)[];
source: string;
}
/** One hop of an optimized trip, from one stop to the next in the order driven, or of a route with via (API 0.67.0). */
export interface TripLeg {
/** Metres from one stop to the next along the road network. */
distance_m: number;
/** Seconds from one stop to the next at the router's free-flow speeds. */
duration_s: number;
}
/** One turn-by-turn step of a route (API 0.74.0): what to do at location, then distance_m to the next step. */
export interface RouteStep {
kind: 'depart' | 'left' | 'right' | 'slight_left' | 'slight_right' | 'sharp_left' | 'sharp_right' | 'uturn' | 'straight' | 'keep_left' | 'keep_right' | 'ramp_left' | 'ramp_right' | 'merge' | 'roundabout' | 'arrive' | 'arrive_left' | 'arrive_right';
location: LonLat;
distance_m: number;
duration_s: number;
/** The road taken here, as OpenStreetMap names it; null when unnamed. */
street: string | null;
/** Mongolian voice phrase ids in the order spoken (Navigator, VoicePlayer). */
voice: string[];
mn: string;
en: string;
}
/** A route's leg: a trip leg with its steps (absent from an API before 0.74.0). */
export interface RouteLeg extends TripLeg {
/** The floor of this leg: 0 on every road leg (absent from an API that does not send it yet). */
floor?: number;
steps?: RouteStep[];
}
/** The whole trip in the order driven, snapped stop to snapped stop. */
export interface Trip {
distance_m: number;
/** Seconds on the road at the router's free-flow speeds. No live traffic, no measured speeds, no time at the stops. */
duration_s: number;
geometry: {
type: 'LineString';
coordinates: LonLat[];
};
/** legs[k] goes from the stop with waypoint_index k to the one with k + 1; on a round trip the last goes back to the first. */
legs: TripLeg[];
}
/** One stop as sent, a route end with its place in the trip. */
export interface TripWaypoint extends RouteEnd {
/** Where this stop comes in the trip: 0 is the first, always the first sent. */
waypoint_index: number;
}
/**
* GET /v1/optimized-trips (API 0.66.0): the stops in the order with the least time on the road. trips holds one trip,
* as Mapbox's answer does; waypoints are the stops in the order sent. Today's closures avoided.
*/
export interface OptimizedTrip {
mode: RouteMode;
/** true: the trip goes back to the first stop. false: it ends at the last stop sent. */
roundtrip: boolean;
trips: Trip[];
waypoints: TripWaypoint[];
closures: RouteClosures;
source: string;
}
/** A tile source tilequery reads: the building and entrance layers of the map. */
export type TilequerySource = 'building' | 'entrance';
/** A feature's geometry type in its tile, as Mapbox's tilequery.geometry. */
export type TilequeryGeometry = 'polygon' | 'linestring' | 'point';
/** How a tilequery feature lies from the point sent. */
export interface TilequeryInfo {
/** Metres from the point sent to the nearest part of the feature, to 0.1 m. 0: the point is inside it. */
distance: number;
geometry: TilequeryGeometry;
/** The tile source and layer the feature is in. */
layer: TilequerySource;
}
/** Second-source check of a building footprint. confidence is null when the run gave none: unknown. */
export interface FootprintCheck {
verdict: 'confirmed' | 'new' | 'doubtful';
source: string;
confidence: number | null;
checked_at: string;
}
/**
* The fields of the feature's tile layer, and nothing more: a building has building fields, an entrance entrance
* fields. null where the map does not know. No building codes.
*/
export interface TilequeryProperties {
/** A building: whether its footprint is backed by a second source. null for other kinds and for a building not yet checked. */
footprint_check?: FootprintCheck | null;
/** The building or entrance id, as in its tile. Never an address. */
id: string;
/** Building only. */
name_mn?: string | null;
/** Building only. */
name_en?: string | null;
/** Building only. */
type?: 'apartment' | 'ger_house' | 'commercial' | 'public' | 'industrial' | 'garage' | 'other' | null;
/** Building only. */
status?: 'planned' | 'under_construction' | 'built';
/** Building only. Metres. */
height_m?: number | null;
/** Building only. */
levels?: number | null;
/** Building only: where height_m came from (osm:height, levels, default:*, estimated:*, unknown). */
height_source?: string | null;
/** Building only: 3 a tag or editor, 2 levels, 1 type default, 0 estimated; 0 and 1 are estimates. null: no height. */
height_confidence?: number | null;
/** Place only: the map category (bank, pharmacy, stop, religion, etc.). */
cat?: string | null;
/** Place only: the map rank (higher shows over lower in collisions). */
rank?: number | null;
/** Building only: official building number from the state register. null: no matched or partial official form. */
building_number?: string | null;
/** Building only: the building's name when it is a name, not a purpose. null: no name or no official form. */
building_name?: string | null;
/** Entrance only. */
kind?: 'door' | 'gate' | 'vehicle_gate' | 'stairwell';
/** Entrance only. */
letter?: string | null;
/** Entrance only: its building. null: none on record. */
building_id?: string | null;
/** 0 to 5 (PLAN.md 5). null: not rated yet. */
confidence: number | null;
source: string;
tilequery: TilequeryInfo;
}
/** One building or entrance at or near the point: a Point, the part of it nearest the point sent. */
export interface TilequeryFeature {
type: 'Feature';
/** [lon, lat]; the point sent when it is inside. The 7th decimal is a mark for your organization. */
geometry: {
type: 'Point';
coordinates: LonLat;
};
properties: TilequeryProperties;
}
/**
* GET /v1/tiles/{sources}/tilequery/{lon},{lat} (API 0.68.0), as Mapbox's Tilequery API: GeoJSON, nearest first, then
* by layer and id. The same rows as the tiles: current, not demolished, nothing in a restricted site.
*/
export interface Tilequery {
type: 'FeatureCollection';
features: TilequeryFeature[];
}
/** Why a road is closed. */
export type ClosureKind = 'flood' | 'washout' | 'bridge_out' | 'snow' | 'mud' | 'works' | 'other';
/** One closure in GET /v1/closures. No partner name, no note. */
export interface ClosureItem {
/** cl-000001. Never reused. */
id: string;
kind: ClosureKind;
/** First closed day, YYYY-MM-DD Ulaanbaatar. */
from: string;
/** Last closed day, or null: until lifted. */
to: string | null;
/** One partner's word, not field checked. */
confidence: number;
perimeter_m: number;
/** Ring vertices, the closing one included. */
vertices: number;
/** Closed ring, [lon, lat]. One of Valhalla's exclude_polygons. */
ring: LonLat[];
}
/** GET /v1/closures: every closure not over today, the same shape as an offline pack's closures block. */
export interface ClosuresResponse {
method: string;
source: string;
/** Today when it was fetched, YYYY-MM-DD Ulaanbaatar. */
as_of: string;
current: number;
/** sha256 of the sorted ids joined by commas; null for none. An offline pack's closures_sha256. */
ids_sha256: string | null;
buffer_m: number;
rule: string;
active_when: string;
/** Per route request, all rings together. */
valhalla_limits: {
perimeter_m: number;
vertices: number;
};
items: ClosureItem[];
}
/** A closure's place: a Point on the stretch (not on a junction), or a LineString of 2 or more points, at most 3000 m. */
export type ClosureGeometry = {
type: 'Point';
coordinates: LonLat;
} | {
type: 'LineString';
coordinates: LonLat[];
};
/** POST /v1/closures. The sender is the key's organization. */
export interface ClosureReport {
geometry: ClosureGeometry;
kind: ClosureKind;
/** First closed day, YYYY-MM-DD Ulaanbaatar. Default today. */
from?: string;
/** Last closed day. Absent or null: until lifted. */
to?: string | null;
/** Your words for your own records, at most 200 characters; never served to others. No names, no phone numbers. */
note?: string;
}
/** One of your organization's closure reports, as stored (POST /v1/closures, GET /v1/closures/{id}, lift). */
export interface Closure {
type: 'Feature';
/** cl-000001. Never reused. */
id: string;
geometry: ClosureGeometry;
properties: {
kind: ClosureKind;
from: string;
to: string | null;
/** Open again from this day, YYYY-MM-DD; set once. null: not lifted. */
lifted: string | null;
/** As sent; '' for none. */
note: string;
confidence: number;
/** UTC ISO 8601. */
reported_at: string;
};
}
/** POST /v1/telemetry/search. */
export interface SearchTelemetryReceipt {
stored: number;
/** Text that was only a phone or an e-mail address, or a pick not in the index. */
dropped: number;
}
/** POST /v1/traces. */
export interface TraceReceipt {
/** Keep it to fetch or delete the trip. null: nothing was stored (every point was inside a restricted site). */
id: string | null;
points: number;
/** Points left out: inside a restricted site. */
restricted: number;
/** Your organization sent this trip before; id is the first upload. */
duplicate: boolean;
/** Raw points are erased after this day, YYYY-MM-DD. */
purge_after: string | null;
}
/** GET /v1/traces/{id}: your trace as stored, GeoJSON. geometry is null once deleted or past purge_after. */
export interface Trace {
type: 'Feature';
id: string;
geometry: {
type: 'LineString';
coordinates: [number, number, number][];
} | null;
properties: {
received_day: string;
purge_after: string;
points: number;
restricted: number;
deleted_at: string | null;
deleted_reason: 'partner' | 'retention' | null;
};
}
/** DELETE /v1/traces/{id}. deleted is true also when the points were gone before. */
export interface TraceDeleted {
id: string;
deleted: boolean;
}
/** west, south, east, north in degrees. */
export type BBox = [number, number, number, number];
/** One 500 m cell of the fixed grid in a month. id is "x/y", the same cell every month. */
export interface TraceCell {
type: 'Feature';
id: string;
geometry: {
type: 'Polygon';
coordinates: [number, number][][];
};
/** Distinct trips through the cell that month. */
properties: {
trips: number;
};
}
/** GET /v1/traces/cells: all partners together, only cells that meet `rule`. No cell means too few, not zero. */
export interface TraceCells {
type: 'FeatureCollection';
/** YYYY-MM, Ulaanbaatar. */
month: string;
rule: Record<string, unknown>;
features: TraceCell[];
}
/** One 500 m cell of the trip cells grid with all partners' moving speed that month. id is "x/y", as TraceCell. */
export interface TraceSpeed {
type: 'Feature';
id: string;
geometry: {
type: 'Polygon';
coordinates: [number, number][][];
};
/** Counted meters over counted seconds of all partners' trips in the cell, to 0.1. No trip count. */
properties: {
km_h: number;
};
}
/** GET /v1/traces/speeds: all partners together, only cells that meet `rule`. No cell means too few trips, not slow. */
export interface TraceSpeeds {
type: 'FeatureCollection';
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** The group rule each cell passed, and how a segment and km_h are measured, keys as the API writes them. */
rule: Record<string, unknown>;
/** Cells north to south, then west to east. */
features: TraceSpeed[];
}
/** A road's line: dock's current road with that OSM id, a LineString, or a MultiLineString when it is in pieces. */
export type TraceRoadGeometry = {
type: 'LineString';
coordinates: LonLat[];
} | {
type: 'MultiLineString';
coordinates: LonLat[][];
};
/** One OSM way with all partners' moving speed that month. id is the OSM way id as text: it can pass 2^53 one day. */
export interface TraceRoad {
type: 'Feature';
id: string;
geometry: TraceRoadGeometry;
properties: {
/** Counted meters over counted seconds of all partners' trips matched to the way, to 0.1. No trip count. */
km_h: number;
/** The road's names and OSM highway class, null when unknown. */
name_mn: string | null;
name_en: string | null;
highway: string | null;
};
}
/**
* Hour band of GET /v1/traces/roads (API 0.49.0), by the Ulaanbaatar time of a trip's first stored point: am_peak Monday
* to Friday 07:00 to 09:59, pm_peak Monday to Friday 17:00 to 19:59, off_peak every other hour, Saturday and Sunday whole.
*/
export type RoadBand = 'am_peak' | 'pm_peak' | 'off_peak';
/** GET /v1/traces/roads, what traceRoads() returns. */
export interface TraceRoads {
type: 'FeatureCollection';
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** The group rule each road passed, and how a trip is matched and km_h measured, keys as the API writes them. */
rule: Record<string, unknown>;
/** Roads by OSM way id, smallest first. */
features: TraceRoad[];
}
/** POST /v1/traffic/probes, what stopProbes() returns (API 0.82.0). */
export interface ProbeReceipt {
/** Points in the batch. */
points: number;
/** Points left out: inside a restricted site. */
restricted: number;
/** Road and 15-minute window sums taken. */
roads: number;
/** Road and window sums left out: their window had closed, 5 minutes after it ended. */
late: number;
}
/** A road on GET /v1/traffic. id is the OSM way id as text. */
export interface TrafficRoad {
type: 'Feature';
id: string;
geometry: TraceRoadGeometry;
properties: {
/** Moving meters over moving seconds of the vehicles on the way in the window, to 0.1. */
km_h: number;
/** Vehicles counted, 5 or more. null when the answer is typical. */
vehicles: number | null;
/** The road's names and OSM highway class, null when unknown. */
name_mn: string | null;
name_en: string | null;
highway: string | null;
};
}
/** A road on GET /v1/traffic/typical. id is the OSM way id as text. */
export interface TypicalTrafficRoad {
type: 'Feature';
id: string;
geometry: TraceRoadGeometry;
properties: {
/** The usual moving speed on the way in this hour band, to 0.1. */
km_h: number;
/** The road's names and OSM highway class, null when unknown. */
name_mn: string | null;
name_en: string | null;
highway: string | null;
};
}
/** GET /v1/traffic/typical, what trafficTypical() returns (API 0.96.0): the usual speeds of this hour band, never live. */
export interface TypicalTraffic {
type: 'FeatureCollection';
kind: 'typical';
/** The label to show ("ердийн"), the hour band of this moment (Ulaanbaatar clock), and the closed month the speeds come from; month is null when no road has a speed. */
typical: {
label: string;
band: string;
hours: string;
month: string | null;
};
/** The rule each road passed, keys as the API writes them. */
rule: Record<string, unknown>;
/** Where the speeds come from: partner traces, all partners together. */
source: string;
/** The last day of the month the speeds come from; null when no road has a speed. */
source_date: string | null;
/** Roads by OSM way id. Empty is valid: no traces, no speed. */
features: TypicalTrafficRoad[];
}
/** GET /v1/traffic, what traffic() returns (API 0.82.0): the last 15-minute window that has ended. */
export interface Traffic {
type: 'FeatureCollection';
/** ISO times, UTC, on the quarter hour. null when the answer is typical. */
window: {
from: string;
to: string;
} | null;
/** The rule each road passed, keys as the API writes them. */
rule: Record<string, unknown>;
/** Roads by OSM way id, smallest first. */
features: TrafficRoad[];
/** live: fleet probes of the last ended window. typical: no live road passed the rule, the speeds are the usual ones for this hour band. */
kind: 'live' | 'typical';
/** null when live. When typical: the label to show ("ердийн"), the hour band of this moment and the closed month the speeds come from. */
typical: {
label: string;
band: string;
hours: string;
month: string;
} | null;
/** Where the speeds come from: partner fleet traces. */
source: string;
/** live: the date the window ended. typical: the last day of the month the speeds come from. */
source_date: string;
}
/** An organizer's event, what createEvent() and event() return (API 0.83.0). */
export interface DockEvent {
id: string;
name: string;
/** [west, south, east, north]: points outside are not stored. */
bbox: BBox;
/** ISO times, UTC. */
starts_at: string;
ends_at: string;
/** When every point is erased: 15 minutes after ends_at. */
closes_at: string;
/** true once the points are erased; the heat cells stay. */
closed: boolean;
}
/** What createEvent() takes. */
export interface EventInput {
/** The event's name, 1 to 120 characters. Never a person's. */
name: string;
/** Inside Mongolia, sides at most 1 degree. */
bbox: BBox;
/** ISO time with a zone, or a Date. */
startsAt: string | Date;
/** After startsAt and after now, at most 14 days after startsAt. */
endsAt: string | Date;
}
/** POST /v1/events/{id}/positions, what stopEventPositions() returns. */
export interface EventPositionsReceipt {
/** Points in the batch. */
points: number;
/** Points stored until the event closes. */
kept: number;
/** Points left out: outside the event's area or time. */
outside: number;
/** Points left out: inside a restricted site. */
restricted: number;
}
/** A live participant's last point, to 4 decimals. Nothing about the participant. */
export interface EventLivePoint {
type: 'Feature';
geometry: {
type: 'Point';
coordinates: LonLat;
};
properties: Record<string, never>;
}
/** GET /v1/events/{id}/live, what eventLive() returns. */
export interface EventLive {
type: 'FeatureCollection';
/** The answer's time; points are from the 2 minutes before it. */
at: string;
/** Participants shown; 0 when fewer than 5 are live, then features is empty. */
participants: number;
/** The rule the answer follows, keys as the API writes them. */
rule: Record<string, unknown>;
/** Sorted by place. */
features: EventLivePoint[];
}
/** A heat cell: a 150-unit EPSG:3857 square, about 100 m at Ulaanbaatar. */
export interface EventHeatCell {
type: 'Feature';
geometry: {
type: 'Polygon';
coordinates: LonLat[][];
};
/** Distinct participants with a point in the cell, 5 or more. */
properties: {
participants: number;
};
}
/** GET /v1/events/{id}/heat, what eventHeat() returns. */
export interface EventHeat {
type: 'FeatureCollection';
/** The 15-minute window; null for the whole event. */
window: {
from: string;
to: string;
} | null;
/** true once the event is closed: the cells are final. */
closed: boolean;
/** The rule each cell passed, keys as the API writes them. */
rule: Record<string, unknown>;
/** North to south, west to east. */
features: EventHeatCell[];
}
/** GET /v1/traces/cells/own: your organization's trips only, every cell with one of them. */
export interface OwnTraceCells extends TraceCells {
/** false while trips received now still count; true from 14 days after the month ends. */
closed: boolean;
}
/** Trips per whole arrival minute. */
export interface ArrivalMinute {
/** Whole minutes; 60 is 60 or more. */
minutes: number;
trips: number;
}
/** A 500 m cell of the trip cells grid where your trips ended. id is "x/y", the same cell every month. */
export interface ArrivalCell {
type: 'Feature';
id: string;
geometry: {
type: 'Polygon';
coordinates: [number, number][][];
};
properties: {
/** Your trips that ended in the cell that month. */
trips: number;
/** Of them, arrivals of 5 minutes or more. */
slow: number;
/** Median arrival minutes in the cell, nearest rank. */
median: number;
};
}
/** GET /v1/traces/arrivals/own: how long your trips spend in the last 200 m. Your organization's trips only. */
export interface OwnArrivals {
type: 'FeatureCollection';
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** false while trips received now still count; true from 14 days after the month ends. */
closed: boolean;
/** How arrival minutes are measured, keys as the API writes them (radius_m, max_minutes, slow_minutes, ...). */
rule: Record<string, unknown>;
/** Your trips with an arrival that month, every cell. */
trips: number;
/** Of them, arrivals of 5 minutes or more. */
slow: number;
/** Nearest rank. null with no trips. */
median: number | null;
p75: number | null;
p90: number | null;
/** Trips per arrival minute, fewest minutes first; minutes with none left out. */
histogram: ArrivalMinute[];
/** Cells your trips ended in; features lists up to 100 of them. */
cells: number;
/** By slow trips, then trips, most first. */
features: ArrivalCell[];
}
/** Trips that started in one Ulaanbaatar weekday and hour: yours in OwnHours, all partners' in AllHours. */
export interface HourSlot {
/** ISO weekday in Ulaanbaatar, 1 Monday to 7 Sunday. */
weekday: number;
/** Hour in Ulaanbaatar, 0 to 23. */
hour: number;
trips: number;
}
/** GET /v1/traces/hours/own: when your trips start, by weekday and hour. Your organization's trips only. */
export interface OwnHours {
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** false while trips received now still count; true from 14 days after the month ends. */
closed: boolean;
/** How the weekday and hour are read, keys as the API writes them. */
rule: Record<string, unknown>;
/** Your trips that month, each once, at the weekday and hour of its first stored point. */
trips: number;
/** 7 counts, Monday first. */
by_weekday: number[];
/** 24 counts, hour 0 first. */
by_hour: number[];
/** Weekday and hour with trips, weekday then hour; slots with none left out. */
slots: HourSlot[];
}
/** GET /v1/traces/hours: when trips start, by weekday and hour, all partners together. A published month only. */
export interface AllHours {
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** The group rule each slot passed, and how the weekday and hour are read, keys as the API writes them. */
rule: Record<string, unknown>;
/** Weekday and hour that passed the rule, weekday then hour. A slot left out had too few trips, not zero. No total. */
slots: HourSlot[];
}
/** Trips that ended in one khoroo in one Ulaanbaatar hour, all partners together. */
export interface KhorooHour {
/** Hour in Ulaanbaatar of the trip end, 0 to 23. */
hour: number;
trips: number;
}
/** A current khoroo polygon with the hours of arrival that passed the rule. id is the polygon's, never an address. */
export interface TraceKhoroo {
type: 'Feature';
id: string;
geometry: {
type: 'MultiPolygon';
coordinates: [number, number][][][];
};
properties: {
district_mn: string;
district_en: string | null;
/** Khoroo number in its district. */
khoroo: number;
/** Hours that passed the rule, from 0. An hour left out had too few trips, not zero. No khoroo total. */
hours: KhorooHour[];
};
}
/** GET /v1/traces/khoroos: where and when trips end, per khoroo and hour, all partners together. A published month only. */
export interface TraceKhoroos {
type: 'FeatureCollection';
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** The group rule each khoroo and hour passed, and how they are read, keys as the API writes them. */
rule: Record<string, unknown>;
/** Khoroos with at least one hour shown, by district then number. */
features: TraceKhoroo[];
}
/** A current khoroo polygon with your trips that ended in it. id is the polygon's, never an address. */
export interface OwnKhoroo {
type: 'Feature';
id: string;
geometry: {
type: 'MultiPolygon';
coordinates: [number, number][][][];
};
properties: {
district_mn: string;
district_en: string | null;
/** Khoroo number in its district. */
khoroo: number;
/** Your trips that ended in this khoroo that month. */
trips: number;
/** Your trips per Ulaanbaatar hour of arrival, from 0. Hours with none left out. */
hours: KhorooHour[];
};
}
/** GET /v1/traces/khoroos/own: where and when your trips end, per khoroo and hour. Your organization only, no group rule. */
export interface OwnKhoroos {
type: 'FeatureCollection';
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** false while trips received now still count; true from 14 days after the month ends. */
closed: boolean;
/** How the khoroo and hour are read, keys as the API writes them. */
rule: Record<string, unknown>;
/** Your trips that month. */
trips: number;
/** Your trips that month ending in no current khoroo polygon. trips is no_khoroo plus every khoroo's trips. */
no_khoroo: number;
/** Khoroos with at least one of your trips, by district then number. */
features: OwnKhoroo[];
}
/** The building type at a trip end. unknown: a building with no type yet; none: no building within 30 m. */
export type BuildingKind = 'apartment' | 'ger_house' | 'commercial' | 'public' | 'industrial' | 'garage' | 'other' | 'unknown' | 'none';
/** Your trips that month ending at one kind of building. */
export interface OwnKind {
kind: BuildingKind;
trips: number;
}
/** GET /v1/traces/kinds/own: what kind of building your trips end at, per month. Your organization only, no group rule. */
export interface OwnKinds {
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** false while trips received now still count; true from 14 days after the month ends. */
closed: boolean;
/** How the building and its kind are read, keys as the API writes them. */
rule: Record<string, unknown>;
/** Your trips that month, the sum of every kind's trips. */
trips: number;
/** Kinds with at least one of your trips, most trips first, then by name. */
kinds: OwnKind[];
}
/** Ids of what a door stands in. Ids only, never shown to people as an address. null when not known. */
export interface Parents {
/** The door of a business. null for a door. */
door: string | null;
building: string | null;
/** Khashaa (plot). */
plot: string | null;
area: string | null;
street: string | null;
/** null when no khoroo polygon covers the door. Never guessed. */
khoroo: string | null;
district: string | null;
}
/** GET /v1/address/{id}: one place by the id geocode, reverse or changes gave. */
export interface Address extends Place {
/** A landmark, business or street: the district its point stands in, in Mongolian. null when no polygon covers it, and for any other kind. */
district_mn?: string | null;
/** A building: whether its footprint is backed by a second source. null for other kinds and for a building not yet checked. */
footprint_check?: FootprintCheck | null;
/** A building: what it is (apartment, ger_house, commercial, public, industrial, garage, other). null when it has no type yet. Absent for other kinds. */
building_type?: string | null;
/** A building: true when its whole name is only a kind word. null when not checked. Absent for other kinds. */
name_is_kind_word?: boolean | null;
/** A landmark or building: one openly licensed photo (CC BY, CC BY-SA, CC0 or public domain) within 30 m. Show credit and license beside it. null when there is none. */
photo?: {
url: string;
credit: string;
license: string;
source: string;
} | null;
/** Set only when photo is null: where to point the map from above (zoom 19 for a building, door or business, 17 otherwise). null when there is a photo or the site is restricted. */
map_from_above?: {
lon: number;
lat: number;
zoom: number;
} | null;
/** Doors and businesses; a khoroo carries its district only; null for every other kind. */
parents: Parents | null;
/** A bus stop: the city bus lines that call at it ("Ч:41"), as nearby() gives them. null for every other place. */
lines: string[] | null;
/** A business or landmark: its map category, as nearby() gives it. null for any other kind. */
category: string | null;
/** A company from a building's directory (API 0.90.0): its floor from the legal address in the state register. null when not said, and for every other place. */
floor?: number | null;
/** A company from a building's directory (API 0.90.0): its flat or office number (N тоот). null when not said. */
flat?: string | null;
/** A district, khoroo or area (next API patch): its boundary as a GeoJSON Polygon or MultiPolygon, about 5 m simplified. null for every other kind and where no polygon is held. */
outline?: {
type: 'Polygon' | 'MultiPolygon';
coordinates: unknown;
} | null;
/** A district, khoroo or area: current buildings and doors inside its boundary (0 is a real zero), with source and as_of. null for every other kind and where no polygon is held: unknown. */
counts?: {
buildings?: number;
doors?: number;
source?: string;
as_of?: string;
[key: string]: unknown;
} | null;
}
/**
* One entrance of a building, in entrances(). With a door: its code and the door's official fields. Without one:
* code null and the building's official form with this entrance's number.
*/
export interface BuildingEntrance {
official_address: OfficialAddress | null;
/** The level: matched бүртгэлтэй, partial хэсэгчилсэн, none албан бус. */
official_status: OfficialStatus;
/** true unless official_status is matched: show "unofficial". */
unofficial: boolean;
official_source: string | null;
official_date: string | null;
/** The date of this entrance's current version, YYYY-MM-DD. null: unknown. */
source_date: string | null;
/** The door code. null: no door minted for this entrance yet. */
code: string | null;
/** The entrance id. Never show it to people as an address. */
id: string;
kind: Entrance['kind'];
/** Entrance or gate number as people say it. null: not known. */
letter: string | null;
floors_served: number[] | null;
point: LonLat;
/** PLAN 5 level of the point. 0 with source generated: placed by rule from the footprint, show it as таамаг. */
confidence: number | null;
source: string;
}
/** GET /v1/address/{id}/entrances: a building's official form and its entrances. */
export interface BuildingEntrances {
/** The building id, as sent. */
building: string;
official_address: OfficialAddress | null;
official_status: OfficialStatus;
unofficial: boolean;
official_source: string | null;
official_date: string | null;
/** Numbered entrances first, in number order, then the rest. Empty when none is mapped or generated. */
entrances: BuildingEntrance[];
/** false until the building's entrance method is approved; entrances is then empty. */
entrances_approved: boolean;
/** Present when entrances_approved is false. */
note?: string;
}
/** One organization in a building's directory. Only organizations matched to an official registration are listed. */
export interface DirectoryEntry {
name: string;
/** Floor (давхар). null when the register does not say. */
floor: number | null;
/** Unit (тоот). null when unknown. */
unit: string | null;
/** The flat or office number, the same value as `unit`, named as geocode and nearby name it. null when unknown. Added in API 0.96.5. */
flat: string | null;
category: string | null;
source: string;
confidence: number | null;
/** Date this version of the entry was recorded. */
source_date: string;
}
/** GET /v1/address/{id}/directory: "Энд байгаа байгууллагууд" for an office building or mall card. */
export interface BuildingDirectory {
/** The building id, as sent or the one the code names. null for a хашаа code. */
building: string | null;
/** The building's code. null until one is minted. */
code: string | null;
/** By floor (unknown floor last), then unit, then name. Empty when none is known. */
entries: DirectoryEntry[];
}
/** One entrance in entrancesNear(): as a building's list shows it, plus its building and distance. */
export interface NearEntrance extends BuildingEntrance {
/** The building id: entrances(building) lists all its entrances. */
building: string;
/** Metres from the query point. */
distance_m: number;
}
/** GET /v1/entrances/near. */
export interface NearEntrances {
/** The query point, as sent. */
point: LonLat;
/** Nearest first, within radius. Empty when no entrance is that close. */
results: NearEntrance[];
}
/** A place in changes(): current, retired (gone, id never reused) or hidden (gone, may come back). */
export type ChangeState = 'current' | 'retired' | 'hidden';
/** One place changed, in changes(). */
export interface Change {
/** The id place(id) takes. */
id: string;
kind: PlaceKind;
/** current: fetch it again with place(id). retired: drop it, the id is never reused. hidden: drop it, it may come back. */
state: ChangeState;
/** Last change, UTC, microseconds. */
changed_at: string;
}
/** GET /v1/changes. Window [since, until). */
export interface ChangesResponse {
since: string;
/** The last index refresh. When next is null, poll again with since = until. */
until: string;
/** Oldest first. One row per place, its last change. */
changes: Change[];
/** More in this window: pass as cursor. null: window done. */
next: string | null;
}
/** What the driver found: the door is right, elsewhere, or gone. */
export type ConfirmResult = 'confirmed' | 'wrong_location' | 'missing';
/**
* POST /v1/confirm body. confirmed and wrong_location need `tap`, the point the driver tapped on the map: the
* placed point. missing has no tap.
*/
export type Confirmation = ConfirmationBase & ConfirmTarget & ({
result: 'confirmed' | 'wrong_location';
tap: LonLat;
} | {
result: 'missing';
tap?: never;
});
/** POST /v1/confirm. Map data does not change at once. */
export interface ConfirmReceipt {
/** Same id for a repeat. */
id: string;
/** already_recorded: the same driver (or your org, with no driver) said the same in the last 24 h. */
status: 'recorded' | 'already_recorded';
result: ConfirmResult;
/** unit: the code named a building or khashaa (an arrival). */
target: 'door' | 'entrance' | 'unit';
code: string | null;
/** The entrance confirmed, also for a door code. null for a unit code. */
entrance: string | null;
/** Metres from the tap to the entrance on the map. null for missing. */
moved_m: number | null;
/** Metres from the tap (missing: the entrance) to the phone GPS. */
gps_m: number;
observed_at: string | null;
received_at: string;
/** confidence: counts toward the entrance confidence (PLAN.md 5). review: a person checks it first. arrival: recorded as an arrival at a building or khashaa, no entrance raised. */
next: 'confidence' | 'review' | 'arrival';
}
/** The answer to sendConfirmPhoto (API 0.71.0). */
export interface ConfirmPhotoReceipt {
/** The confirmation the photo belongs to. */
id: string;
/** queued: stored with the confirmation. unchanged: this photo already was. */
status: 'queued' | 'unchanged';
/** SHA-256 of the photo as stored, after its metadata was removed: not the hash of the file you sent. */
sha256: string;
received_at: string;
/** A person reviews it once the entrance has a door. It is never shown on a public page. */
next: 'review';
}
/** Confirmations and door reports by result. duplicate and unmapped come from door reports. */
export interface ConfirmCounts {
confirmed: number;
wrong_location: number;
missing: number;
duplicate: number;
/** A real door with no code yet. */
unmapped: number;
}
/** One Ulaanbaatar day the drivers were on site (observed_at, else received). */
export interface ConfirmDay extends ConfirmCounts {
/** YYYY-MM-DD. */
day: string;
/** Different drivers that day, by your driver ids. Rows with no driver id are not counted here. */
drivers: number;
}
/** A failure point: a door or entrance your drivers said is wrong, missing or a duplicate. */
export interface ConfirmFlagged {
/** null when your drivers named the entrance, not a code. */
code: string | null;
/** The entrance id, as in the entrance tile layer. */
entrance: string;
/** true when the door or entrance has since been retired. */
retired: boolean;
wrong_location: number;
missing: number;
duplicate: number;
/** The newest of them, on site. */
last_at: string;
}
/** GET /v1/confirm/own: your organization's rows only. */
export interface OwnConfirmations {
/** YYYY-MM, Ulaanbaatar. */
month: string;
/** false while confirmations queued offline can still join; true from 8 days after the month ends. */
closed: boolean;
totals: ConfirmCounts;
/** Different drivers in the month, by your driver ids. Never the ids. */
drivers: number;
/** Days with at least one row, oldest first. */
days: ConfirmDay[];
/** Most first, at most 100. None inside a restricted site. */
flagged: ConfirmFlagged[];
}
/** A door name your organization holds (API 0.52.0, feature door_names). Never a door code, never a phone. */
export interface DoorName {
/** As registered, spaces collapsed. */
name: string;
/** The door it names. */
code: string;
registered_at: string;
/** After this the name finds nothing and any organization may take it. */
expires_at: string;
}
/** POST /v1/door-names. registered: yours from now. moved: your name now names this door. unchanged: it already did. */
export interface DoorNameReceipt extends DoorName {
status: 'registered' | 'moved' | 'unchanged';
}
/** One of your door names, in doorNames(). */
export interface OwnDoorName extends DoorName {
/** true after expires_at: it finds nothing, and stays listed until another organization takes it. */
expired: boolean;
/** When the transfer code you made stops working (API 0.53.0); null when none is waiting. */
transfer_until: string | null;
}
/** GET /v1/door-names: your organization's names, A to Z. */
export interface OwnDoorNames {
/** Unexpired names one organization may hold at once. */
limit: number;
names: OwnDoorName[];
}
/** GET /v1/door-names/{name}: the door as door() answers it, official address first. The holder is never shown. */
export interface NamedDoor {
name: string;
expires_at: string;
door: Door;
}
/** POST /v1/door-names/{name}/transfer-code (API 0.53.0). The code is shown once, never again. */
export interface DoorNameTransfer {
name: string;
/** dnt_ and 24 characters. Anyone holding it can claim the name: give it only to the organization taking it. */
transfer_code: string;
/** Refused after this: 7 days, or the name's expires_at if sooner. */
transfer_until: string;
}
/** DELETE /v1/door-names/{name}/transfer-code. cancelled: a waiting code was cleared; false: none was waiting. */
export interface DoorNameTransferCancel {
name: string;
cancelled: boolean;
}
/** POST /v1/door-names/{name}/claim: the name is yours from now; the door and expires_at are as they were. */
export interface DoorNameClaim extends DoorName {
status: 'transferred';
}
/**
* POST /v1/door-names/{name}/notes (API 0.58.0, feature hotel.notes): how guests find your entrance. 1 to 500
* characters each, no phone number, no e-mail. null or absent: none in that language; give at least one.
*/
export interface DoorNotesBody {
instructions_mn?: string | null;
instructions_en?: string | null;
}
/** POST /v1/door-names/{name}/notes. queued: a new note waits for review. updated: your waiting note has this text now. unchanged: it had. */
export interface DoorNoteReceipt {
/** The waiting note; writing again keeps it. */
id: string;
status: 'queued' | 'updated' | 'unchanged';
name: string;
/** The door the name points at, which the note is for. */
code: string;
instructions_mn: string | null;
instructions_en: string | null;
/** When the waiting text was last written. */
received_at: string;
/** A person reviews it. Until then door() serves the notes the map holds, not this one. */
next: 'review';
}
/** One of your notes in doorNotes(). */
export interface DoorNoteState {
id: string;
/** waiting: not reviewed, guests do not see it. accepted: door() serves the languages it gave. refused: not used. */
state: 'waiting' | 'accepted' | 'refused';
instructions_mn: string | null;
instructions_en: string | null;
/** When this text was last written. */
received_at: string;
/** When a person accepted or refused it; null while it waits. */
reviewed_at: string | null;
}
/** GET /v1/door-names/{name}/notes (API 0.59.0): your org's notes for the door, never another org's. */
export interface DoorNotes {
name: string;
/** The door the name points at now. */
code: string;
/** The waiting one first, then the newest review, at most 20. Empty: you wrote none. */
notes: DoorNoteState[];
}
/** POST /v1/door-names/{name}/photo (API 0.60.0). queued: a new photo waits for review. updated: your waiting photo is now this one. unchanged: it was. */
export interface DoorPhotoReceipt {
/** The waiting photo; sending again keeps it. */
id: string;
status: 'queued' | 'updated' | 'unchanged';
name: string;
/** The door the name points at, which the photo is for. */
code: string;
/** SHA-256 of the photo as stored, after its metadata was removed: not the hash of the file you sent. */
sha256: string;
/** Size as stored, in bytes. */
bytes: number;
/** Pixels as stored, before the EXIF orientation turns it. */
width: number;
height: number;
/** When the waiting photo was last sent. */
received_at: string;
/** A person reviews the photo. Once accepted, doorPhoto() serves it. */
next: 'review';
}
/** doorPhoto() (API 0.61.0): the accepted gate photo, for your app only. */
export interface DoorPhoto {
/** SHA-256 of the photo served, as sendDoorPhoto() answered it. Pass it back to skip the bytes when unchanged. */
sha256: string;
/** The JPEG, metadata removed, orientation kept. null: the photo with the sha256 you passed is still the one. */
jpeg: Uint8Array | null;
}