Routing

Map matching

Snaps an ordered GPS trace to the most plausible route on the road network.

Signed in? Open this endpoint in the dashboard playground, which uses your organization automatically. Signed out, use Try in Playground below with your API key.

POST
/mapmatching

Authorization

queryapiKey<token>

Your API key from the dashboard, sent as ?apiKey=<key>.

Request Body

GPS trace and matching options. Must be a JSON object.

application/json
  1. body
mode*string

Travel profile used to decide which roads can plausibly connect the GPS measurements. drive represents a passenger car.

Value in"walk""hike""scooter""motorcycle""drive""light_truck""bicycle""mountain_bike""road_bike""bus""medium_truck""truck""truck_dangerous_goods""heavy_truck""long_truck"
waypoints*array<>

Ordered GPS measurements to match. Input order is preserved; the API does not optimize or reorder these points.

Deviation (unsupported): at least 2 waypoints are required (Geoapify's schema allows 1; a single point cannot be matched and returns 400).

Items2 <= items <= 1000
geocode_step_address?boolean

When true, adds an address object to each matched step.

Deviation (approximated): the address is derived without a geocoder: street (the way name), state, country and country_code (lower case, from the routing tiles' admin data). suburb, city, county and postcode are never returned.

Defaultfalse
lookup_step_osmtags?boolean

When true, adds osmtags to each matched step.

Deviation (empty): osmtags is always an empty object {}; there is no OSM tag store behind the engines.

Defaultfalse
lookup_osm_overlap?boolean

When true, adds overlaps to each matched step.

Deviation (empty): overlaps is always an empty array [].

Defaultfalse
lang?string

Language code for the step instruction text.

Default"en"
Value in"en""de""es""fr""hi""it""pt""ru""sl""sv""ca""cs"

Response Body

Matched route as a GeoJSON FeatureCollection.

application/json
  1. response
type*unknown
features*array<>

Exactly one feature with the matched route.

bbox?array<number>

[minLon, minLat, maxLon, maxLat] of the matched route.

Items4 <= items <= 4
Example Request
curl -X POST 'https://api.data.graphitelab.ai/v1/mapmatching' \  -H 'Content-Type: application/json' \  -d '{  "mode": "drive",  "waypoints": [    {      "location": [        -86.15813,        39.769      ]    },    {      "location": [        -86.15813,        39.7705      ]    },    {      "location": [        -86.15813,        39.772      ]    },    {      "location": [        -86.15813,        39.7735      ]    },    {      "location": [        -86.15813,        39.775      ]    },    {      "location": [        -86.15813,        39.7765      ]    }  ]}'
Example Response
{  "type": "FeatureCollection",  "features": [    {      "type": "Feature",      "properties": {        "mode": "drive",        "distance": 827,        "time": 74.43,        "legs": [          {            "time": 74.43,            "distance": 827,            "steps": [              {                "from_index": 0,                "to_index": 1,                "time": 1.17,                "distance": 13,                "osm_way_id": 113084204,                "instruction": {                  "text": "Drive north on North Meridian Street."                },                "toll": false,                "ferry": false,                "bridge": false,                "tunnel": false,                "roundabout": false,                "speed": 40,                "speed_limit": 40,                "surface": "paved",                "lane_count": 2,                "road_class": "tertiary",                "name": "North Meridian Street",                "begin_bearing": 2,                "end_bearing": 2              },              {                "from_index": 1,                "to_index": 2,                "time": 2.79,                "distance": 31,                "osm_way_id": 113084204,                "toll": false,                "ferry": false,                "bridge": false,                "tunnel": false,                "roundabout": false,                "speed": 40,                "speed_limit": 40,                "surface": "paved",                "lane_count": 2,                "road_class": "tertiary",                "name": "North Meridian Street",                "begin_bearing": 2,                "end_bearing": 2              },              {                "from_index": 2,                "to_index": 3,                "time": 3.78,                "distance": 42,                "osm_way_id": 113084204,                "toll": false,                "ferry": false,                "bridge": false,                "tunnel": false,                "roundabout": false,                "speed": 40,                "speed_limit": 40,                "surface": "paved",                "lane_count": 2,                "road_class": "tertiary",                "name": "North Meridian Street",                "begin_bearing": 1,                "end_bearing": 1              }            ]          }        ],        "waypoints": [          {            "original_index": 0,            "location": [              -86.15802,              39.769051            ],            "original_location": [              -86.15813,              39.769            ],            "match_type": "matched",            "match_distance": 10.917381,            "leg_index": 0,            "step_index": 0          },          {            "original_index": 1,            "location": [              -86.157968,              39.770495            ],            "original_location": [              -86.15813,              39.7705            ],            "match_type": "matched",            "match_distance": 13.773137,            "leg_index": 0,            "step_index": 7          }        ]      },      "geometry": {        "type": "MultiLineString",        "coordinates": [          [            [              -86.15802,              39.769051            ],            [              -86.158016,              39.769167            ],            [              -86.158003,              39.769441            ],            [              -86.157723,              39.776491            ]          ]        ]      }    }  ],  "bbox": [    -86.15802,    39.769051,    -86.157723,    39.776491  ]}

POST /v1/mapmatching takes a noisy GPS trace (2 to 1,000 points) and returns the road-network route the vehicle most likely followed, with per-point match results and road-level steps. Use it to clean up recorded tracks, attribute distance and time to roads, or recover road names and speeds along a trip.

How it works

The gateway runs a single Engine, Valhalla, in map-snap mode (Meili). It makes two Valhalla calls in parallel:

  1. trace_attributes supplies the matched edges (one step per road edge), matched points, geometry, and road attributes such as speed, surface, lane count and OSM way ID.

  2. trace_route supplies the turn instruction text for each step, in the requested lang. If this call fails (other than the Engine being down), steps are returned without instruction.

Costing is derived from mode. Road data is OpenStreetMap; admin data (state, country) comes from the Valhalla tiles. No Geocoding Waterfall, Nominatim or other geocoder is involved.

Timestamps are used for matching only when every waypoint has one and they are strictly increasing. Otherwise all timestamps are ignored. bearing values are passed through to Valhalla as headings.

Quick start

curl -s -X POST -H "Content-Type: application/json" -d '{"mode":"drive","waypoints":[{"location":[-86.15813,39.7690]},{"location":[-86.15813,39.7705]},{"location":[-86.15813,39.7720]},{"location":[-86.15813,39.7735]},{"location":[-86.15813,39.7750]},{"location":[-86.15813,39.7765]}]}' "https://api.data.graphitelab.ai/v1/mapmatching?apiKey=$GRAPHITE_API_KEY"

Response, trimmed to the first waypoint and first step:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "mode": "drive",
        "distance": 827,
        "time": 74.43,
        "legs": [
          {
            "time": 74.43,
            "distance": 827,
            "steps": [
              {
                "from_index": 0,
                "to_index": 1,
                "time": 1.17,
                "distance": 13,
                "osm_way_id": 113084204,
                "instruction": { "text": "Drive north on North Meridian Street." },
                "toll": false,
                "ferry": false,
                "bridge": false,
                "tunnel": false,
                "roundabout": false,
                "speed": 40,
                "speed_limit": 40,
                "surface": "paved",
                "lane_count": 2,
                "road_class": "tertiary",
                "name": "North Meridian Street",
                "begin_bearing": 2,
                "end_bearing": 2
              }
            ]
          }
        ],
        "waypoints": [
          {
            "original_index": 0,
            "location": [-86.15802, 39.769051],
            "original_location": [-86.15813, 39.769],
            "match_type": "matched",
            "match_distance": 10.917381,
            "leg_index": 0,
            "step_index": 0
          }
        ]
      },
      "geometry": {
        "type": "MultiLineString",
        "coordinates": [[[-86.15802, 39.769051], [-86.158016, 39.769167], [-86.157723, 39.776491]]]
      }
    }
  ],
  "bbox": [-86.15802, 39.769051, -86.157723, 39.776491]
}

Parameters

Send a JSON body. Only mode and waypoints are required. The apiKey goes in the query string.

NameTypeRequiredWhat it does
modestringyesTravel profile used to decide which roads can connect the points: walk, hike, scooter, motorcycle, drive, light_truck, bicycle, mountain_bike, road_bike, bus, medium_truck, truck, truck_dangerous_goods, heavy_truck, long_truck.
waypointsarrayyes2 to 1,000 ordered GPS points. Order is preserved, never reordered.
waypoints[].locationarrayyes[longitude, latitude].
waypoints[].timestampstringnoISO 8601 date-time. Used only if all points have one and they strictly increase.
waypoints[].bearingnumbernoTravel direction in degrees clockwise from north, 0 to 360.
geocode_step_addressbooleannoAdds an address object to each step. Default false.
lookup_step_osmtagsbooleannoAdds osmtags to each step. Default false.
lookup_osm_overlapbooleannoAdds overlaps to each step. Default false.
langstringnoInstruction language: en, de, es, fr, hi, it, pt, ru, sl, sv, ca, cs. Default en.

Full parameter reference

Response

The body is a GeoJSON FeatureCollection with one Feature and a top-level bbox. This response has no place_id, datasource or rank fields.

FieldMeaning
geometryMultiLineString of the matched route, [longitude, latitude] pairs.
properties.distanceTotal distance in meters.
properties.timeTotal time in seconds.
properties.legs[0].stepsOne step per road edge. Each has distance (meters), time (seconds), osm_way_id, name, road_class, surface, lane_count, speed and speed_limit (km/h), truck_limit when known, flags (toll, ferry, bridge, tunnel, roundabout), begin_bearing and end_bearing.
steps[].from_index, to_indexIndexes into the matched geometry for the step.
steps[].instruction.textTurn instruction in lang. Present only on steps that start a maneuver.
properties.waypointsOne entry per input point, in input order.
waypoints[].match_typematched, unmatched, interpolated or merged.
waypoints[].locationSnapped position. null when the point is not matched or interpolated.
waypoints[].original_locationThe point you sent.
waypoints[].match_distanceDistance in meters from your point to the snapped position; null when unmatched.
waypoints[].leg_index, step_indexWhich step the point was matched to.

Differences from Geoapify

  • unsupported: at least 2 waypoints are required (1 returns 400). The transit and approximated_transit modes are not accepted.

  • approximated: geocode_step_address returns only street, state, country and country_code (lower case). Step instruction carries only text.

  • empty: lookup_step_osmtags yields osmtags: {} and lookup_osm_overlap yields overlaps: [] on every step.

  • extra: a trace that cannot be matched to any road returns 400 ("The trace could not be matched to the road network").

Step fields elevation, elevation_gain, max_elevation, min_elevation, admin_area, rightside, traversability, from_stop_* and to_stop_* are not returned. application/geo+json and XML output are not supported.

US and Canada notes

Matching runs on OpenStreetMap roads in the Valhalla tiles for the US and Canada. State and country in step addresses come from the tiles' admin data, not from ZCTA, FSA or Address Point sources. Street names are the OSM way names as stored, so Canadian French names appear as OSM has them, and lang affects only the instruction text.

More examples

With address lookups and timestamps

Sets geocode_step_address, lookup_step_osmtags and lookup_osm_overlap, and sends a strictly increasing timestamp on every point so timing is used for matching. Each step gains address, osmtags (always {}) and overlaps (always []).

curl -s -X POST -H "Content-Type: application/json" -d '{"mode":"drive","geocode_step_address":true,"lookup_step_osmtags":true,"lookup_osm_overlap":true,"waypoints":[{"location":[-86.15813,39.7690],"timestamp":"2026-11-01T14:00:00Z"},{"location":[-86.15813,39.7705],"timestamp":"2026-11-01T14:00:10Z"},{"location":[-86.15813,39.7720],"timestamp":"2026-11-01T14:00:20Z"},{"location":[-86.15813,39.7735],"timestamp":"2026-11-01T14:00:30Z"},{"location":[-86.15813,39.7750],"timestamp":"2026-11-01T14:00:40Z"}]}' "https://api.data.graphitelab.ai/v1/mapmatching?apiKey=$GRAPHITE_API_KEY"

A step from the response (trimmed):

{
  "from_index": 0,
  "to_index": 1,
  "time": 1.17,
  "distance": 13,
  "osm_way_id": 113084204,
  "name": "North Meridian Street",
  "address": { "street": "North Meridian Street", "state": "None", "country": "None" },
  "osmtags": {},
  "overlaps": []
}

Errors and limits

StatusMeaning
400Invalid or missing parameters: fewer than 2 or more than 1,000 waypoints, bad location, timestamp or bearing, unsupported mode, or a trace that cannot be matched to any road.
401Missing or invalid API key.
500Unexpected gateway error.
502An Engine returned an error.
503An Engine is down.

Limit: 1,000 waypoints per request.

Known issue: with geocode_step_address=true, state and country can come back as the text "None" and country_code is missing, because admin data is missing in the production tiles or is not passed through by the gateway. The same defect affects /routing with details=admin_areas. Treat those values as unreliable until it is fixed.

Try in Playground (signed in to your Graphite Lab organization). Signed out, use the paste-your-key widget on the reference page.