Static map images with no key, built for agents

# Static map images with no key, built for agents

An LLM can look at an image, but it cannot run MapLibre. `GET`/`POST https://mapmap.ai/api/static-map` renders a MapLibre view to a PNG, WebP or JPEG, with a route, GeoJSON or marker overlay, and it needs no API key: the endpoint is rate-limited instead of metered. It is served from the website, not the `api.mapmap.ai` gateway, and every render carries OpenStreetMap attribution baked into the image. The one real footgun is polyline precision, and we own it below because getting it wrong renders the wrong place with no error.

## Why does an agent need a server-rendered map?

Because agents are vision models, not rendering runtimes. A model can judge an image perfectly well: does this route cross the river where expected, does the isochrone actually cover the depot, is this pin on the right side of the ring road. What it cannot do is spin up WebGL, fetch vector tiles and evaluate a style to produce that image in the first place. Static map rendering closes the gap: the agent computes with the routing and analysis APIs, then asks this endpoint for a picture of the result, to check its own work or to show a human.

That is also why there is no key. The agents this exists for often have no billing relationship yet; some are mid-signup, some are evaluating, some are one-shot tools, a pattern we described in [maps for AI agents](/news/maps-for-ai). A keyless endpoint with hard limits serves all of them without a credential dance, and the limits, not a meter, are the guard.

## How do I render a route I just got back?

Pipe the encoded polyline from a routing response into `route=`, and let the camera auto-fit: with no `center` and no `bbox`, the view frames whatever overlay you pass.

```bash
export BASE=https://api.mapmap.ai
export API_KEY=snk_...

# 1. Get a route from the OSRM-compatible endpoint (precision 5 geometry).
# London Bridge to Greenwich: about 9 km, a 1,450-character polyline.
GEOM=$(curl -fsS -H "Authorization: Bearer $API_KEY" \
"$BASE/route/v1/driving/-0.0877,51.5079;-0.0098,51.4934" \
| jq -r '.routes[0].geometry')

# 2. Render it. No key on this endpoint. precision=5 is load-bearing here.
curl -fsS -G "https://mapmap.ai/api/static-map" \
--data-urlencode "route=$GEOM" \
-d "precision=5" -d "size=800x500" \
-o route.png
```

That route is deliberately a short one, because `route=` travels in the query string and the query string caps at 8,000 bytes. A city-scale hop fits with room to spare; a long intercity line does not. London to Birmingham encodes to roughly 7,500 characters, which is over 10,000 once URL-encoded, and the endpoint answers `414 query string too long: at most 8000 bytes` rather than rendering something truncated. See [long routes](#what-do-i-do-with-a-route-too-long-for-the-url) below for the way round it.

Sizes go up to 1280x1280 via `size=WxH` (or `width=`/`height=`), and appending `@2x` (or `retina=2`) doubles the pixel density for retina displays. `route_colour=` and `route_width=` restyle the line, `bearing=` and `pitch=` (capped at 85 degrees) tilt the camera, and `format=webp` or `format=jpeg` with `quality=` from 1 to 100 trades fidelity for bytes.

## Which polyline precision does your geometry use?

It depends on which endpoint produced it, and a mismatch fails silently. `POST /route` and the MCP `route` tool return an encoded polyline at precision 6, the native Valhalla shape, and precision 6 is also this endpoint's default. `GET /route/v1/{profile}/{coordinates}`, the OSRM-compatible endpoint, returns precision 5 by default, matching real OSRM.

An encoded polyline carries no marker of its own precision. Decode a precision-5 string as precision 6 and every coordinate is divided by an extra factor of ten: no error, no warning, just a route drawn confidently in the wrong place, shrunk towards zero-zero at a tenth of every coordinate you sent. We hit this ourselves while building the endpoint, which is why the rule is written into the [API reference](/docs/api-reference): anything from `/route/v1` gets an explicit `precision=5` unless the request asked for `geometries=polyline6`. Anything from `POST /route` or the MCP tool can rely on the default.

## What do I do with a route too long for the URL?

Send it in the body instead of the query string. The 8,000-byte cap is on the query string alone; a `POST` body may be up to 512,000 bytes, which is enough for any road route you are likely to draw.

The switch is not simply "same request, but POST". `route=` is a query parameter whichever verb you use, so moving to `POST` while leaving the polyline in the URL hits exactly the same `414`. What the body accepts is GeoJSON, so ask the routing endpoint for GeoJSON in the first place and post that:

```bash
# Ask for GeoJSON rather than an encoded polyline, and there is no
# precision question to get wrong either.
curl -fsS -H "Authorization: Bearer $API_KEY" \
"$BASE/route/v1/driving/-0.1276,51.5072;-1.8904,52.4862?geometries=geojson" \
| jq -c '{type: "Feature", geometry: .routes[0].geometry,
properties: {stroke: "#2563eb", "stroke-width": 4}}' \
> line.json

curl -fsS -X POST "https://mapmap.ai/api/static-map?size=800x500" \
-H "Content-Type: application/json" \
--data @line.json \
-o route.png
```

That is the same London to Birmingham route that overflows the query string, rendering fine as a 50 KB body. It is also the answer for anything else that outgrows a URL, which is why the isochrone example below takes the same shape.

## What else can go on the map?

GeoJSON, markers, satellite imagery and 3D buildings, alongside the route overlay.

| Parameter | What it does |
|---|---|
| `geojson=` (repeatable) or a JSON POST body | GeoJSON overlays; simplestyle `stroke`, `fill` and `fill-opacity` properties are honoured |
| `markers=lon,lat[,label[,colour]]` | Up to 50 pins, `;`-separated; labels are 1-2 characters |
| `satellite=1` | Sentinel-2 imagery where the pilot region has coverage |
| `buildings3d=1` | 3D building extrusions; pair it with a `pitch` |
| `style=` | `light` (default) or `dark`, a hosted style id from `GET /styles`, or an `https://` style URL on an allowlisted MapMap host |
| `lang=` and `pois=0` | Label language (`local` or an ISO 639 code); hide POI labels |

A real isochrone polygon is usually too big for a URL, so POST it as the body instead, straight from the [isochrones API](/news/isochrones):

```bash
# Render an /isochrone response as the overlay
curl -fsS -X POST "https://mapmap.ai/api/static-map?size=800x500" \
-H "Content-Type: application/json" \
--data @isochrone-response.json \
-o isochrone.png

# Three labelled pins, auto-framed, retina
curl -fsS "https://mapmap.ai/api/static-map?markers=-0.1276,51.5072,A;-0.1246,51.5033,B;-0.1195,51.5033,C&size=600x400@2x" \
-o pins.png
```

Responses carry an `ETag` and a long `Cache-Control`: a year, immutable, for a pinned render, and an hour for an unpinned hosted style that could be republished. Send `If-None-Match` and expect `304`s on a map you render often.

## What are the limits, and what is not live yet?

The guard on this endpoint is hard limits, not billing. Dimensions cap at 1280x1280, markers at 50, the query string at 8,000 bytes and a POST body at 512,000 bytes; requests are rate-limited per client with a concurrent-render ceiling on top, so the endpoint cannot become a free rendering farm. If you need volume rendering, that is a conversation, not a workaround.

Three more honest edges. `satellite=1` only shows imagery where the pilot region has coverage; elsewhere you get the vector style. The intended agent entry point, an MCP tool called `render_map` that wraps this endpoint with client-friendly image handling, is not yet live: it ships from the box-side MCP server separately, so call the HTTP endpoint directly for now. And attribution is not optional: every image carries OpenStreetMap/OpenMapTiles attribution, Sentinel-2/Copernicus wording when satellite imagery renders, and the MapMap mark, bottom-right. No parameter removes any of it, so credit © OpenStreetMap contributors wherever the image ends up; the render already does.

## Try it

One line, no signup, no key:

```bash
curl "https://mapmap.ai/api/static-map?center=-0.1276,51.5072&zoom=13&size=800x500" -o map.png
```

That is central London at zoom 13 (zoom runs 0 to 20, and `bbox=west,south,east,north` fits a box instead). The full parameter table lives in the API reference, the styles it renders are covered in the [maps guide](/docs/maps), and the routing requests that feed it run from your browser in the [playground](/playground).