How MAP works
Six concepts cover ~95% of the platform. Skim or read in order.
API keys & auth
Every /v1/* request requires a key, sent as a Bearer token:
Authorization: Bearer map_live_...
For tile requests (where most map clients can't set custom headers), you can put the key in the query string instead:
https://api.map.bd/v1/tiles/bangladesh/11/1456/880?api_key=map_live_...
Keys come in two prefixes: map_live_ for production and test_ for development. Both authenticate the same way; the prefix is purely a hint to your CI to make sure you don't commit live keys.
Revoke a key from the dashboard. Revoked keys are rejected with a 401 within 30 seconds (the gateway refresh interval).
bdnlp normalisation
The gateway runs every geocoding query through bdnlp before it reaches Nominatim. This is what makes Banglish + Bangla + landmark queries work where they fail on plain Nominatim or Google.
The pipeline:
- Detect Bangla numerals and convert to ASCII (১২ → 12)
- Parse house / road / block / sector tokens (with both EN and BN regexes)
- Detect landmark prepositions ("near", "কাছে") and translate landmark types ("হাসপাতাল" → hospital)
- Match the locality token against 1,400+ curated BD place spellings
- Issue up to 3 query variants against Nominatim in parallel
- Merge, then re-rank by parsed-component overlap
The result includes a _map_parsed block so you can see what the pipeline detected. Use this in your UI to give users feedback:
{
"_map_parsed": {
"Original": "House 23, Road 11, Banani",
"House": "23", "Road": "11", "Locality": "Banani"
},
"_map_variants_tried": ["Banani, Bangladesh", "House 23, Road 11, Banani"]
}To skip bdnlp and proxy raw to Nominatim, pass ?raw=true. Useful for A/B benchmarks.
Routing costing modes
The routing endpoints (/v1/directions, /v1/distance-matrix, /v1/optimized-route, etc.) all accept a costing field. Defaults to auto (private car).
| Mode | Use for |
|---|---|
| auto | Private car, default ride-share use |
| taxi | Same as auto but prefers main roads |
| truck | Cargo / parcel — respects weight + height restrictions |
| motorcycle | Avoids no-bike roads |
| motor_scooter | CNG / autorickshaw, scooters |
| bicycle | Generic bicycle |
| pedestrian | Walking — uses footways, sidewalks |
| bus | Buses — respects bus lanes |
For rickshaws specifically, use the dedicated /v1/directions/rickshaw endpoint — it presets the bicycle costing with Dhaka-tuned overrides (slow cruise, no motorways, side-street preference).
Webhooks & signatures
Subscribe URLs at /dashboard/webhooks. Then POST driver positions to /v1/events/track; the gateway fires callbacks on transitions (entering/exiting a geofence, ETA crossing a threshold).
Each callback includes an HMAC-SHA256 signature so you can verify authenticity:
POST https://yourapp/webhooks/map
X-MAP-Signature: sha256=<hex of HMAC-SHA256(secret, raw_body)>
X-MAP-Event: geofence.enter
X-MAP-Delivery: <uuid>
X-MAP-Attempt: 1
{ "event": "geofence.enter", "webhook_id": 42, "driver_id": "d-123", ... }Verification in any language is the same shape:
// Node.js
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-map-signature']));Failed deliveries (5xx, 408, 429, or network errors) are retried with exponential backoff: 1s, 4s, 16s. After 3 retries the event is dropped — you should make your webhook endpoint idempotent.
Rate limits
Each key has a per-minute quota set at creation time (default 100/min, max 10,000/min). The token bucket allows short bursts up to 2× the rate. Every response sets:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 31 # seconds until full
When you exceed the rate, you get a 429 with a Retry-After header. The SDKs surface this as MapApiError.retry_after.
Vector tiles
Tiles come from a Bangladesh-only PMTiles bundle served by Martin. The schema is the OpenMapTiles spec, so any compatible style (Positron, Dark Matter, OSM Bright) works.
Three things to know:
- Bangla labels by default. Layers with a
name_bnattribute render Bangla in our packaged style. - Self-hosted style. Serve
/style/positron.jsonfrom the same origin as your app to avoid cross-port CORS issues. - Tile range: zoom 0 – 14. Past 14, we serve the parent tile and let the client over-zoom.