APIs
Elevation API: points
Point lookups, batch lookups, and path sampling — parameters, response shapes, and source-selection behavior.
Elevations computed from the best available source at each location, returned in meters. All endpoints require an API key.
| Endpoint | Methods | Input |
|---|---|---|
/v1/elevation/point |
GET |
lat, lon |
/v1/elevation/points |
GET, POST |
latlons — pipe-separated lat,lon|lat,lon|… |
/v1/elevation/sample |
GET, POST |
path (same syntax, ≥ 2 vertices) + samples=N |
POST variants accept a JSON body with the same field names as the query
parameters (the body wins on conflict). In a body, latlons/path may be a
pipe-separated string, a list of "lat,lon" strings, or a list of
[lat, lon] pairs.
sample computes N evenly spaced points along the geodesic polyline
(endpoints included), then behaves exactly like points — useful for
elevation profiles along a route.
Examples
Single point:
curl "https://api.darly.io/v1/elevation/point?lat=45.51&lon=-73.56" \
-H "X-API-Key: $DARLY_API_KEY"import os
import requests
resp = requests.get(
"https://api.darly.io/v1/elevation/point",
params={"lat": 45.51, "lon": -73.56},
headers={"X-API-Key": os.environ["DARLY_API_KEY"]},
)
print(resp.json()["result"]["elevation"])const resp = await fetch(
"https://api.darly.io/v1/elevation/point?lat=45.51&lon=-73.56",
{ headers: { "X-API-Key": process.env.DARLY_API_KEY } },
)
const { result } = await resp.json()
console.log(result.elevation)Batch (each point in a batch counts as one lookup against your limits):
curl -X POST "https://api.darly.io/v1/elevation/points" \
-H "X-API-Key: $DARLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"latlons": "45.51,-73.56|49.28,-123.12|19.43,-99.13"}'import os
import requests
resp = requests.post(
"https://api.darly.io/v1/elevation/points",
json={"latlons": [[45.51, -73.56], [49.28, -123.12], [19.43, -99.13]]},
headers={"X-API-Key": os.environ["DARLY_API_KEY"]},
)
for r in resp.json()["results"]:
print(r["lat"], r["lon"], r["elevation"])const resp = await fetch("https://api.darly.io/v1/elevation/points", {
method: "POST",
headers: {
"X-API-Key": process.env.DARLY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
latlons: [[45.51, -73.56], [49.28, -123.12], [19.43, -99.13]],
}),
})
const { results } = await resp.json()Elevation profile — 50 samples along a leg of Mount Rainier:
curl -G "https://api.darly.io/v1/elevation/sample" \
--data-urlencode "path=46.85,-121.76|46.79,-121.74" \
--data-urlencode "samples=50" \
-H "X-API-Key: $DARLY_API_KEY"import os
import requests
resp = requests.get(
"https://api.darly.io/v1/elevation/sample",
params={"path": "46.85,-121.76|46.79,-121.74", "samples": 50},
headers={"X-API-Key": os.environ["DARLY_API_KEY"]},
)
profile = [r["elevation"] for r in resp.json()["results"]]Shared options
All point endpoints accept:
| Param | Values | Default | Behavior |
|---|---|---|---|
surface |
dtm, dsm |
dtm |
Terrain (bare earth) or surface (includes buildings/canopy). Only collections carrying the requested asset are considered. |
vertical_datum |
egm2008, wgs84_ellipsoid |
egm2008 |
egm2008 = orthometric height above the EGM2008 geoid (what most people mean by "elevation above sea level"). wgs84_ellipsoid = height above the WGS84 ellipsoid (what raw GPS reports). Converted from each source's native datum via geoid grids. |
interpolation |
bilinear, nearest |
bilinear |
Ignored when radius > 0. Bilinear degrades to nearest where a full 2×2 neighborhood is unavailable (raster edges, nodata neighbors). |
radius |
meters, 0–100 | 0 |
0 samples at the point. Otherwise: mean of valid cells whose center falls within the circle (nodata excluded). A radius smaller than the pixel degrades to the containing cell. |
collections |
ordered STAC collection IDs | omitted | Comma-separated (JSON array on POST). IDs are the STAC collection IDs from the catalog — list them via GET /collections. Only these collections are considered, and order is priority: the first collection answers wherever it has valid data; each later one is only consulted where every earlier one came up empty (nodata or no coverage). Useful where overlapping collections exist and you know which you want. Omitted: all collections your plan allows, ranked finest resolution first — lidar naturally beats the 30 m globals. Unknown and not-allowed IDs return the same error; so does an ID without the requested surface. |
How the source is chosen
For each point:
- Find candidate items intersecting the point (filtered by your allowed
collections and the requested
surface). - Rank by collection resolution (finest first), then item date (newest
first) within a collection. An explicit
collectionslist replaces the resolution ranking with your order — first in the list is tried first (item date still breaks ties within a collection). - Read the best candidate. Nodata falls through to the next candidate (voids, coastal edges, tile overlap) — and to the global 30 m datasets last.
- Nothing valid anywhere →
elevation: null,collection: null; the request still succeeds.
There is no fake bathymetry: open ocean is null, not 0.
Response shape
point returns result (single object); points/sample return results
(array, same order as the input):
{
"status": "OK",
"result": {
"lat": 45.51,
"lon": -73.56,
"elevation": 36.42,
"collection": "can-nrcan-hrdem-1m",
"vertical_datum": "egm2008",
"surface": "dtm",
"resolution": 1.0
}
}resolution is the source dataset's ground sampling distance in meters — use
it to judge how much to trust the value.
Caps and errors
- Up to 512 points per request on paid plans, 100 on Free (
latlonsentries orsamples), up to 25pathvertices,radiusup to 100 m. - Validation failures, including unknown query/body fields, return
400{"status": "INVALID_REQUEST", "error": "…"}. - Missing key →
401; per-minute and monthly limit errors →429. See rate limits & quotas for bodies and headers.