# Elevation API: raster extracts

> Clip a DEM as a cloud-optimized GeoTIFF — elevation or hillshade, with output size or resolution, projection, and provenance options.

`GET /v1/elevation/raster` clips a DEM over your bounding box and returns a
**cloud-optimized GeoTIFF** — either float32 elevation in meters (`-9999`
nodata, the default) or, with `render=hillshade`, a uint8 shaded-relief image
— composited from the best available sources exactly like
[point lookups](/docs/elevation). Requires an [API key](/docs/authentication);
response bytes count against your monthly [data-out quota](/docs/limits).

```bash cURL
curl -G "https://api.darly.io/v1/elevation/raster" \
  --data-urlencode "bbox_left=-121.82" \
  --data-urlencode "bbox_bottom=46.75" \
  --data-urlencode "bbox_right=-121.65" \
  --data-urlencode "bbox_top=46.90" \
  --data-urlencode "width_px=1024" \
  --data-urlencode "height_px=1024" \
  --data-urlencode "projection=utm" \
  -H "X-API-Key: $DARLY_API_KEY" \
  -o rainier-dtm.tif
```

```python Python
import os
import requests

resp = requests.get(
    "https://api.darly.io/v1/elevation/raster",
    params={
        "bbox_left": -121.82, "bbox_bottom": 46.75,
        "bbox_right": -121.65, "bbox_top": 46.90,
        "width_px": 1024, "height_px": 1024,
        "projection": "utm",
    },
    headers={"X-API-Key": os.environ["DARLY_API_KEY"]},
)
resp.raise_for_status()
print("sources:", resp.headers["x-data-sources"])
with open("rainier-dtm.tif", "wb") as f:
    f.write(resp.content)
```

```javascript JavaScript
const params = new URLSearchParams({
  bbox_left: "-121.82", bbox_bottom: "46.75",
  bbox_right: "-121.65", bbox_top: "46.90",
  width_px: "1024", height_px: "1024",
  projection: "utm",
})
const resp = await fetch(
  `https://api.darly.io/v1/elevation/raster?${params}`,
  { headers: { "X-API-Key": process.env.DARLY_API_KEY } },
)
const tiff = Buffer.from(await resp.arrayBuffer())
```

The result opens directly in QGIS, GDAL, and rasterio.

## Parameters

| Param | Values | Default | Behavior |
|---|---|---|---|
| `bbox_left`, `bbox_bottom`, `bbox_right`, `bbox_top` | numbers | required | Extract bounds, in `bbox_projection` coordinates. Antimeridian-crossing bboxes are rejected. |
| `bbox_projection` | `epsg:NNNN` | `epsg:4326` | CRS of the bbox parameters. |
| `projection` | `epsg:NNNN`, `utm` | `epsg:4326` | Output raster CRS. `utm` picks the UTM zone of the bbox center. |
| `width_px` + `height_px` | 10–2,500 each | — | Exact output size; output bounds match the bbox exactly. |
| `resolution_m` | 0.5–1,000 | — | Pixel size in meters (converted to degrees at the bbox center latitude for geographic output CRS). Bounds are buffered right/down to whole pixels. |
| `resolution_x` + `resolution_y` | > 0 | — | Pixel size in output-projection units. Bounds buffered like `resolution_m`. |
| `source_mask` | `true`, `false` | `false` | Adds band 2 (`source_index`): per-pixel source collection, mapped by the `darly-source-map` tag (0 = nodata). Not valid with `render=hillshade`. |
| `output_profile` | `default`, `zstd` | `default` | COG compression: DEFLATE, or ZSTD (smaller and faster, needs newer readers). |
| `render` | `elevation`, `hillshade` | `elevation` | Output type. `hillshade` returns a single uint8 shaded-relief band (1–255, `0` = nodata) instead of elevation values. Mutually exclusive with `source_mask`. |
| `azimuth`, `altitude`, `z_factor` | 0–360, 0–90, >0–100 | 315, 45, 1 | Hillshade sun direction (°), sun angle above horizon (°), and vertical exaggeration. Only apply when `render=hillshade`. |
| `surface` | `dtm`, `dsm` | `dtm` | Terrain (bare earth) or surface (includes buildings/canopy). Only collections carrying the requested asset are composited. |
| `vertical_datum` | `egm2008`, `wgs84_ellipsoid` | `egm2008` | Output vertical datum. `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). Every source is converted **before** compositing. |
| `interpolation` | `bilinear`, `nearest` | `bilinear` | Resampling used to warp each source onto the output grid. Bilinear for smooth terrain; nearest preserves original cell values. |
| `collections` | ordered STAC collection IDs | omitted | Comma-separated, priority order: **the first collection ends up on top of the mosaic**; later ones only fill its gaps. IDs are the STAC collection IDs from the [catalog](/docs/stac) — list them via `GET /collections`. Omitted: all collections your plan allows, finest resolution first. Unknown and not-allowed IDs return a `400`. |

Provide **exactly one** sizing spec: `width_px`+`height_px`, `resolution_m`,
or `resolution_x`+`resolution_y`. The point-lookup `radius` parameter does
not apply to rasters.

## How the mosaic is built

1. Collections are ranked like point lookups: your allowed collections,
   filtered by `surface`, finest resolution first — or exactly the
   `collections` you listed, in your order. **The first collection in the
   list ends up on top of the mosaic.**
2. Sources are composited onto the output grid in rank order (newest item
   first within a collection); each later source only fills pixels the
   earlier ones left as nodata — it never overwrites.
3. Each source is converted to the requested `vertical_datum` **before**
   compositing, which removes the systematic vertical offset between
   datasets. Pixels a
   source can't datum-shift (e.g. NAVD88 outside CONUS — see
   [coverage](/docs/coverage)) fall through to the next source.
4. There is **no blending or feathering** at dataset seams — remaining seams
   are real differences between surveys, not artifacts we hide. Provenance is
   exposed instead: the `x-data-sources` response header, the
   `darly-collections` / `darly-source-map` GeoTIFF tags, and the optional
   `source_mask` band.
5. A very large bbox over a dense lidar collection automatically falls
   through to the next-ranked collection instead of opening thousands of
   tiles. If you pinned a single collection explicitly, there is nothing to
   fall through to and you get a `400` asking for a smaller bbox instead.

A bbox no collection covers still returns `200` with an all-nodata raster —
the raster equivalent of `elevation: null`.

## Hillshade

`render=hillshade` shades the composited terrain into a single **uint8** band
(1–255, `0` = nodata) instead of returning elevation values — a ready-to-map
shaded relief. The mosaic is built identically (same ranking, datum
normalization, and `surface` selection); the shading is computed from the
result with correct metric ground spacing, so it is accurate even for the
default WGS84 output. Tune the light with `azimuth`, `altitude`, and
`z_factor`; the defaults (315° / 45° / 1) match the Explorer's shaded-relief
backdrop. `source_mask` is not available in this mode.

```bash cURL
curl -G "https://api.darly.io/v1/elevation/raster" \
  --data-urlencode "bbox_left=-121.82" \
  --data-urlencode "bbox_bottom=46.75" \
  --data-urlencode "bbox_right=-121.65" \
  --data-urlencode "bbox_top=46.90" \
  --data-urlencode "resolution_m=10" \
  --data-urlencode "render=hillshade" \
  -H "X-API-Key: $DARLY_API_KEY" \
  -o rainier-hillshade.tif
```

Because uint8 is a quarter the size of float32, a hillshade costs
correspondingly less against your data-out quota than the same extract in
elevation mode.

## Caps

- Output size: **2,500 px per side** (Pro/Advanced), **512 px** on Free.
- Bbox extent: **100 km** per side (geodesic).
- Per-minute raster calls are limited separately from point lookups — see
  [rate limits & quotas](/docs/limits).

Worst case (2,500², float32) is ~25 MB before compression; responses are
returned directly, no signed-URL indirection.

## Raster vs. downloads

The raster endpoint reprojects, mosaics, and normalizes datums for you, and
meters the actual response bytes. If you want the **original source files**
untouched, use the STAC catalog: source-agency assets are direct public
URLs, and Darly-hosted assets are downloadable on paid plans via
[COG downloads](/docs/downloads).
