# 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](/docs/authentication).

| 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:

```bash cURL
curl "https://api.darly.io/v1/elevation/point?lat=45.51&lon=-73.56" \
  -H "X-API-Key: $DARLY_API_KEY"
```

```python Python
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"])
```

```javascript JavaScript
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](/docs/limits)):

```bash cURL
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"}'
```

```python Python
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"])
```

```javascript JavaScript
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:

```bash cURL
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"
```

```python Python
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](/docs/stac) — 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:

1. Find candidate items intersecting the point (filtered by your allowed
   collections and the requested `surface`).
2. Rank by collection resolution (finest first), then item date (newest
   first) within a collection. An explicit `collections` list replaces the
   resolution ranking with your order — first in the list is tried first
   (item date still breaks ties within a collection).
3. Read the best candidate. Nodata falls through to the next candidate
   (voids, coastal edges, tile overlap) — and to the global 30 m datasets
   last.
4. 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):

```json Response
{
  "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 (`latlons`
  entries or `samples`), up to **25 `path` vertices**, `radius` up 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](/docs/limits) for bodies and headers.
