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
401despite 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.