Skip to main content

Encoded Polyline Decoder & Encoder API

REAL DATA

Decode and encode Google/OSRM encoded polylines at precision 5 or 6 with GeoJSON output, great-circle length, Douglas-Peucker simplify and a decode trace.

Authoritative reference data or standards computation

Base URL
/api/polyline
Capabilities
4 routes
Last updated
July 30, 2026
Data sources · 4
+1 more

GET /api/polyline/decode

Live requestRuns against the public API
No key required
GET/api/polyline/decode?value=_p%7EiF%7Eps%7CU_ulLnnqC_mqNvxq%60%40&precision=5

Request parameters

The encoded polyline, exactly as your routing provider returned it. Percent-encode the eight characters a URI query forbids ([ \ ] ^ ` { | }); ~, _, ? and @ are safe raw. Surrounding whitespace is trimmed — it is below the alphabet's ASCII 63 floor, so it can never be meaningful. Maximum 8,000 characters.

Decimal places the integers are scaled by. 5 = Google Directions/Routes overview_polyline, @mapbox/polyline default, GraphHopper. 6 = OSRM geometries=polyline6, Valhalla shape. No other precision is accepted.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/polyline/decode?value=_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40&precision=5"
const res = await fetch("https://randomapi.dev/api/polyline/decode?value=_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40&precision=5");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/polyline/decode?value=_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40&precision=5").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/polyline/decode?value=_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40&precision=5"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 2
value string required

The encoded polyline, exactly as your routing provider returned it. Percent-encode the eight characters a URI query forbids ([ \ ] ^ ` { | }); ~, _, ? and @ are safe raw. Surrounding whitespace is trimmed — it is below the alphabet's ASCII 63 floor, so it can never be meaningful. Maximum 8,000 characters.

allowed: ≤ 8000
example: value=_p~iF~ps|U_ulLnnqC_mqNvxq`@
precision int

Decimal places the integers are scaled by. 5 = Google Directions/Routes overview_polyline, @mapbox/polyline default, GraphHopper. 6 = OSRM geometries=polyline6, Valhalla shape. No other precision is accepted.

default: 5
allowed: 5 – 6
example: precision=6
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=precision,pointCount
exclude list

Return all fields except these (comma-separated).

example: exclude=radiusMeters
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 11
precision integer

Decimal places used to scale the decoded integers.

example: 5

pointCount integer

Number of coordinate pairs decoded.

example: 3

encodedLength integer

Characters of 'value' that were decoded, after surrounding whitespace is trimmed.

example: 27

coordinates object[]

Decoded points in the format's own order, { latitude, longitude } in WGS 84 decimal degrees, at most 'precision' decimals each.

example: [{"latitude":38.5,"longitude":-120.2}]

geojson object

RFC 7946 geometry ready to paste into a map tool: LineString for two or more points, Point for exactly one (section 3.1.4 requires two or more positions), 'coordinates' in [longitude, latitude] order (section 3.1.1), plus a section 5 'bbox' array [west, south, east, north].

example: {"type":"LineString","bbox":[-126.453,38.5,-120.2,43.252],"coordinates":[[-120.2,38.5],[-120.95,40.7],[-126.453,43.252]]}

bounds object

Plain minimum/maximum of the decoded coordinates as { west, south, east, north }. A geometry crossing the antimeridian therefore yields a box spanning most of the globe; RFC 7946 section 5.2's west > east narrow box is deliberately not inferred.

example: {"west":-126.453,"south":38.5,"east":-120.2,"north":43.252}

lengthMeters float

Sum of the great-circle segment lengths on the exposed mean-Earth sphere, in metres, rounded to millimetres. 0 for a single point. Not road distance.

example: 788906.96

lengthKilometers float

The same length in kilometres.

example: 788.90696

canonical boolean

True when every value was encoded minimally. A non-minimal group (a redundant continuation character) still decodes, and adds a meta.warnings entry.

example: true

model string

Explicit distance model: mean-earth-sphere. Not ellipsoidal, routed or terrain distance.

example: mean-earth-sphere

radiusMeters float

IUGG mean Earth radius used for every length, in metres.

example: 6371008.8

Documented examples

Build-generated requests and complete responses
3
Google's own worked example (precision 5)
GET /api/polyline/decode?value=_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40&precision=5
{
  "data": {
    "precision": 5,
    "pointCount": 3,
    "encodedLength": 27,
    "coordinates": [
      {
        "latitude": 38.5,
        "longitude": -120.2
      },
      {
        "latitude": 40.7,
        "longitude": -120.95
      },
      {
        "latitude": 43.252,
        "longitude": -126.453
      }
    ],
    "geojson": {
      "type": "LineString",
      "bbox": [
        -126.453,
        38.5,
        -120.2,
        43.252
      ],
      "coordinates": [
        [
          -120.2,
          38.5
        ],
        [
          -120.95,
          40.7
        ],
        [
          -126.453,
          43.252
        ]
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
OSRM polyline6 / Valhalla shape (precision 6)
GET /api/polyline/decode?value=e~epoA%7CjfpOiDaK&precision=6
{
  "data": {
    "precision": 6,
    "pointCount": 2,
    "encodedLength": 15,
    "coordinates": [
      {
        "latitude": 42.225139,
        "longitude": -8.670911
      },
      {
        "latitude": 42.225224,
        "longitude": -8.670718
      }
    ],
    "geojson": {
      "type": "LineString",
      "bbox": [
        -8.670911,
        42.225139,
        -8.670718,
        42.225224
      ],
      "coordinates": [
        [
          -8.670911,
          42.225139
        ],
        [
          -8.670718,
          42.225224
        ]
      ]
    },
    "bounds": {
      "west": -8.670911,
      "south": 42.225139,
      "east": -8.670718,
      "north": 42.225224
    },
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Eight-point route at precision 6
GET /api/polyline/decode?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6
{
  "data": {
    "precision": 6,
    "pointCount": 8,
    "encodedLength": 48,
    "coordinates": [
      {
        "latitude": 52.435858,
        "longitude": 10.743689
      },
      {
        "latitude": 52.435763,
        "longitude": 10.743718
      },
      {
        "latitude": 52.439483,
        "longitude": 10.784067
      },
      {
        "latitude": 52.434343,
        "longitude": 10.785341
      },
      {
        "latitude": 52.434264,
        "longitude": 10.785223
      },
      {
        "latitude": 52.434186,
        "longitude": 10.785377
      },
      {
        "latitude": 52.431267,
        "longitude": 10.786081
      },
      {
        "latitude": 52.43059,
        "longitude": 10.779242
      }
    ],
    "geojson": {
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

GET /api/polyline/encode

Live requestRuns against the public API
No key required
GET/api/polyline/encode?coordinates=38.5%2C-120.2%3B40.7%2C-120.95%3B43.252%2C-126.453&precision=5

Request parameters

'lat,lon' pairs separated by ';', for example 38.5,-120.2;40.7,-120.95. Whitespace around numbers and separators is ignored, as are empty segments. At most 1000 pairs.

Decimal places the integers are scaled by. 5 = Google Directions/Routes overview_polyline, @mapbox/polyline default, GraphHopper. 6 = OSRM geometries=polyline6, Valhalla shape. No other precision is accepted.

Axis order of the INPUT text only. 'lonlat' accepts GeoJSON-order pairs. The encoded output is always latitude-first, as the format requires.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/polyline/encode?coordinates=38.5,-120.2;40.7,-120.95;43.252,-126.453&precision=5"
const res = await fetch("https://randomapi.dev/api/polyline/encode?coordinates=38.5,-120.2;40.7,-120.95;43.252,-126.453&precision=5");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/polyline/encode?coordinates=38.5,-120.2;40.7,-120.95;43.252,-126.453&precision=5").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/polyline/encode?coordinates=38.5,-120.2;40.7,-120.95;43.252,-126.453&precision=5"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 3
coordinates string required

'lat,lon' pairs separated by ';', for example 38.5,-120.2;40.7,-120.95. Whitespace around numbers and separators is ignored, as are empty segments. At most 1000 pairs.

allowed: ≤ 8000
example: coordinates=42.225139,-8.670911;42.225224,-8.670718
precision int

Decimal places the integers are scaled by. 5 = Google Directions/Routes overview_polyline, @mapbox/polyline default, GraphHopper. 6 = OSRM geometries=polyline6, Valhalla shape. No other precision is accepted.

default: 5
allowed: 5 – 6
example: precision=6
order enum

Axis order of the INPUT text only. 'lonlat' accepts GeoJSON-order pairs. The encoded output is always latitude-first, as the format requires.

default: latlon
allowed: latlon | lonlat
example: order=lonlat
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=precision,pointCount
exclude list

Return all fields except these (comma-separated).

example: exclude=radiusMeters
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 11
precision integer

Decimal places the coordinates were scaled by.

example: 5

pointCount integer

Number of input pairs encoded.

example: 3

encoded string

The encoded polyline.

example: _p~iF~ps|U_ulLnnqC_mqNvxq`@

encodedLength integer

Characters in 'encoded'.

example: 27

urlEncoded string

'encoded' run through encodeURIComponent — safe to paste into a query string, a path segment or a form body. It escapes the eight characters RFC 3986 section 3.4 forbids in a query ([ \ ] ^ ` { | }) and also @ and ?, which section 3.4 permits but which are ambiguous elsewhere in a URL; over-escaping is safe, under-escaping is not.

example: _p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40

points object[]

One row per input pair: { index, latitude, longitude, encodedLatitude, encodedLongitude, offsetMeters } — the value you sent, the value the precision grid could actually store (Math.round(value * 10 ** precision), which breaks exact ties toward positive infinity where C's round() breaks them away from zero), and the great-circle distance between them in metres, rounded to a tenth of a millimetre. Trim it with ?exclude=points.

example: [{"index":0,"latitude":38.5,"longitude":-120.2,"encodedLatitude":38.5,"encodedLongitude":-120.2,"offsetMeters":0}]

lossyPointCount integer

How many input points the precision grid moved.

example: 0

maxOffsetMeters float

Largest offsetMeters across all points, rounded to a tenth of a millimetre. Bounded by 0.7863 m at precision 5 and 0.07863 m at precision 6.

example: 0

roundTripsExactly boolean

True when decoding 'encoded' at 'precision' reproduces every input coordinate exactly — equivalently, lossyPointCount === 0.

example: true

model string

Explicit distance model: mean-earth-sphere. Not ellipsoidal, routed or terrain distance.

example: mean-earth-sphere

radiusMeters float

IUGG mean Earth radius used for every length, in metres.

example: 6371008.8

Documented examples

Build-generated requests and complete responses
4
Google's three-point example (precision 5)
GET /api/polyline/encode?coordinates=38.5,-120.2;40.7,-120.95;43.252,-126.453&precision=5
{
  "data": {
    "precision": 5,
    "pointCount": 3,
    "encoded": "_p~iF~ps|U_ulLnnqC_mqNvxq`@",
    "encodedLength": 27,
    "urlEncoded": "_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40",
    "points": [
      {
        "index": 0,
        "latitude": 38.5,
        "longitude": -120.2,
        "encodedLatitude": 38.5,
        "encodedLongitude": -120.2,
        "offsetMeters": 0
      },
      {
        "index": 1,
        "latitude": 40.7,
        "longitude": -120.95,
        "encodedLatitude": 40.7,
        "encodedLongitude": -120.95,
        "offsetMeters": 0
      },
      {
        "index": 2,
        "latitude": 43.252,
        "longitude": -126.453,
        "encodedLatitude": 43.252,
        "encodedLongitude": -126.453,
        "offsetMeters": 0
      }
    ],
    "lossyPointCount": 0,
    "maxOffsetMeters": 0,
    "roundTripsExactly": true,
    "model": "mean-earth-sphere",
    "radiusMeters": 6371008.8
  },
  "meta": {
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
The same points as OSRM polyline6
GET /api/polyline/encode?coordinates=38.5,-120.2;40.7,-120.95;43.252,-126.453&precision=6
{
  "data": {
    "precision": 6,
    "pointCount": 3,
    "encoded": "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI",
    "encodedLength": 32,
    "urlEncoded": "_izlhA~rlgdF_%7BgeC~ywl%40_kwzCn%60%7BnI",
    "points": [
      {
        "index": 0,
        "latitude": 38.5,
        "longitude": -120.2,
        "encodedLatitude": 38.5,
        "encodedLongitude": -120.2,
        "offsetMeters": 0
      },
      {
        "index": 1,
        "latitude": 40.7,
        "longitude": -120.95,
        "encodedLatitude": 40.7,
        "encodedLongitude": -120.95,
        "offsetMeters": 0
      },
      {
        "index": 2,
        "latitude": 43.252,
        "longitude": -126.453,
        "encodedLatitude": 43.252,
        "encodedLongitude": -126.453,
        "offsetMeters": 0
      }
    ],
    "lossyPointCount": 0,
    "maxOffsetMeters": 0,
    "roundTripsExactly": true,
    "model": "mean-earth-sphere",
    "radiusMeters": 6371008.8
  },
  "meta": {
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
GeoJSON-order input, longitude first
GET /api/polyline/encode?coordinates=-8.670911,42.225139;-8.670718,42.225224&order=lonlat&precision=6
{
  "data": {
    "precision": 6,
    "pointCount": 2,
    "encoded": "e~epoA|jfpOiDaK",
    "encodedLength": 15,
    "urlEncoded": "e~epoA%7CjfpOiDaK",
    "points": [
      {
        "index": 0,
        "latitude": 42.225139,
        "longitude": -8.670911,
        "encodedLatitude": 42.225139,
        "encodedLongitude": -8.670911,
        "offsetMeters": 0
      },
      {
        "index": 1,
        "latitude": 42.225224,
        "longitude": -8.670718,
        "encodedLatitude": 42.225224,
        "encodedLongitude": -8.670718,
        "offsetMeters": 0
      }
    ],
    "lossyPointCount": 0,
    "maxOffsetMeters": 0,
    "roundTripsExactly": true,
    "model": "mean-earth-sphere",
    "radiusMeters": 6371008.8
  },
  "meta": {
    "endpoint": "polyline",
    "route": "/encode",
    "params": {
      "coordinates": "-8.670911,42.225139;-8.670718,42.225224",
      "precision": 6,
      "order": "lonlat"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Coordinates the precision-5 grid cannot store exactly
GET /api/polyline/encode?coordinates=38.5000049,-120.2000051&precision=5
{
  "data": {
    "precision": 5,
    "pointCount": 1,
    "encoded": "_p~iF`qs|U",
    "encodedLength": 10,
    "urlEncoded": "_p~iF%60qs%7CU",
    "points": [
      {
        "index": 0,
        "latitude": 38.5000049,
        "longitude": -120.2000051,
        "encodedLatitude": 38.5,
        "encodedLongitude": -120.20001,
        "offsetMeters": 0.6919
      }
    ],
    "lossyPointCount": 1,
    "maxOffsetMeters": 0.6919,
    "roundTripsExactly": false,
    "model": "mean-earth-sphere",
    "radiusMeters": 6371008.8
  },
  "meta": {
    "endpoint": "polyline",
    "route": "/encode",
    "params": {
      "coordinates": "38.5000049,-120.2000051",
      "precision": 5,
      "order": "latlon"
    },
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

GET /api/polyline/simplify

Live requestRuns against the public API
No key required
GET/api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=10

Request parameters

The encoded polyline, exactly as your routing provider returned it. Percent-encode the eight characters a URI query forbids ([ \ ] ^ ` { | }); ~, _, ? and @ are safe raw. Surrounding whitespace is trimmed — it is below the alphabet's ASCII 63 floor, so it can never be meaningful. Maximum 8,000 characters.

Decimal places the integers are scaled by. 5 = Google Directions/Routes overview_polyline, @mapbox/polyline default, GraphHopper. 6 = OSRM geometries=polyline6, Valhalla shape. No other precision is accepted.

Maximum distance any dropped point may sit from the retained line, in metres on the exposed sphere. 0 drops only points whose distance to their chord is exactly 0 (duplicates and exactly collinear points).

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=10"
const res = await fetch("https://randomapi.dev/api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=10");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=10").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=10"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 3
value string required

The encoded polyline, exactly as your routing provider returned it. Percent-encode the eight characters a URI query forbids ([ \ ] ^ ` { | }); ~, _, ? and @ are safe raw. Surrounding whitespace is trimmed — it is below the alphabet's ASCII 63 floor, so it can never be meaningful. Maximum 8,000 characters.

allowed: ≤ 8000
example: value=_p~iF~ps|U_ulLnnqC_mqNvxq`@
precision int

Decimal places the integers are scaled by. 5 = Google Directions/Routes overview_polyline, @mapbox/polyline default, GraphHopper. 6 = OSRM geometries=polyline6, Valhalla shape. No other precision is accepted.

default: 5
allowed: 5 – 6
example: precision=6
toleranceMeters float required

Maximum distance any dropped point may sit from the retained line, in metres on the exposed sphere. 0 drops only points whose distance to their chord is exactly 0 (duplicates and exactly collinear points).

allowed: 0 – 100000
example: toleranceMeters=10
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=precision,toleranceMeters
exclude list

Return all fields except these (comma-separated).

example: exclude=radiusMeters
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 16
precision integer

Precision used to decode the input AND re-encode the output.

example: 6

toleranceMeters float

Requested tolerance, echoed.

example: 10

algorithm string

Named method, so the numbers are reproducible.

example: ramer-douglas-peucker/spherical-cross-track

pointCountBefore integer

Points decoded from 'value'. At most 1000 on this route, and a geometry whose shape would drive Ramer–Douglas–Peucker past 150,000 spherical distance evaluations is refused with a 400 rather than run over the CPU budget — any geometry of 547 points or fewer always fits, whatever its shape.

example: 8

pointCountAfter integer

Points retained.

example: 5

removedPointCount integer

pointCountBefore - pointCountAfter.

example: 3

maxDeviationMeters float

Measured maximum distance from a dropped point to the retained chord that replaced it, in metres rounded to the nearest millimetre. 0 when nothing was dropped. The algorithm's own guarantee is exact — no dropped point is further than toleranceMeters from its chord — but because this figure is rounded rather than truncated it can read up to 0.5 mm above toleranceMeters (reachable only when a tolerance is set to within half a millimetre of an actual deviation).

example: 8.276

keptIndexes integer[]

Indexes of the retained points in the original list — strictly increasing, always starting at 0 and ending at pointCountBefore - 1. Use it to carry per-point attributes across.

example: [0,1,2,6,7]

coordinates object[]

Retained points as { latitude, longitude }.

example: [{"latitude":52.435858,"longitude":10.743689}]

encoded string

The retained points re-encoded at 'precision'. Decoding it at the same precision reproduces 'coordinates' exactly.

example: cxl_cBqwvnS|Dy@ogFyxmAn`O{|Bhi@ljL

urlEncoded string

'encoded', query-safe (encodeURIComponent).

example: cxl_cBqwvnS%7CDy%40ogFyxmAn%60O%7B%7CBhi%40ljL

lengthMetersBefore float

Great-circle length of the original chain, in metres.

example: 4178.287

lengthMetersAfter float

Great-circle length of the simplified chain, in metres.

example: 4170.459

geojson object

RFC 7946 geometry of the simplified line, same rules as /decode.

example: {"type":"LineString","bbox":[10.743689,52.43059,10.786081,52.439483],"coordinates":[[10.743689,52.435858],[10.743718,52.435763],[10.784067,52.439483],[10.786081,52.431267],[10.779242,52.43059]]}

model string

Explicit distance model: mean-earth-sphere. Not ellipsoidal, routed or terrain distance.

example: mean-earth-sphere

radiusMeters float

IUGG mean Earth radius used for every length, in metres.

example: 6371008.8

Documented examples

Build-generated requests and complete responses
3
Trim an eight-point route to a 10-metre tolerance
GET /api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=10
{
  "data": {
    "precision": 6,
    "toleranceMeters": 10,
    "algorithm": "ramer-douglas-peucker/spherical-cross-track",
    "pointCountBefore": 8,
    "pointCountAfter": 5,
    "removedPointCount": 3,
    "maxDeviationMeters": 8.276,
    "keptIndexes": [
      0,
      1,
      2,
      6,
      7
    ],
    "coordinates": [
      {
        "latitude": 52.435858,
        "longitude": 10.743689
      },
      {
        "latitude": 52.435763,
        "longitude": 10.743718
      },
      {
        "latitude": 52.439483,
        "longitude": 10.784067
      },
      {
        "latitude": 52.431267,
        "longitude": 10.786081
      },
      {
        "latitude": 52.43059,
        "longitude": 10.779242
      }
    ],
    "encoded": "cxl_cBqwvnS|Dy@ogFyxmAn`O{|Bhi@ljL",
    "urlEncoded": "cxl_cBqwvnS%7CDy%40ogFyxmAn%60O%7B%7CBhi%40ljL",
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Aggressive 500-metre simplification
GET /api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=500
{
  "data": {
    "precision": 6,
    "toleranceMeters": 500,
    "algorithm": "ramer-douglas-peucker/spherical-cross-track",
    "pointCountBefore": 8,
    "pointCountAfter": 3,
    "removedPointCount": 5,
    "maxDeviationMeters": 416.566,
    "keptIndexes": [
      0,
      2,
      7
    ],
    "coordinates": [
      {
        "latitude": 52.435858,
        "longitude": 10.743689
      },
      {
        "latitude": 52.439483,
        "longitude": 10.784067
      },
      {
        "latitude": 52.43059,
        "longitude": 10.779242
      }
    ],
    "encoded": "cxl_cBqwvnSqaFszmAxjPplH",
    "urlEncoded": "cxl_cBqwvnSqaFszmAxjPplH",
    "lengthMetersBefore": 4178.287,
    "lengthMetersAfter": 3808.184,
    "geojson": {
      "type": "LineString",
      "bbox": [
        10.743689,
        52.43059,
        10.784067,
        52.439483
      ],
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
Zero tolerance keeps every point that is not exactly collinear
GET /api/polyline/simplify?value=cxl_cBqwvnS%7CDy%40ogFyxmAf%60IsnA%7CCjFzCsHluD_k%40hi%40ljL&precision=6&toleranceMeters=0
{
  "data": {
    "precision": 6,
    "toleranceMeters": 0,
    "algorithm": "ramer-douglas-peucker/spherical-cross-track",
    "pointCountBefore": 8,
    "pointCountAfter": 8,
    "removedPointCount": 0,
    "maxDeviationMeters": 0,
    "keptIndexes": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7
    ],
    "coordinates": [
      {
        "latitude": 52.435858,
        "longitude": 10.743689
      },
      {
        "latitude": 52.435763,
        "longitude": 10.743718
      },
      {
        "latitude": 52.439483,
        "longitude": 10.784067
      },
      {
        "latitude": 52.434343,
        "longitude": 10.785341
      },
      {
        "latitude": 52.434264,
        "longitude": 10.785223
      },
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

GET /api/polyline/inspect

Returns 200 with a stable diagnostic record even for a string /decode rejects. It only fails on parameter validation and on the 2,000-point cap.

Live requestRuns against the public API
No key required
GET/api/polyline/inspect?value=e%7EepoA%7CjfpOiDaK

Request parameters

The encoded polyline, exactly as your routing provider returned it. Percent-encode the eight characters a URI query forbids ([ \ ] ^ ` { | }); ~, _, ? and @ are safe raw. Surrounding whitespace is trimmed — it is below the alphabet's ASCII 63 floor, so it can never be meaningful. Maximum 8,000 characters.

How many varint groups to include in 'trace'. Each coordinate pair is two groups (latitude then longitude), so the default traces four points. 0 omits the trace.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/polyline/inspect?value=e~epoA%7CjfpOiDaK"
const res = await fetch("https://randomapi.dev/api/polyline/inspect?value=e~epoA%7CjfpOiDaK");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/polyline/inspect?value=e~epoA%7CjfpOiDaK").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/polyline/inspect?value=e~epoA%7CjfpOiDaK"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 2
value string required

The encoded polyline, exactly as your routing provider returned it. Percent-encode the eight characters a URI query forbids ([ \ ] ^ ` { | }); ~, _, ? and @ are safe raw. Surrounding whitespace is trimmed — it is below the alphabet's ASCII 63 floor, so it can never be meaningful. Maximum 8,000 characters.

allowed: ≤ 8000
example: value=_p~iF~ps|U_ulLnnqC_mqNvxq`@
traceGroups int

How many varint groups to include in 'trace'. Each coordinate pair is two groups (latitude then longitude), so the default traces four points. 0 omits the trace.

default: 8
allowed: 0 – 100
example: traceGroups=6
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=valueLength,decodable
exclude list

Return all fields except these (comma-separated).

example: exclude=trace
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 13
valueLength integer

Characters examined, after surrounding whitespace is trimmed.

example: 15

decodable boolean

True when the whole string parses as an even number of complete varint groups using only the ASCII 63–126 alphabet. Precision-dependent coordinate range is a separate question, answered by precisionHypotheses.

example: true

problem object nullable

null when decodable; otherwise { kind, message, index, character, codePoint } where kind is one of unsupported-character, unfinished-value, odd-value-count, oversized-value, and index is 0-based into the trimmed string (character/codePoint are null for the non-character kinds).

example: null

groupCount integer

Complete varint groups read before the string ended or failed.

example: 4

pointCount integer nullable

groupCount / 2 when decodable, else null — a partial decode is never published as points.

example: 2

canonical boolean nullable

True when every group read is minimally encoded; null when not decodable.

example: true

nonCanonicalGroupIndexes integer[]

0-based indexes of groups carrying a redundant most-significant zero chunk. Empty for every real-world encoder's output.

example: []

likelyPrecision integer nullable

The single precision under which every coordinate is a valid WGS 84 position, or null when both or neither qualify. A hypothesis, not a verdict.

example: 6

likelyPrecisionBasis string

Why, in words, including the evidence that rules a precision out.

example: Only precision 6 keeps every coordinate inside latitude ±90 and longitude ±180; at precision 5 latitude reaches 422.25139 at point 0.

precisionHypotheses object[]

One row per candidate precision (5 then 6): { precision, verdict, firstCoordinate, lastCoordinate, latitudeRange, longitudeRange, outOfRangeAtPoint, note }, verdict being 'consistent' or 'out-of-range'. Empty array when not decodable.

example: [{"precision":5,"verdict":"out-of-range","firstCoordinate":{"latitude":422.25139,"longitude":-86.70911},"lastCoordinate":{"latitude":422.25224,"longitude":-86.70718},"latitudeRange":{"minimum":422.25139,"maximum":422.25224},"longitudeRange":{"minimum":-86.70911,"maximum":-86.70718},"outOfRangeAtPoint":0,"note":"Point 0 reads as latitude 422.25139 at precision 5, outside -90 to 90, so the string cannot be precision 5."}]

disallowedInQueryCharacters string[]

The distinct characters present in 'value' that RFC 3986 section 3.4 forbids unescaped in a query component ([ \ ] ^ ` { | }), in order of first appearance. ~, _, ? and @ are legal and never listed.

example: ["|"]

urlEncoded string

'value' run through encodeURIComponent, ready to resend. It also escapes @ and ?, which a query component permits.

example: e~epoA%7CjfpOiDaK

trace object[]

Up to 'traceGroups' rows: { groupIndex, pointIndex, axis, characters, characterCodes, fiveBitChunks, zigZagValue, delta, accumulated, atPrecision5, atPrecision6 }. fiveBitChunks are least-significant-first with the continuation bit stripped ((code - 63) & 0x1f), so zigZagValue === the sum of chunk[i] * 32^i exactly. Everything up to 'accumulated' is integer and precision-independent; only the last two divide by the scale factor.

example: [{"groupIndex":0,"pointIndex":0,"axis":"latitude","characters":"e~epoA","characterCodes":[101,126,101,112,111,65],"fiveBitChunks":[6,31,6,17,16,2],"zigZagValue":84450278,"delta":42225139,"accumulated":42225139,"atPrecision5":422.25139,"atPrecision6":42.225139}]

Documented examples

Build-generated requests and complete responses
3
Diagnose a string whose precision you do not know
GET /api/polyline/inspect?value=e~epoA%7CjfpOiDaK
{
  "data": {
    "valueLength": 15,
    "decodable": true,
    "problem": null,
    "groupCount": 4,
    "pointCount": 2,
    "canonical": true,
    "nonCanonicalGroupIndexes": [],
    "likelyPrecision": 6,
    "likelyPrecisionBasis": "Only precision 6 keeps every coordinate inside latitude ±90 and longitude ±180; at precision 5 latitude reaches 422.25139 at point 0.",
    "precisionHypotheses": [
      {
        "precision": 5,
        "verdict": "out-of-range",
        "firstCoordinate": {
          "latitude": 422.25139,
          "longitude": -86.70911
        },
        "lastCoordinate": {
          "latitude": 422.25224,
          "longitude": -86.70718
        },
        "latitudeRange": {
          "minimum": 422.25139,
          "maximum": 422.25224
        },
        "longitudeRange": {
          "minimum": -86.70911,
          "maximum": -86.70718
        },
        "outOfRangeAtPoint": 0,
        "note": "Point 0 reads as latitude 422.25139 at precision 5, outside -90 to 90, so the string cannot be precision 5."
      },
      {
        "precision": 6,
        "verdict": "consistent",
        "firstCoordinate": {
          "latitude": 42.225139,
          "longitude": -8.670911"generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
A precision-5 string that also decodes at precision 6
GET /api/polyline/inspect?value=_p~iF~ps%7CU_ulLnnqC_mqNvxq%60%40&traceGroups=6
{
  "data": {
    "valueLength": 27,
    "decodable": true,
    "problem": null,
    "groupCount": 6,
    "pointCount": 3,
    "canonical": true,
    "nonCanonicalGroupIndexes": [],
    "likelyPrecision": null,
    "likelyPrecisionBasis": "Both precision 5 and precision 6 keep every coordinate inside latitude ±90 and longitude ±180, so the string alone cannot decide. Precision 5 puts point 0 at 38.5, -120.2; precision 6 puts it at 3.85, -12.02. Check which one lands where you expect.",
    "precisionHypotheses": [
      {
        "precision": 5,
        "verdict": "consistent",
        "firstCoordinate": {
          "latitude": 38.5,
          "longitude": -120.2
        },
        "lastCoordinate": {
          "latitude": 43.252,
          "longitude": -126.453
        },
        "latitudeRange": {
          "minimum": 38.5,
          "maximum": 43.252
        },
        "longitudeRange": {
          "minimum": -126.453,
          "maximum": -120.2
        },
        "outOfRangeAtPoint": null,
        "note": "All 3 coordinates stay inside latitude ±90 and longitude ±180 at precision 5, so this reading is possible."
      },
      {
        "precision": 6,
        "verdict": "consistent",
        "firstCoordinate": {
          "latitude": 3.85,
          "longitude": -12.02"generatedAt": "2026-07-30T15:14:08.000Z"
  }
}
A truncated string that /decode rejects
GET /api/polyline/inspect?value=_p~iF~ps%7CU_ulL
{
  "data": {
    "valueLength": 14,
    "decodable": false,
    "problem": {
      "kind": "odd-value-count",
      "message": "'value' decoded 3 values; encoded polylines store latitude/longitude pairs, so the count must be even. The string is truncated after a latitude delta (or it carries three values per vertex, which this endpoint does not support).",
      "index": 10,
      "character": null,
      "codePoint": null
    },
    "groupCount": 3,
    "pointCount": null,
    "canonical": null,
    "nonCanonicalGroupIndexes": [],
    "likelyPrecision": null,
    "likelyPrecisionBasis": "The string does not parse as an encoded polyline (odd-value-count at index 10), so no precision hypothesis is possible and no coordinates are published.",
    "precisionHypotheses": [],
    "disallowedInQueryCharacters": [
      "|"
    ],
    "urlEncoded": "_p~iF~ps%7CU_ulL",
    "trace": [
      {
        "groupIndex": 0,
        "pointIndex": 0,
        "axis": "latitude",
        "characters": "_p~iF",
        "characterCodes": [
          95,
          112,
          126,
          105,
          70
        ],
        "fiveBitChunks": [
          0,
          17,
          31,
          10,
    …
    "generatedAt": "2026-07-30T15:14:08.000Z"
  }
}

About this API

Coverage & behavior

A callable implementation of the Encoded Polyline Algorithm Format — the compact ASCII string that Google Directions/Routes returns as overview_polyline, that OSRM returns for geometries=polyline and polyline6, and that Valhalla returns as shape. Paste the string in, get coordinates, GeoJSON and a length out.

This is pure computation with no dataset: nothing is bundled, fetched or looked up. The format stores each coordinate as a delta from the previous one, scaled by 10^precision and rounded to an integer, zig-zag encoded so the sign lives in the low bit, split into 5-bit chunks least-significant first, and shifted into printable ASCII by adding 63. Every route here is deterministic and synchronous — the same query returns the same bytes forever, and no route reads the clock, a seed or a random number.

Precision 5 versus precision 6 — the one thing that goes wrong

Provider Precision
Google Directions / Routes overview_polyline, @mapbox/polyline default, GraphHopper 5
OSRM geometries=polyline6, Valhalla shape 6

Read a precision-6 string at precision 5 and the coordinates overflow ±90/±180 — /decode returns a 400 that names the exact latitude and point. Read a precision-5 string at precision 6 and you get a valid-looking answer that is ten times too small, which is why /decode and /simplify attach a meta.warnings entry when every coordinate lands inside latitude ±9 and longitude ±18. That warning is a heuristic with a disclosed false-positive mode (a genuine geometry near the Gulf of Guinea trips it), never a verdict. /inspect exists to answer the question properly.

Lossiness is structural

Encoding rounds to a fixed grid, so it is not lossless:

Precision Latitude grid step Worst-case point displacement
5 1.11195 m 0.7863 m
6 0.11120 m 0.07863 m

/encode reports, per point, the value you sent, the value the grid could store, and the great-circle offsetMeters between them, plus lossyPointCount, maxOffsetMeters and roundTripsExactly. The integer is Math.round(value * 10 ** precision); JavaScript breaks exact ties toward positive infinity, where C's round() (used by OSRM) breaks them away from zero — a difference only reachable with an exact …5 tie.

Putting a polyline in a URL

Exactly eight characters of the ASCII 63–126 alphabet are illegal raw in a query component under RFC 3986 section 3.4: [ \ ] ^ ` { | }. ~, _, ? and @ are legal and are never flagged. Note that all eight are inside the alphabet, so they decode perfectly — which is exactly why they have to be escaped rather than detected as errors. Anything below ASCII 63 — space, %, &, +, =, # — can never be part of a valid polyline, so if a route sees one the string was mangled in transit, and the 400 says which character at which index. /encode and /simplify hand back a ready urlEncoded form (encodeURIComponent, which also escapes @ and ? — over-escaping is safe, under-escaping is not), and /inspect lists disallowedInQueryCharacters.

Strictness: no partial decodes, ever

A mangled string is a precise 400 on /decode and /simplify, never a best-effort coordinate list. Three independent guards:

  1. accumulation is arithmetic (chunk * 2 ** shift), never a 32-bit <</|=; JavaScript shifts are mod 32, so the textbook accumulator collapses to -1 on a string of continuation characters and returns a nonsense coordinate instead of failing;
  2. a single value spanning more than six characters is rejected — 30 bits is the most a WGS 84 delta can need, even at precision 6;
  3. every accumulated coordinate is range-checked before it can reach the response.

Guards 2 and 3 are genuinely independent: a well-formed six-character value can still be an out-of-range coordinate. Non-minimal encodings (a redundant trailing zero chunk) are accepted, reported via canonical: false and nonCanonicalGroupIndexes, and warned about — no real-world encoder emits them.

Limits, and why they are where they are

  • value and coordinates: 8,000 characters. Both caps are reachable, not decorative: 8,000 ? characters decode to 4,000 points, while a realistic 2,000-point trace at ~11 m spacing encodes to just over 8,000 characters.
  • /decode and /inspect: 2,000 points. Decoding is linear (1.08 ms for a 2,000-point request including serialization), so what binds here is response size. The reachable worst case is not a plausible trace but a deliberate one — 1,998 points that all render with six decimals and a three-digit longitude, from a value sitting exactly on the 8,000-character cap — because every coordinate is emitted twice, in coordinates and in geojson.coordinates, and meta.params echoes the whole value: 154,373 bytes, about 151 KiB compact. Adding ?pretty=true re-serializes that with two-space indent and reaches 302,472 bytes, about 295 KiB; the platform's 256 KiB budget is a compact-envelope figure and pretty is an opt-in debug flag, so this is stated rather than silently claimed to fit. (A 2,000-point staircase of short coordinates — the shape a benchmark naturally reaches for — is only 119 KiB, so do not take that for the worst case.)
  • /encode and /simplify: 1,000 points, plus one budget that is about CPU rather than bytes. /encode is linear. /simplify is not: Ramer–Douglas–Peucker degrades to O(n²) whenever the farthest point of every span sits beside one of its ends, and each point costs roughly twice as much again when the along-track projection falls off the segment and the distance has to be clamped to an endpoint. A zig-zag is the obvious shape that does this, but not the only one — a right-angle staircase that never reverses in either axis does it too, which is why the refusal below describes what the algorithm measured rather than claiming to know the shape you sent. A point cap alone cannot bound that honestly. Measured across five adversarial families (comb, near-pole decaying zig-zag, pole-crossing zig-zag, retrograde zig-zag, constant zig-zag) on the full route path including serialization: 3.0 ms at 500 points, 4.9 ms at 600, 10.9 ms at 1,000 — so a bare 1,000-point cap would leave no margin at all against the free-tier Worker's 10 ms of CPU per request. Raising toleranceMeters is not a way around that, though the reason is narrower than it might look: a span stops splitting as soon as its own measured deviation is within the tolerance, so a coarser tolerance genuinely does prune earlier — but on a constant-amplitude zig-zag every span deviates by the same amount, so the cost falls off a cliff instead of tapering. Measured on the 1,000-point zig-zag in the test suite (amplitude ≈ 22.218 m): toleranceMeters of 0, 1, 5, 10, 20, 22 and 22.21 all hit the budget, while 22.219 and above return at once — and return two points, because a tolerance at or above the deviation of the whole geometry from the straight line between its endpoints always collapses it to those endpoints. So the work is metered instead: one request may spend 150,000 spherical distance evaluations, which is enough that every geometry of up to 547 points always fits, whatever its shape (the absolute worst case for 547 points is 546 × 545 / 2 = 148,785 evaluations) and an ordinary trace is never affected — a realistic 1,000-point GPS trace needs a few thousand evaluations and 0.56 ms, a monotone 1,000-point staircase 0.80 ms. A geometry that pushes past the budget gets a precise 400 naming it, at about 4 ms, instead of being run over the platform's CPU limit; nothing is ever truncated or partially simplified. For a longer or denser geometry, use /decode, simplify it in slices, or ask your routing provider for a coarser overview first.
  • All four routes are compute, not list, deliberately: count caps at 100 and truncating a geometry to 100 points would be a silently wrong answer. The price is that format=ndjson and format=csv are unavailable on this endpoint. fields, exclude, pretty and unwrap all work.

Distances

lengthMeters, offsetMeters and maxDeviationMeters are great-circle values on the same mean-Earth sphere as /api/geodesy, from the same shared helper, with the IUGG mean radius 6,371,008.8 metres exposed as radiusMeters on every response. They are not road distance, routed distance, ellipsoidal (Vincenty/Karney) distance or terrain distance. Trigonometric results are rounded to millimetres (offsets to a tenth of a millimetre, since they are sub-metre by construction) — that is a cross-engine-stability measure, not a claim of millimetre accuracy.

/simplify

Ramer–Douglas–Peucker with spherical cross-track distance, clamped to the nearer endpoint when the along-track projection leaves the segment (the unclamped infinite-great-circle variant is a common library bug and keeps different points). It guarantees every dropped point sits within toleranceMeters of the chord that replaced it, and returns the measured maxDeviationMeters so you can check — that figure is rounded to the nearest millimetre, so if you set a tolerance to within half a millimetre of an actual deviation it can read up to 0.5 mm above the tolerance you asked for; the guarantee is on the geometry, not on the rounded report. It does not preserve topology — an aggressively simplified loop can self-intersect — it never snaps to roads, and it is not idempotent: re-simplifying the output at the same tolerance can legitimately remove more points, because the recursion then measures against the new chords.

What this endpoint deliberately does not do

No routing, map matching, road snapping or traffic. No ellipsoidal distance. 2D only — a stream carrying a third value per vertex surfaces as an odd value count, not as elevation support. Precision 5 and 6 only. This format only: HERE's Flexible Polyline and OSRM's geometries=geojson are different things. No MultiLineString or Polygon output — one encoded polyline is one line. No RFC 7946 section 5.2 antimeridian-aware bbox narrowing, so a geometry crossing the dateline yields a box spanning most of the globe, by design. GET only, hence the input caps; there is no POST route and no upload.

The four specification pages behind this endpoint were last read end to end on 2026-07-30.

Use it for

  • Paste the overview_polyline from a Google Directions or Routes response into a URL and see the actual coordinates
  • Work out whether a routing provider handed you precision 5 or precision 6 before wiring its geometry into a map
  • Turn an encoded route into a GeoJSON LineString you can drop straight into geojson.io, Leaflet or a Mapbox source
  • Cut a dense GPS trace down to a map-sized geometry with a stated maximum deviation in metres

Frequently asked questions

Why does my encoded polyline decode to coordinates in the middle of the ocean?

Almost always the wrong precision. Google's format uses 5 decimal places; OSRM's polyline6 and every Valhalla shape use 6. Read a precision-6 string at precision 5 and the values overflow ±90/±180 — this endpoint returns a 400 that says so. Read a precision-5 string at precision 6 and you silently get coordinates ten times too small, which Valhalla's own docs warn land "commonly, in the middle of an ocean". Use /api/polyline/inspect to see which precision the string is consistent with.

How do I put an encoded polyline in a URL query string?

Percent-encode it. Exactly eight characters of the alphabet are illegal raw in a query component under RFC 3986 section 3.4: [ \ ] ^ ` { | }. ~, _, ? and @ are safe as-is. %, &, +, =, # and space are all below the alphabet's ASCII 63 floor, so if the endpoint sees one the string was mangled in transit — and it says which character at which index. /api/polyline/encode returns a ready urlEncoded form.

Is encoding a polyline lossless?

No. Precision 5 snaps every coordinate to a grid about 1.11 m apart in latitude (0.11 m at precision 6), so a point can move up to about 0.79 m at precision 5. /encode reports the exact rounded value and the great-circle offsetMeters for every point, plus roundTripsExactly.

Can this decode an OSRM polyline6 geometry or a Valhalla shape?

Yes — pass precision=6. OSRM's geometries=polyline6 and Valhalla's shape use the identical algorithm with a 1e-6 scale factor. Google Directions/Routes overview_polyline and GraphHopper's default use precision=5.

Does /simplify guarantee every original point stays within the tolerance?

Yes. Ramer–Douglas–Peucker only drops a point once its spherical distance to the retained chord is within toleranceMeters, and the response returns the measured maxDeviationMeters so you can check — rounded to the nearest millimetre, so it can read up to 0.5 mm above the tolerance you asked for even though the geometry itself is inside it. It does not preserve topology — an aggressively simplified loop can self-intersect — and it never snaps to roads.

Standards & references