50,000 free calls a month, card-free. Get an API key →

news / static-map-images · raw .md
Maps5 min readMapMap engineering
A single framed map view held in registration brackets like a photographic plate, with a route line entering the frame and continuing across it as printed geometry.

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. 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 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: 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.

ParameterWhat it does
geojson= (repeatable) or a JSON POST bodyGeoJSON 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=1Sentinel-2 imagery where the pilot region has coverage
buildings3d=13D 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=0Label 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:

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 304s 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, and the routing requests that feed it run from your browser in the playground.