Every Reforger map, over HTTP.
Every Arma Reforger map Enfumap has, its named places, and positions described in words. JSON over HTTPS, read only.
- No key needed
- CORS open
- Cached, fast
- 10 requests a second
https://enfumap.com/api/v1List every live map, or only the ones that changed lately.
GET /mapsIts size, author, Workshop ID, GUID and the address of every tile style.
GET /maps/{slug}Show the tiles, find a place by name, or turn a position into words.
Drawing a mapEvery answer has the same shape: { "ok": true, "data": … } when it worked, and { "ok": false, "error": "code", "message": "what went wrong" } with a 4xx status when it didn't.
Coordinates
Positions are world metres, the same numbers the game and its scripts use. x runs east and z runs north, both from the map's bottom-left corner. A map is worldSizeX metres wide and worldSizeZ metres tall (worldSize is the first).
As on the in-game map. 3 + 3 digits for a 100 m square (047 070), 4 + 4 for 10 m (0477 0709).
Bearings are degrees clockwise from north. locate also gives the 8 point direction: N, NE, E…
Map details
Besides its title, author, layers and tiles, every map in /maps and /maps/{slug} has these:
workshopstring | nullThe Workshop ID of the addon the map is built from (it is the addon's GUID). null for the base game's own maps.modVersionstring | nullThat addon's version when the tiles were built, like "1.0.12". null for the base game's maps.worldGuidstring | nullThe resource GUID of the world file (.ent) the map was exported from, 16 hex characters. null where it isn't known.worldSizeXnumberHow far the world runs east to west, in metres.worldSizeZnumberHow far it runs south to north, in metres. Every map so far is square, so the two match, but nothing promises it.worldSizenumberMetres across, the same as worldSizeX. Kept so older code keeps working.updatedAtintegerWhen the map last changed on Enfumap (a new build, satellite or hand drawn layer), in unix seconds.Keeping a copy in sync
Ask /maps?updatedSince= once a day with the time of your last check (unix seconds or a date) and refresh only the maps it returns. A map that is taken off the site just stops being listed, so now and then compare the full list's slugs with your own to catch those.
/places returns at most 500 a request: read on with offset (?limit=500&offset=500, then 1000…) until you have as many as the X-Total-Count header says. The order never changes between requests, so no page overlaps or skips.
Errors
unknown_map404There's no live map with that slug. List them with /api/v1/maps.bad_updatedSince400updatedSince isn't unix seconds or a date.bad_position400x or z is missing or isn't a number.off_map400The position is outside the map (it's worldSize metres across).no_layer404The map has no tiles in that style. Satellite, fogless and drawn only exist for some maps: the ones it has are in tiles.layers.no_tile404There's no tile at that address: a zoom past what that style is drawn to, a position off the map, or a typo.rate_limited429Over the rate limit. Wait the seconds in the Retry-After header, then carry on.Rate limits
Each address can make 10 requests a second, with bursts of up to 40 at once, so a page that fires off a few calls as it loads is fine. Go over it and you get a 429 with "error": "rate_limited" and a Retry-After header saying how many seconds to wait. Nothing is blocked for longer than that. Tiles have their own, roomier limit: 60 a second with bursts of 200, plenty for a map that's being dragged around.
Map data hardly ever changes, so the easiest way to stay well under the limit is to keep what you fetch: a map's places only change when the map is rebuilt, and its tiles.url carries a version that changes when it does. Building something that needs more? Get in touch.
List maps
/api/v1/mapsEvery live map: its title, the game's own name for its world (Everon is Eden), whether it's official or community made, its size (worldSize, and worldSizeX and worldSizeZ for each side), how many named places it has, a preview image and the address of its tiles. Also the Workshop ID and version of the addon the map is built from, the GUID of its world file (.ent), and updatedAt: when the map last changed, in unix seconds. Add updatedSince to get only the maps changed after a moment, so a daily poll can fetch just the difference. A map that is taken off the site simply stops being listed, so to spot removals compare the slugs against your own copy.
Parameters
updatedSincestringOnly maps changed after this moment: unix seconds (1793000000) or a date (2026-11-01, 2026-11-01T08:00:00Z). Compare it with updatedAt.
curl "https://enfumap.com/api/v1/maps"
Press Try it to call the live API.
Get a map
/api/v1/maps/{slug}One map, with everything needed to draw it: tile address, tile size, zoom range and metres per pixel at full detail, plus its world name, author, the Workshop ID and version of its addon, and the GUID of its world file. worldSizeX and worldSizeZ are the two sides in metres (every map so far is square, but that isn't promised). tiles.layers has the address of every map style it has (topo always, satellite and fogless once it's been photographed, drawn for the few maps with a hand drawn style), and tiles.layerMaxNativeZoom the zoom each one is drawn up to.
Parameters
slugpathrequiredThe map's name in its address: everon, arland, kolguyev…
curl "https://enfumap.com/api/v1/maps/everon"
Press Try it to call the live API.
Search places
/api/v1/maps/{slug}/placesThe map's named places: towns, villages, hills, bays, lakes and landmarks, with their world position. Search by name (accents and punctuation don't matter), filter by kind, or leave q off to get every place. A request returns at most 500: page through the rest with offset. The order is the same every time, so pages never overlap or skip a place, and the X-Total-Count response header says how many places match in all.
Parameters
slugpathrequiredThe map.
qstringName to search for. Every word has to match, so "saint pierre" finds Saint-Pierre.
kindstringComma separated kinds: capital, city, town, village, local, landmark, hill, ridge, valley, island, stream, lake, bay, sea.
limitintegerHow many to return, 1 to 500. Defaults to 20 when searching, 500 otherwise.
offsetintegerSkip this many places first, to read on past the 500 limit: offset=500 gives the next page. Defaults to 0.
curl "https://enfumap.com/api/v1/maps/everon/places?q=saint%20pierre"
Press Try it to call the live API.
Describe a position
/api/v1/maps/{slug}/locateTurns a position into words: the in-game grid reference and where it is from the nearest settlement, like "306 m NE of Montignac". Made for kill feeds, reports and server logs.
Parameters
slugpathrequiredThe map.
xnumberrequiredMetres east from the map's left edge.
znumberrequiredMetres north from the map's bottom edge.
curl "https://enfumap.com/api/v1/maps/everon/locate?x=5000&z=7300"
Press Try it to call the live API.
Health
/api/v1/healthWhether the API is up and how many maps it's serving. Handy for uptime checks.
Parameters
None.
curl "https://enfumap.com/api/v1/health"
Press Try it to call the live API.
Drawing a map
Tiles are 256 px WebP in the usual {z}/{x}/{y} layout, zoom 0 being the whole map in one tile, and each zoom drawn from the game's map data rather than scaled up. A tile's address changes when its map is rebuilt, so they're safe to cache forever. With Leaflet, a flat projection that maps metres straight to pixels does it:
import L from "leaflet";
const { data: map } = await (await fetch("https://enfumap.com/api/v1/maps/everon")).json();
const s = map.tiles.tileSize / map.worldSize;
const view = L.map("map", {
crs: L.extend({}, L.CRS.Simple, { transformation: new L.Transformation(s, 0, -s, map.tiles.tileSize) }),
maxZoom: map.tiles.maxNativeZoom + 2,
});
L.tileLayer("https://enfumap.com" + map.tiles.url, {
tileSize: map.tiles.tileSize,
maxNativeZoom: map.tiles.maxNativeZoom,
noWrap: true,
}).addTo(view);
// positions are [z, x] in world metres
view.setView([7094, 4773], 4);
L.marker([7094, 4773]).addTo(view).bindPopup("Montignac");That draws the topographic style. The other styles use the same grid of tiles: see Map styles.
API tiles carry a small, faint enfumap.com here and there. Please credit Enfumap wherever you show a map, and for community maps their author too: it's in each map's author and license.
Try it on a real map
The code above, running live: it picks a random map through the API and draws its tiles, with every style that map has.
Press Load a random map: it calls the live API and draws what comes back.
Map styles
A map can have up to four styles. They share one grid: the same tile sizes, the same {z}/{x}/{y}, the same world coordinates, so you can switch between them without moving the view. map.tiles.layers lists the ones this map has, by name, with their addresses, and map.tiles.layerMaxNativeZoom the zoom each is drawn up to. Zoom in past it and let your map library enlarge the tiles, the way Leaflet does with maxNativeZoom.
topoTopographicThe drawn map: terrain shading and contours, roads, buildings, forests, water. Every map has it, drawn to zoom 6 or 7.satelliteSatelliteThe terrain photographed from above. Only maps that have been captured.foglessFoglessThe same photos with the haze lifted. Comes with satellite.drawnHand drawnThe topo redrawn by hand: ink and pencil on parchment, a wash of colour, sketched trees. An extra style for a few maps, drawn to zoom 6.Don't assume a style exists: check for its key in tiles.layers first. Asking for a style a map doesn't have gets a no_layer error (404). Each style has its own faint enfumap.com mark on the API's tiles, in the style's own look (on the hand drawn style it is written in pen).
// every style this map has: topo, and satellite / fogless / drawn when it has them
const styles = Object.keys(map.tiles.layers);
// the hand drawn one if there is one, topo if not
const style = map.tiles.layers.drawn ? "drawn" : "topo";
L.tileLayer("https://enfumap.com" + map.tiles.layers[style], {
tileSize: map.tiles.tileSize,
maxNativeZoom: map.tiles.layerMaxNativeZoom[style], // each style is drawn to its own zoom
noWrap: true,
}).addTo(view);The endpoint behind the list, GET /api/v1/maps, also has layers for every map, so a picker can show only the styles that exist before you load a single map.