open-distance

API

Response-compatible with Google's legacy Distance Matrix API, plus two extra arrays exposing per-endpoint geocode confidence.

Endpoint

GET https://open-distance.com/maps/api/distancematrix/json
    ?origins=<A>|<B>|...
    &destinations=<C>|<D>|...
    &units=imperial|metric        # default imperial
    &mode=driving                  # only mode supported

Each origin/destination is either an address string or a lat,lng pair. Multiple endpoints separated by |. Up to 100 elements (origins × destinations) per request.

Response

{
  "destination_addresses": ["…canonical or raw input…"],
  "destination_matches":   ["rooftop" | "interpolated" | "coords" | ""],
  "origin_addresses":      ["…"],
  "origin_matches":        ["…"],
  "rows": [
    { "elements": [
      { "status": "OK" | "NOT_FOUND" | "ZERO_RESULTS",
        "distance": { "text": "5.4 mi", "value": 8690 },
        "duration": { "text": "11 mins", "value": 660 } }
    ] }
  ],
  "status": "OK"
}

The confidence indicator

Each endpoint comes back with a match value telling you how it was located:

ValueMeansTypical accuracy
rooftopExact mapped point (NAD or OpenAddresses rooftop dataset)Building-level
interpolatedOSM addr-tagged node, or TIGER segment interpolation~30–100 m
coordsCaller-supplied lat,lng directlyWhatever the caller gave us
""Geocode failed; raw input is echoedElement returns NOT_FOUND

Caching

Successful responses send Cache-Control: public, max-age=3600, so Cloudflare's edge cache absorbs identical queries for an hour. The /coverage endpoint sends max-age=86400. You're welcome to cache responses in your own backend for as long as you like.

Rate limits

Per-IP rate limits on the hosted deployment:

There is no paid tier. The hosted instance is intentionally a free shared resource for casual use. If you need higher limits, self-host on your own Cloudflare account — limits are env-var configurable per tier, or set all three to 0 for an unlimited deployment.

Response headers

Every API response (both 200 success and 429 rate-limited) carries the following headers so you can self-throttle without a probe round-trip:

HeaderWhat it means
X-RateLimit-Limit-SecondConfigured per-second limit (25 by default)
X-RateLimit-Remaining-SecondRequests left in the current 1-second window
X-RateLimit-Reset-SecondSeconds until the 1-second window rolls (always 1)
X-RateLimit-Limit-HourConfigured per-hour limit (500 by default)
X-RateLimit-Remaining-HourRequests left in the current hour bucket
X-RateLimit-Reset-HourSeconds until the hour bucket rolls
X-RateLimit-Limit-DayConfigured per-day limit (10,000 by default)
X-RateLimit-Remaining-DayRequests left in the current day bucket
X-RateLimit-Reset-DaySeconds until the day bucket rolls
RateLimit-LimitIETF draft header: limit of the tightest tier
RateLimit-RemainingRemaining of the tightest tier
RateLimit-ResetSeconds until the tightest tier resets

When you hit the limit

You get HTTP 429 with "status":"OVER_QUERY_LIMIT", a Retry-After header (seconds until the offending tier rolls), and X-RateLimit-Tier = sec / hour / day indicating which bucket overflowed. The response body includes a link back to the source so you can self-host with your own limits.

Deviations from the legacy Distance Matrix shape

Other endpoints

PathWhat it returns
/healthzLiveness + sentinel-tile probe
/coverageVersion, data sources, supported match values

Self-hosting

The whole stack (Cloudflare Worker + R2 + D1 + KV) costs ~$5–10/month for the entire continental US. Source: github.com/kurtpayne/open-distance. ./refresh.sh handles the data pipeline from a clean clone.