# Migrating from NextBillion.ai

A port from NextBillion is a rewrite of the call layer, not a URL swap. Their
Directions, Distance Matrix, Navigation and Isochrone APIs are a proprietary
family with their own request and response shapes, so every endpoint below
needs client changes. What you gain in exchange is an ADR dangerous-goods
model, EV journey planning, a self-host licence with a published price, and an
agent surface you can call without a key.

**Be straight about the shape.** MapMap's routing is
[OSRM-compatible on the URL endpoint](/docs/api-reference#endpoints) and
Valhalla-shaped on `POST /route`. NextBillion's is neither. Their Fast and
Flexible variants share one envelope (`status`, `routes[]`, `legs[]`,
`steps[]`, with `latitude`/`longitude` objects and `distance`/`duration` as
`{value}` wrappers) and take named `origin`/`destination` query parameters.
A handful of their parameter names are borrowed from OSRM spelling
(`overview`, `approaches`, `bearings`, `alternatives`, `geometry=polyline6`),
but the envelope is not OSRM's and there is no `costing` or `costing_options`
anywhere in it. Anyone who tells you the Flexible API is Valhalla-shaped has
not read the specification. Checked against their own OpenAPI document on
**3 September 2026**.

## Key swap

NextBillion keys are issued after a business account is set up with them.
MapMap keys are issued by one unauthenticated call, card-free:

```sh
curl -fsS -X POST "https://api.mapmap.ai/v1/keys" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "accept_tos": true}'
```

Their key travels as a `key=` query parameter on every request. Ours goes in
`Authorization: Bearer snk_…` (preferred), or `?api_key=snk_…` for URL-only
contexts such as style and tile URLs. Verify the email and the free tier is
50,000 calls a month, commercial use allowed.

Note the host difference while you are porting: their documentation is on
`docs.nextbillion.ai` and their API is on `api.nextbillion.io`.

## Endpoint mapping

| NextBillion | MapMap | Notes |
| --- | --- | --- |
| `GET /directions/json` (Fast) | `GET /route/v1/{profile}/{coords}` or `POST /route` | Coordinates move from `origin=`/`destination=` query parameters to a path of `lon,lat;lon,lat` (OSRM endpoint) or a `locations[]` array of `{lat, lon}` (native). Watch the order: theirs is `lat,lng`. |
| `GET /directions/json?option=flexible` | `POST /route` | The native endpoint is where truck, ADR and route options live. See the parameter table below. |
| `GET /distancematrix/json` | `POST /matrix` | Their `origins`/`destinations` become `sources`/`targets` arrays of `{lat, lon}`. Response is `durations` (seconds) and `distances` (metres), row-major, with `null` for unreachable pairs. Up to 10,000 elements per request. |
| `POST /mdm/create` and `GET /mdm/status` (async matrix) | `POST /matrix`, or `POST /v1/jobs/matrix` + `GET /v1/jobs/{id}` | Two ways across. Up to 10,000 elements the matrix answers synchronously and the polling pair disappears; above that the [asynchronous lane](/docs/api-reference#asynchronous-jobs) is the same submit-and-poll shape you already have, at 40,000 elements, with an optional signed webhook so you can stop polling altogether. |
| `POST /optimization/v2` and `GET /optimization/v2/result` | `POST /optimise`, or `POST /v1/jobs/optimise` + `GET /v1/jobs/{id}` | Same choice. Up to 200 unique locations it is one synchronous call and the result-polling disappears; up to 2,000 the [asynchronous lane](/docs/api-reference#asynchronous-jobs) keeps the job-id shape and adds a signed webhook. Truck and ADR constraints shape the plan either way, polling costs no quota, and a failed job refunds what its submission was charged. Limits are published: 200 or 2,000 unique locations, 1,500 km matrix span. See [optimisation](/docs/optimisation). |
| `GET /snapToRoads/json` | `POST /trace_route`, `POST /trace_attributes` | Their `path` becomes `shape` (an array of `{lat, lon}`) or `encoded_polyline`. `timestamps` and `radiuses` have no direct equivalent; `shape_match` (`edge_walk`, `map_snap`, `walk_or_snap`) selects the snapping behaviour instead. |
| `POST /route-reconstruction`, `POST /postTripRoute` | `POST /route/report` | Distance and duration broken down by road class, admin area, toll, bridge, tunnel and surface, over an existing route or trace. |
| `GET /isochrone/json` | `POST /isochrone` | `contours_minutes` and `contours_meters` become one `contours` array whose entries carry a `time` in **minutes** or a `distance` in **kilometres**. `polygons`, `denoise` and `generalize` port by name. Ten contours per request against their four. |
| `GET /geocode` | `GET /geocode` | Their `q` becomes `query`, and `at` becomes `focus`. The response shape differs: ours returns candidates with `name`, `label`, `lat`/`lon`, `type` and address parts, not their HERE-style `items[]` with `position{lat,lng}`. This one always needs client changes. |
| `GET /revgeocode` | `GET /geocode/reverse` | Their `at=lat,lng` becomes `lat` and `lon` parameters. |
| `GET /navigation/json` | `GET /route/v1/{profile}/{coords}?steps=true` | Add `voice_instructions=true` and `banner_instructions=true` for Mapbox-shaped spoken prompts and banners, including lane diagrams where OSM carries `turn:lanes`. |
| `GET /incidents` | `POST /v1/incidents/along` | Incidents and closures along a route shape, with per-source coverage metadata. Coverage differs by deployment; `501` where none is configured. |
| `POST /hos/trip-plan` | `POST /optimise` | Driver hours are expressed as vehicle time windows, breaks and shift limits inside the optimisation problem rather than as a separate endpoint. See [driver hours](/docs/optimisation). |
| `/places/autosuggest`, `/places/autocomplete` | `GET /geocode/suggest`, `GET /geocode/retrieve` | Search-as-you-type: a compact suggestion list per keystroke, then the full feature for the row the user picked. |
| `/optimization/driver-assignment/v1`, `/clustering` | No direct equivalent | Model the assignment as a VRP over `POST /optimise`, or run your own clustering over `POST /matrix` output. |

## Parameter compatibility

Every mapped parameter below was checked as existing on **both** sides.
Anything that exists on only one side is in the section after it.

| Concept | NextBillion | MapMap | Watch out |
| --- | --- | --- | --- |
| Vehicle profile | `mode` = `car`, `truck`, `motorcycle`, `bike`, `walk` | `costing` = `auto`, `truck`, `motorcycle`, `bicycle`, `pedestrian` (plus `bus`, `motor_scooter`) | `bike` becomes `bicycle`, `walk` becomes `pedestrian`. Fast-variant callers only ever had `car` and `truck`. |
| Truck height, width, length | `truck_size` = `height,width,length` in **centimetres** | `height`, `width`, `length` in **metres** | Divide by 100. Theirs is one packed string, ours is three fields. |
| Truck weight | `truck_weight` in **kilograms** | `weight` in **tonnes** | Divide by 1,000. Their own docs are inconsistent here: weight is kg while `truck_axle_load` is tonnes. |
| Axle load | `truck_axle_load` in tonnes | `costing_options.truck.axle_load` in tonnes | Same unit, so this one ports directly. |
| Avoidances | `avoid` = `toll`, `ferry`, `highway`, … | `avoid_tolls`, `avoid_ferries`, `avoid_motorways` booleans | Their pipe-separated list becomes separate booleans. `avoid_tolls` and `avoid_motorways` are motorised profiles only. |
| Shortest versus fastest | `route_type` = `fastest`, `shortest` | `shortest` boolean | Default on both is fastest. |
| Alternatives | `alternatives`, `altcount` | `alternatives` = `true`/`false` or a number, max 3 | One field instead of two. |
| Geometry encoding | `geometry` = `polyline`, `polyline6`, `geojson` | `geometries` = `polyline`, `polyline6`, `geojson` on the OSRM endpoint | Same three values under a pluralised name. Their Flexible variant drops `geojson`, so a Flexible caller gets it back here. |
| Time-dependent routing | `departure_time`, a UNIX timestamp in seconds | `date_time`, an object of `{type, value}` such as `{"type": 0, "value": "2026-08-13T09:20"}` | Both cost against typical conditions at the given time, but the encoding differs: a local ISO-8601 string with a type discriminator, not an epoch. |
| Step-by-step legs | `steps` (Fast only) | `steps=true` | Their Flexible variant has no `steps` at all, so a Flexible caller gains this in the move. |

Two of their parameters have **no documented MapMap equivalent** and are not
in the table above because the table is a parity list: `approaches`
(`unrestricted` or `curb`) and `bearings`. Both are OSRM-named on their side,
but neither appears in the MapMap API reference, so treat curbside approach
and start-bearing constraints as capability you are giving up rather than
remapping.

### What ports with client changes

Everything. There is no drop-in path here, and it would be dishonest to
suggest one. Budget for:

- **Coordinate order and encoding.** Theirs is `lat,lng` in a query string;
  ours is `{lat, lon}` objects in JSON, or bare `lon,lat` pairs on the
  OSRM-compatible URL endpoint. That last one has caught everybody at least
  once.
- **Unit conversions.** Centimetres to metres, kilograms to tonnes, and their
  isochrone `contours_meters` to our `distance` in kilometres.
- **Response parsing.** Their `routes[].legs[].distance{value}` becomes a
  plain `distance_m`; their `status`/`msg` envelope becomes HTTP status plus
  a [problem+json](/docs/conventions) body.
- **Deciding what to do with the polling pairs.** Below the synchronous caps
  (10,000 matrix elements, 200 optimisation locations) both collapse to one
  call and the job-id plumbing comes out. Above them, keep it: `/v1/jobs/…`
  is the same submit-and-poll shape, and the polling half is quota-free.

## What MapMap adds

- **ADR dangerous goods, modelled properly.** Their `hazmat_type` is a flat
  nine-value filter (`explosives`, `gas`, `flammable_liquid`,
  `flammable_gas`, `organic`, `toxic`, `radioactive`, `corrosive`, `other`)
  that avoids unsuitable roads, and their specification contains no ADR
  tunnel category, no tunnel-restriction code and no placard concept. Tunnels
  are all or nothing through `avoid=tunnel`. MapMap takes a `tunnel_code` of
  B to E with `hazmat` on the truck profile and enforces
  [ADR 8.6.4](/docs/adr) in costing, returns `applied_adr` saying what was
  enforced, and answers the pure tunnel-entry question through
  `POST /adr/check` with no routing at all. Read the limits on that page
  honestly before you rely on it: it reflects what the map and the ADR matrix
  record, and it certifies nothing.
- **EV journey planning.** `POST /v1/ev/plan` works out the charge stops:
  physics consumption over the route's own legs, charge times integrated over
  the vehicle's charging curve rather than energy divided by peak power, and
  a reserve floor the state of charge never breaches. When no plan exists it
  says `feasible: false` with the cause and the furthest reachable point.
  `POST /v1/charging/along` ranks charge points along a route by power with
  their real engine-computed detour. NextBillion's published API carries no
  EV endpoint or parameter.
- **Self-hosting with a published price.** The whole stack, gateway, routing
  engine, geocoder and MCP server, from one Docker Compose file, with licence
  anchors on the [pricing page](/pricing) and in `/pricing.json`. Their
  on-premises deployment is offered "exclusively under our Enterprise pricing
  plan" and quoted on request.
- **Offline territories.** ed25519-signed [territory packages](/docs/territories)
  that verify with no network, do not expire, and update differentially. On-device
  routing, search and turn-by-turn against the local package.
- **An agent surface.** A hosted [MCP server](/docs/mcp) with 39 tools at
  `https://mcp.mapmap.ai/mcp`, connectable with no key at all under fair use,
  plus `/llms.txt`, `/llms-full.txt`, `/openapi.json`, `/pricing.json`,
  one-call keys and x402 machine payments.
- **Measured clearance.** [`POST /v1/clearance/along`](/docs/clearance) judges
  a vehicle against surveyed point-cloud geometry rather than map tags, where
  a survey exists.

## What has no replacement

Say this out loud before you plan the migration, because it is the part that
decides whether you can move at all.

- **Asset tracking, live tracking and geofencing.** NextBillion ships a
  tracking platform (their Skynet asset APIs, device battery and location
  telemetry, geofence entry and exit events). **MapMap has no equivalent and
  is not trying to build one.** If that platform is load-bearing for you,
  keep it or replace it with a telematics vendor: Samsara, Motive, Webfleet,
  Verizon Connect, Geotab or Teltonika-based integrators all sell this as
  their core product, and most take routing from an API like ours. MapMap
  routes and complies; it does not track your fleet.
- **Their per-customer map editing.** Private-road and custom-restriction
  overlays are not a hosted self-service product here today. Custom territory
  builds are a self-host and Sovereign tier conversation.
- **Static map images from the routing platform.** There is a
  [static map endpoint](/docs/api-reference#static-map-images) on the website,
  not on the gateway, and it is not a like-for-like replacement for their
  `static_map_image` tooling.
- **Hosted worldwide coverage on day one.** Hosted coverage is
  territory-based; check `GET /territories`. Self-host covers anywhere you
  build.
- **Hours-of-service as a standalone endpoint.** The capability exists inside
  `POST /optimise` as shifts, breaks and time windows, but a client calling
  `POST /hos/trip-plan` directly is rewriting, not remapping.

## Billing differences to encode

Their pricing is quoted per order, per asset or per API call under an annual
commitment. Ours is published and prepaid: two price classes detected per
request, Standard (car, bicycle and pedestrian routing, matrix, isochrone,
matching, geocoding) and Premium (anything truck or ADR, drawing 20 included
calls from the free tier). No credit means a `402` or `429`, never surprise
billing, and a request answered `4xx` is never charged. Machine-readable at
`https://mapmap.ai/pricing.json`.

## Attribution

MapMap requires "© OpenStreetMap contributors"; compiled styles carry it
structurally. See [licensing](/docs/licensing).

Full API reference: [/docs/api-reference](/docs/api-reference) (append `.md`
for raw markdown). Units, errors and quotas:
[conventions](/docs/conventions). A side-by-side on commercial terms:
[NextBillion.ai alternative](/compare/nextbillion-alternative).
