# Elevation & Terrain Data Scraper - Altitude by Coordinates (`ninhothedev/elevation-scraper`) Actor

$0.5/1K 🔥 Elevation scraper! Terrain height for any coordinates, grid or route from 11 datasets. No key. JSON, CSV, Excel or API in seconds. Build hiking profiles & GIS pipelines ⚡

- **URL**: https://apify.com/ninhothedev/elevation-scraper.md
- **Developed by:** [ninhothedev](https://apify.com/ninhothedev) (community)
- **Categories:** Developer tools, Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Elevation & Terrain Data Scraper — altitude for any coordinates, no API key

Get the **terrain elevation (altitude) of any latitude/longitude on Earth** — and the depth of any point in the ocean — without an API key, without registration and without a Google Elevation API bill. Feed it a list of coordinates, a bounding box, or a route, and get back clean elevation data in metres and feet.

Powered by the free public [OpenTopoData](https://www.opentopodata.org/) instance, which serves eleven verified digital elevation models (DEMs), from global 30 m SRTM coverage down to 8 m New Zealand LiDAR and European sea-floor bathymetry.

- **No API key, no account, no OAuth.** Just coordinates in, elevation out.
- **Three modes:** single points, bounding-box grids, and interpolated route profiles.
- **Honest nulls.** Outside a dataset's coverage you get `elevation_m: null` — never a fake `0`.
- **~$0.50 per 1,000 elevation points** on the Apify platform.

***

### What you get

One dataset item per coordinate:

| Field | Description |
|---|---|
| `latitude` | Latitude of the sampled point (decimal degrees) |
| `longitude` | Longitude of the sampled point (decimal degrees) |
| `elevation_m` | Elevation above sea level in metres. Negative for ocean depth. `null` when the dataset has no data there |
| `elevation_ft` | Same value in feet, rounded to 0.1. `null` when `elevation_m` is `null` |
| `dataset` | The DEM that answered (e.g. `srtm30m`) |
| `in_coverage` | `true` when the dataset actually returned a height for this coordinate |
| `point_index` | Position of the point in the request order — keeps grids and profiles ordered |
| `path_distance_km` | Cumulative great-circle distance from the first point. Path mode only, otherwise `null` |
| `source` | Always `opentopodata` |
| `scraped_at` | UTC ISO-8601 timestamp of the run |

#### Sample output

```json
[
  {
    "latitude": 47.421,
    "longitude": 10.9855,
    "elevation_m": 2937.0,
    "elevation_ft": 9635.8,
    "dataset": "srtm30m",
    "in_coverage": true,
    "point_index": 0,
    "path_distance_km": null,
    "source": "opentopodata",
    "scraped_at": "2026-07-28T18:10:03+00:00"
  },
  {
    "latitude": -30.0,
    "longitude": -40.0,
    "elevation_m": null,
    "elevation_ft": null,
    "dataset": "srtm30m",
    "in_coverage": false,
    "point_index": 1,
    "path_distance_km": null,
    "source": "opentopodata",
    "scraped_at": "2026-07-28T18:10:03+00:00"
  }
]
```

The second row is the South Atlantic. SRTM is a *land* elevation model, so it correctly reports `null` — not `0`. **Sea level and "no data" are different things, and this Actor never conflates them.** If you need ocean depth, pick a bathymetry dataset such as `gebco2020` or `mapzen`.

***

### Three modes

#### 1. `points` — an explicit coordinate list

Give it `["52.5200,13.4050", "47.4210,10.9855"]` and it returns one item per pair. Ideal for enriching an existing list of addresses, sensor sites, store locations or GPS fixes.

#### 2. `grid` — a bounding box + step

Set `bboxSouth`, `bboxNorth`, `bboxWest`, `bboxEast` and `gridStep` (in degrees; `0.01` ≈ 1.1 km of latitude). The Actor builds the lat/lon grid itself and **caps it against `maxItems` before a single request goes out**, so a careless bbox can never turn into a runaway run. Perfect for terrain rasters, viewshed inputs and slope analysis.

#### 3. `path` — waypoints interpolated into a profile

Give two or more waypoints in `coordinates` and a `pathSamples` count. The Actor resamples the polyline into evenly spaced points *by ground distance* (haversine, not degrees) and adds a cumulative `path_distance_km` to every row — an elevation profile you can plot straight away.

***

### Datasets and their coverage (all verified live)

| Dataset | Resolution | Coverage |
|---|---|---|
| `srtm30m` | ~30 m | Global land, 60°N–56°S (NASA SRTM GL1) — **default** |
| `srtm90m` | ~90 m | Global land, 60°N–56°S (NASA SRTM GL3) |
| `aster30m` | ~30 m | Global land, 83°N–83°S (ASTER GDEM v3) |
| `mapzen` | ~30 m | Global land **and** ocean, near-global composite (Mapzen Terrarium) |
| `gebco2020` | ~450 m | Global land **and** ocean bathymetry (GEBCO 2020) |
| `etopo1` | ~1.8 km | Global land and ocean, coarse (NOAA ETOPO1) |
| `eudem25m` | 25 m | Europe (EEA member states) only (Copernicus EU-DEM v1.1) |
| `ned10m` | ~10 m | Continental USA, Hawaii, parts of Alaska only (USGS NED / 3DEP) |
| `bkg200m` | 200 m | Germany only (BKG DGM200) |
| `nzdem8m` | 8 m | New Zealand only (LINZ) |
| `emod2018` | varies | European seas only — bathymetry, negative depths (EMODnet 2018) |

Rule of thumb: **use a regional dataset when your points are inside its region** (it is far more precise), and a global one otherwise. Querying a regional dataset outside its region returns `elevation_m: null` with `in_coverage: false`. That is correct behaviour, not a failure.

***

### Rate limits — read this before planning a big run

This Actor talks to the **free public OpenTopoData instance**. Verified live against the running service (`x-opentopodata-version: 1.9.0`):

- **100 locations per request.** Sending 101 returns HTTP 400 with `"Too many locations provided (101), the limit is 100."` — confirmed for both GET and POST. The Actor chunks your coordinates into batches of exactly 100.
- **About 1 call per second.** Three back-to-back calls produced an HTTP 429 on the third. The Actor waits ≥1.1 s between batches and backs off exponentially on 429.
- **1,000 calls per day**, per the operator's published fair-use policy. Note: the public instance exposes **no** `X-RateLimit-*` or `Retry-After` headers, so the daily counter cannot be read from a response — you only find out by receiving a 429.

Practically, 1,000 calls × 100 locations = **up to 100,000 elevation points per day** on the free public instance. If you hit the quota, the run fails with an explicit rate-limit message rather than silently returning nothing.

**Need more?** OpenTopoData is open source and self-hostable. Running your own instance removes the 1 call/second and 1,000 calls/day limits entirely (you only pay for your own server). This Actor's request logic is unchanged against a self-hosted instance.

***

### Use cases

- **Route & hiking elevation profiles.** Path mode turns a GPX-style waypoint list into a plottable profile with cumulative distance and altitude gain.
- **Solar and wind siting.** Grid mode gives you the terrain raster you need for shading, slope, aspect and wind-exposure modelling across a candidate site.
- **Flood risk modelling.** Sample elevations across a floodplain, compare heights against river gauge levels, and rank addresses by height above a reference datum.
- **GIS and data pipelines.** Enrich any table of coordinates — property listings, cell towers, agricultural plots, delivery stops — with a height column, with no key management.

***

### Input example

```json
{
  "mode": "path",
  "coordinates": ["47.4210,10.9855", "47.5000,11.1000"],
  "dataset": "srtm30m",
  "pathSamples": 100,
  "maxItems": 500
}
```

Grid example:

```json
{
  "mode": "grid",
  "bboxSouth": 52.45,
  "bboxNorth": 52.55,
  "bboxWest": 13.30,
  "bboxEast": 13.45,
  "gridStep": 0.01,
  "dataset": "eudem25m",
  "maxItems": 500
}
```

***

### Pricing

Pay-per-result: **about $0.50 per 1,000 elevation points**. A 500-point run costs roughly $0.25. Because up to 100 points ride in a single upstream call, even large grids finish quickly and cheaply.

***

### Related Actors

- [Geocoding Scraper](https://apify.com/ninhothedev/geocoding-scraper) — turn addresses into coordinates, then feed them straight into this Actor.
- [Reverse Geocoding Scraper](https://apify.com/ninhothedev/reverse-geocoding-scraper) — turn coordinates back into addresses.
- [OpenStreetMap Notes Scraper](https://apify.com/ninhothedev/openstreetmap-notes-scraper) — community map notes for any area.
- [Airports & Airlines Scraper](https://apify.com/ninhothedev/airports-airlines-scraper) — airport, runway and airline reference data.

***

### FAQ

**Do I need an API key?** No. There is nothing to sign up for.

**Why is my elevation `null`?** The chosen dataset has no data at that coordinate — usually because it is a regional DEM and your point is outside its region, or a land-only DEM and your point is over water. Switch to a global dataset (`srtm30m`, `aster30m`) or a bathymetric one (`gebco2020`, `mapzen`).

**Why not just return 0 for missing data?** Because 0 m is a real, meaningful elevation (sea level). Substituting 0 for "unknown" silently corrupts every downstream flood, slope or profile calculation. This Actor keeps them strictly distinct via `elevation_m: null` and `in_coverage: false`.

**How accurate is it?** Depends on the DEM. SRTM 30 m has a vertical accuracy of roughly ±16 m; regional models like `ned10m`, `eudem25m` and `nzdem8m` are considerably better. Cross-checking two datasets for the same point is a cheap accuracy sanity check.

**Can I get ocean depth?** Yes — use `gebco2020`, `mapzen` or `etopo1` globally, or `emod2018` for European seas. Depths come back as negative `elevation_m`.

**Data source and licensing.** Elevation values come from public DEMs (NASA, USGS, ESA/Copernicus, GEBCO, BKG, LINZ, EMODnet) served via OpenTopoData. Check each dataset's own attribution requirements before redistributing.

# Actor input Schema

## `mode` (type: `string`):

How the coordinates to look up are produced. 'points' uses your explicit coordinate list, 'grid' generates a lat/lon grid inside a bounding box, 'path' interpolates your waypoints into an evenly spaced elevation profile.

## `coordinates` (type: `array`):

List of 'lat,lon' pairs, e.g. '52.5200,13.4050'. Used in points mode as the exact lookup list, and in path mode as the ordered waypoints of the route. Ignored in grid mode.

## `dataset` (type: `string`):

Which digital elevation model to query. Global datasets cover the whole planet; regional ones are far more precise but return null outside their region. Bathymetry datasets also give negative values for ocean depth.

## `bboxSouth` (type: `number`):

Grid mode only: southern edge of the bounding box, in decimal degrees (-90 to 90).

## `bboxNorth` (type: `number`):

Grid mode only: northern edge of the bounding box, in decimal degrees (-90 to 90).

## `bboxWest` (type: `number`):

Grid mode only: western edge of the bounding box, in decimal degrees (-180 to 180).

## `bboxEast` (type: `number`):

Grid mode only: eastern edge of the bounding box, in decimal degrees (-180 to 180).

## `gridStep` (type: `number`):

Grid mode only: spacing in degrees between generated grid points. 0.01 deg is roughly 1.1 km of latitude. Smaller steps mean far more points, so the grid is capped by maxItems before any request is sent.

## `pathSamples` (type: `integer`):

Path mode only: how many evenly spaced samples to interpolate along the route defined by your waypoints. More samples give a smoother elevation profile.

## `maxItems` (type: `integer`):

Hard cap on how many elevation points this run produces. Also caps the generated grid or path before any API request is made.

## Actor input object example

```json
{
  "mode": "points",
  "coordinates": [
    "52.5200,13.4050",
    "47.4210,10.9855"
  ],
  "dataset": "srtm30m",
  "bboxSouth": 52.45,
  "bboxNorth": 52.55,
  "bboxWest": 13.3,
  "bboxEast": 13.45,
  "gridStep": 0.01,
  "pathSamples": 50,
  "maxItems": 500
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "coordinates": [
        "52.5200,13.4050",
        "47.4210,10.9855"
    ],
    "bboxSouth": 52.45,
    "bboxNorth": 52.55,
    "bboxWest": 13.3,
    "bboxEast": 13.45,
    "gridStep": 0.01,
    "pathSamples": 50,
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("ninhothedev/elevation-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "coordinates": [
        "52.5200,13.4050",
        "47.4210,10.9855",
    ],
    "bboxSouth": 52.45,
    "bboxNorth": 52.55,
    "bboxWest": 13.3,
    "bboxEast": 13.45,
    "gridStep": 0.01,
    "pathSamples": 50,
    "maxItems": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("ninhothedev/elevation-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "coordinates": [
    "52.5200,13.4050",
    "47.4210,10.9855"
  ],
  "bboxSouth": 52.45,
  "bboxNorth": 52.55,
  "bboxWest": 13.3,
  "bboxEast": 13.45,
  "gridStep": 0.01,
  "pathSamples": 50,
  "maxItems": 500
}' |
apify call ninhothedev/elevation-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ninhothedev/elevation-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/eQXjv1F1dQyArz0nj/builds/z1I9KqRufSN4bmfUC/openapi.json
