Map Tiles

Display Geocerta raster tiles inside a GeoCertaMap instance using your API key.

How it works

Tile requests are authenticated with a short-lived JWT. Your server exchanges your API key for a token, proxies tile requests to the Geocerta tile service with that token in the auth-token header, and passes the image back to the browser. The token is valid for 30 minutes; your proxy should refresh it automatically.


1. Get a token

Exchange your API key for a tile token by calling POST /auth/token.

Base URL: https://api.geocerta.io

Sandbox URL: https://api-staging.geocerta.io

curl -X POST https://api.geocerta.io/auth/token \
  -H "api-key: YOUR_API_KEY"

Response

The token expires after 30 minutes. Cache it and refresh it before it expires rather than fetching a new one for every tile.


2. Create a tile proxy route

The tile service base URLs are:

Environment URL
Production https://tiles.geocerta.io
Sandbox https://tiles-staging.geocerta.io

Your server proxies tile requests to the Geocerta tile service, injecting the auth-token header. This keeps your JWT out of the browser.

The example below is a Next.js App Router route handler. The same pattern applies to any server framework.

// app/api/tiles/route.ts
import { NextRequest } from 'next/server';

const TILES_URL = 'https://tiles.geocerta.io'; // sandbox: https://tiles-staging.geocerta.io
const API_URL   = 'https://api.geocerta.io';   // sandbox: https://api-staging.geocerta.io
const API_KEY   = process.env.GEOCERTA_API_KEY;

let cachedToken: string | null = null;
let tokenExpiresAt = 0;

function parseExpiry(token: string): number {
  const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString());
  return payload.exp * 1000;
}

async function getToken(): Promise<string> {
  if (cachedToken && Date.now() < tokenExpiresAt - 60_000) return cachedToken;

  const res = await fetch(`${API_URL}/auth/token`, {
    method: 'POST',
    headers: { 'api-key': API_KEY! },
  });
  if (!res.ok) throw new Error(`Failed to get tile token: ${res.status}`);

  const { token } = await res.json();
  cachedToken = token;
  tokenExpiresAt = parseExpiry(token);
  return token;
}

async function fetchTile(url: string, token: string) {
  return fetch(url, { headers: { 'auth-token': token } });
}

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl;
  const z = searchParams.get('z');
  const x = searchParams.get('x');
  const y = searchParams.get('y');
  if (!z || !x || !y) return new Response('Missing z/x/y', { status: 400 });

  const tileUrl = `${TILES_URL}/${z}/${x}/${y}.png`;

  let token = await getToken();
  let res = await fetchTile(tileUrl, token);

  // Token may have expired mid-session — retry once with a fresh token
  if (res.status === 401) {
    cachedToken = null;
    token = await getToken();
    res = await fetchTile(tileUrl, token);
  }

  const data = await res.arrayBuffer();
  return new Response(data, {
    status: res.status,
    headers: { 'content-type': res.headers.get('content-type') ?? 'image/png' },
  });
}

3. Configure the map style

Pass a style object to GeoCertaMap that points to your proxy route.

const tileStyle = {
  version: 8 as const,
  glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf',
  sources: {
    'geocerta-tiles': {
      type: 'raster' as const,
      tiles: ['/api/tiles?z={z}&x={x}&y={y}'],
      tileSize: 256,
    },
  },
  layers: [
    {
      id: 'geocerta-tiles',
      type: 'raster' as const,
      source: 'geocerta-tiles',
    },
  ],
};

const map = new GeoCertaMap({
  container: 'map',
  style: tileStyle,
  center: [-0.1276, 51.5074],
  zoom: 14,
});

The {z}, {x}, and {y} placeholders are replaced automatically for each tile request.


Errors

Token endpoint (POST /auth/token)

Status Cause
401 Missing or invalid API key
403 API key does not have permission to access tiles

Tile proxy

Status Cause
401 Token is missing, malformed, or has expired. The proxy example above handles this automatically by clearing the cached token and retrying once with a fresh one.
403 Token was issued for a different account or has been revoked
400 Missing z, x, or y parameters

Token expiry: Tokens are valid for 30 minutes. The proxy keeps a module-level cache and proactively refreshes 60 seconds before expiry. If a tile request returns 401 despite a seemingly valid token (e.g. after a server restart or clock skew), the proxy discards the cache and fetches a new token before retrying the tile once.