API Documentation

Quick Start

Add Terratile terrain to a CesiumJS viewer in a few lines. Replace YOUR_API_KEY with the key from your dashboard.

const viewer = new Cesium.Viewer("cesiumContainer");

viewer.scene.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  "https://terrain.terratile.io?key=YOUR_API_KEY",
  { requestVertexNormals: true }
);

The provider fetches layer.json for metadata, then streams quantized-mesh tiles as the camera moves.

Authentication

Every request requires an API key. You can get one by signing up and visiting the dashboard. Keys use the prefix tt_live_.

Query parameter

GET https://terrain.terratile.io/layer.json?key=tt_live_...

Request header

X-API-Key: tt_live_...

If both are present the query parameter takes precedence.

CLI Authentication

Terratile supports a device authorization flow for terminals, scripts, and AI agents. No browser automation or credential handling required.

One-liner setup

curl -sL https://terratile.io/auth/cli.sh | sh

This opens a browser for GitHub sign-in, then prints your API key directly to the terminal.

Manual device flow

For programmatic control (CLI tools, IDE plugins, CI pipelines), use the device flow endpoints directly:

# 1. Start the device flow
curl -s -X POST https://terratile.io/auth/device \
  -H "Content-Type: application/json" -d '{}'

# Response:
# {
#   "device_code": "a1b2c3...",
#   "user_code": "ABCD-1234",
#   "verification_uri": "https://terratile.io/device",
#   "expires_in": 600,
#   "interval": 5
# }

# 2. Direct the user to open verification_uri and enter user_code
#    (or open verification_uri?code=ABCD-1234 directly)

# 3. Poll for the token
curl -s "https://terratile.io/auth/device/token?device_code=a1b2c3..."

# While waiting:  { "status": "pending" }           (HTTP 202)
# On success:     { "status": "authorized", "api_key": "tt_live_..." }
# On expiry:      { "error": "Code expired or invalid" }  (HTTP 404)

CLI plan upgrade

If you're hitting rate limits (HTTP 429), upgrade directly from the terminal:

curl -sL https://terratile.io/auth/cli.sh | sh -s upgrade starter

Or with the device flow endpoints:

# Start upgrade flow
curl -s -X POST https://terratile.io/auth/device \
  -H "Content-Type: application/json" \
  -d '{"plan": "starter"}'

# User signs in with GitHub, completes Stripe checkout in browser

# Poll for confirmation
curl -s "https://terratile.io/auth/device/token?device_code=..."
# { "status": "completed", "tier": "starter" }

Valid plan values: starter, production, business.

Endpoints

Terrain tile

GET https://terrain.terratile.io/{z}/{x}/{y}.terrain?key=KEY

Returns a binary quantized-mesh tile, gzip-encoded. z is the zoom level, x and y are tile coordinates. Vertex normals are included when requested via CesiumTerrainProvider.

Layer metadata

GET https://terrain.terratile.io/layer.json?key=KEY

Returns a JSON document describing available zoom levels, tile scheme, and extensions. CesiumJS fetches this automatically when you create a terrain provider.

Response details

Header Value
Content-Type application/octet-stream (tiles) / application/json (layer.json)
Content-Encoding gzip
Access-Control-Allow-Origin *

Integrations

Terratile serves standard quantized-mesh tiles compatible with any client that supports the format. Below are integration examples for popular frameworks.

CesiumJS — Query Parameter Auth

The simplest integration. Pass your API key as a query parameter:

const viewer = new Cesium.Viewer("cesiumContainer", {
  baseLayerPicker: false,
  geocoder: false,
});

viewer.scene.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  "https://terrain.terratile.io?key=YOUR_API_KEY",
  { requestVertexNormals: true }
);

CesiumJS — Header Auth (X-API-Key)

For production apps where you don't want the key visible in network URLs, use the X-API-Key header via a custom Resource:

const resource = new Cesium.Resource({
  url: "https://terrain.terratile.io",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
  },
});

const viewer = new Cesium.Viewer("cesiumContainer");

viewer.scene.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  resource,
  { requestVertexNormals: true }
);

The header is sent on every tile and layer.json request. If both a query parameter and header are present, the query parameter takes precedence.

CesiumJS — Error Handling & Retry on 429

CesiumJS does not natively retry on 429. For production apps under load, add a retry callback via Cesium.Resource:

const resource = new Cesium.Resource({
  url: "https://terrain.terratile.io?key=YOUR_API_KEY",
  retryCallback: async (resource, error) => {
    if (error && error.statusCode === 429) {
      const retryAfter = error.getResponseHeader?.("Retry-After");
      const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 10000;
      await new Promise((r) => setTimeout(r, delay));
      return true; // retry the request
    }
    return false;
  },
  retryAttempts: 3,
});

const viewer = new Cesium.Viewer("cesiumContainer");

viewer.scene.terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  resource,
  { requestVertexNormals: true }
);

The retryCallback returns true to signal CesiumJS to retry. The Retry-After header tells you exactly how long to wait.

Resium (React + CesiumJS)

Resium wraps CesiumJS as React components. Create the terrain provider once and pass it to the Viewer:

import { Viewer } from "resium";
import { CesiumTerrainProvider } from "cesium";
import { useEffect, useState } from "react";

function App() {
  const [terrainProvider, setTerrainProvider] = useState(null);

  useEffect(() => {
    CesiumTerrainProvider.fromUrl(
      "https://terrain.terratile.io?key=YOUR_API_KEY",
      { requestVertexNormals: true }
    ).then(setTerrainProvider);
  }, []);

  if (!terrainProvider) return <div>Loading terrain...</div>;

  return <Viewer full terrainProvider={terrainProvider} />;
}

export default App;

Install with npm install resium cesium. Resium works with Vite, Next.js, and Create React App.

deck.gl TerrainLayer

deck.gl can render quantized-mesh terrain tiles via its TerrainLayer (v8.8+):

import { Deck } from "@deck.gl/core";
import { TerrainLayer } from "@deck.gl/geo-layers";

const terrainLayer = new TerrainLayer({
  id: "terrain",
  minZoom: 0,
  maxZoom: 15,
  elevationData:
    "https://terrain.terratile.io/{z}/{x}/{y}.terrain?key=YOUR_API_KEY",
  texture:
    "https://services.arcgisonline.com/ArcGIS/rest/services/" +
    "World_Imagery/MapServer/tile/{z}/{y}/{x}",
  meshMaxError: 4.0,
});

const deck = new Deck({
  initialViewState: {
    latitude: 46.24,
    longitude: -122.18,
    zoom: 11.5,
    bearing: 140,
    pitch: 60,
    maxPitch: 89,
  },
  controller: true,
  layers: [terrainLayer],
});

The TerrainLayer automatically parses the binary quantized-mesh format when the tile URL ends in .terrain.

Rate Limits

Requests are rate-limited per API key using a 10-second fixed window. When the limit is exceeded the API returns 429 with a Retry-After header.

Tier Rate Price
Sandbox 5 req/s Free
Starter 20 req/s $49/mo
Production 50 req/s $119/mo
Business 150 req/s $299/mo
Custom Custom Contact us

Errors

The API returns standard HTTP status codes. Error responses include a JSON body with a message field.

Status Message Cause
401 Missing API key No key param or header provided
403 Invalid API key Key not found or revoked
429 Rate limit exceeded Over tier limit; retry after Retry-After seconds
500 Server error Internal issue