Back to the map

Embed and API

Put the map in your own page, lay the water on your own map, or read the modelled flood depths of 28 June to 1 July 2026. All of it is free. The embed needs no key; the API takes a free key.

Get a free API key

A key raises the limit from 60 requests a minute to 1,200 a minute for each of your users. Your key shows here and comes to you by email.

We keep your name, email, organisation and key, to send you the key and to contact you about the API. Each request is counted by key, not by address. Email derrick@dawnstackai.com to delete your key and details.

JavaScript library

One script gives you the embed, the depth calls and map layers for Google Maps and Leaflet:

<script src="https://flood-check.dawnstackai.com/sdk/v1.js"></script>
<script>
  LagosFlood.init({ key: 'lfk_your_key' });

  // Hourly depth at one point
  const d = await LagosFlood.depth(6.5578, 3.3378);
  console.log(d.peak.depth_cm, d.peak.time);

  // A summary for many points (the library sends them 100 at a time)
  const { results } = await LagosFlood.depthMany(listings.map((l) => ({ id: l.id, lat: l.lat, lon: l.lon })));
</script>

Water on a Google map

const map = new google.maps.Map(el, { center: { lat: 6.53, lng: 3.37 }, zoom: 13 });
const flood = LagosFlood.googleMapsLayer(map, { hour: 'peak', opacity: 0.85 });
flood.setHour('2026-07-01T04:00');  // change the hour
flood.remove();

Water on a Leaflet map

const flood = LagosFlood.leafletLayer({ hour: 'peak' }).addTo(map);
flood.setHour(40);

The 3D map, with a controller

const m = LagosFlood.embed('#flood-map', {
  hour: 65, imagery: false,
  pin: { lat: 6.5578, lon: 3.3378, name: 'Listing 1043' },
});
m.on('pin', (e) => console.log(e.depth_cm, e.peak_cm));
m.setTime('2026-06-30T17:00').play();
m.fly(6.555, 3.34, { distance: 3000 });
m.setLayer('buildings', false);

The map fills the element you give it, so give the element a height. Commands sent before the map is ready wait for it. m.ready is a promise for that moment.

Map tiles

The water as 256 px map tiles, in the grid that Google Maps, Leaflet, Mapbox and MapLibre use. The edge of the water stays smooth at street zoom. Tiles outside the map are clear.

GET /api/v1/tiles/{hour}/{z}/{x}/{y}.png?key=lfk_your_key
GET /api/v1/tiles/peak/15/16693/15791@2x.png?key=lfk_your_key

hour is 0 to 95, a local time like 2026-06-30T17:00, or peak (17:00 on 30 June). Add @2x for 512 px tiles on sharp screens. Zoom 0 to 20.

// MapLibre or Mapbox GL
map.addSource('flood', { type: 'raster', tileSize: 256,
  tiles: ['https://flood-check.dawnstackai.com/api/v1/tiles/peak/{z}/{x}/{y}.png?key=lfk_your_key'] });
map.addLayer({ id: 'flood', type: 'raster', source: 'flood', paint: { 'raster-opacity': 0.85 } });

// Leaflet, without the library
L.tileLayer('https://flood-check.dawnstackai.com/api/v1/tiles/peak/{z}/{x}/{y}.png?key=lfk_your_key',
  { opacity: 0.85, attribution: 'Flood Check' }).addTo(map);

Embed the map

Add an iframe, or use LagosFlood.embed from the library. The embed needs no key. This one opens with a pin in Mushin at 17:00 on 30 June, without satellite imagery:

<iframe
  src="https://flood-check.dawnstackai.com/?embed=1&at=6.5578,3.3378&t=65&imagery=0"
  style="width: 100%; height: 560px; border: 0"
  loading="lazy" allow="clipboard-write"
  title="Lagos flood map"></iframe>
SettingWhat it does
embed=1Map and timeline only, with a link back to the full map. Needed.
t=0 to 95The hour on the timeline. 0 is 00:00 on 28 June; 65 is 17:00 on 30 June, the peak.
at=lat,lon&n=NameOpens with a pin and its depth card.
imagery=0Painted ground instead of satellite imagery. Use it in a commercial product (see Terms).
ui=noneNo timeline: the map only.
play=1Plays the four days when it opens.

Control the embedded map

Send commands with postMessage, and listen for what the map sends back:

const map = document.querySelector('iframe').contentWindow;

// Drop a pin and fly to it
map.postMessage({ type: 'lagosflood:pin', lat: 6.4432, lon: 3.4721, name: 'Our office' }, '*');
// Set the time, by hour or by local time
map.postMessage({ type: 'lagosflood:time', time: '2026-06-30T17:00' }, '*');
// Play or pause, move the camera, switch a layer
map.postMessage({ type: 'lagosflood:play', on: true }, '*');
map.postMessage({ type: 'lagosflood:fly', lat: 6.555, lon: 3.34, distance: 3000 }, '*');
map.postMessage({ type: 'lagosflood:layer', layer: 'buildings', on: false }, '*');
map.postMessage({ type: 'lagosflood:clearPin' }, '*');

window.addEventListener('message', (e) => {
  if (e.data?.type === 'lagosflood:ready') { /* the model has run; commands work now */ }
  if (e.data?.type === 'lagosflood:pin') console.log(e.data.depth_cm, e.data.peak_cm, e.data.time);
  if (e.data?.type === 'lagosflood:time') console.log(e.data.hour, e.data.time);
});

Layers: flood, receded, imagery, highlight (buildings with water at the door), roadflood, buildings, roads, labels, rain.

API

Base address: https://flood-check.dawnstackai.com/api/v1. Every answer is JSON or PNG, open to any site (CORS), and cached for a day. The depths are the same ones the map shows.

Send your key in a header, or add it to the address:

Authorization: Bearer lfk_your_key
GET /api/v1/depth?lat=6.5578&lon=3.3378&key=lfk_your_key
Limit
No key60 requests a minute from one address. Good to try the API.
With a key1,200 requests a minute for each user of your app.

Over the limit, the answer is 429 with Retry-After: 60. A key that is not valid gets 401.

Regions

GET /api/v1/regions

The bounds, grid, hours and peak of the map (one region, Lagos), and the overlay and tile addresses.

Depth at a point

GET /api/v1/depth?lat=6.5578&lon=3.3378
GET /api/v1/depth?lat=6.5578&lon=3.3378&time=2026-06-30T17:00
{
  "region": "lagos",
  "cell": { "lat": 6.55756, "lon": 3.33764, "size_m": [60, 60] },
  "ground_m": 12.2,
  "open_water": false,
  "unit": "cm",
  "timezone": "Africa/Lagos",
  "times": ["2026-06-28T00:00", "2026-06-28T01:00", ...],
  "depth_cm": [0, 0, ...],
  "peak": { "depth_cm": 80, "time": "2026-07-01T04:00" },
  "hours_10cm_or_more": 38,
  "at": { "time": "2026-06-30T17:00", "depth_cm": 48 },
  "attribution": "...",
  "disclaimer": "..."
}

Depths are in 2 cm steps. A point outside the map answers 404 with the bounds that are covered.

Overlay image

GET /api/v1/overlay.png?hour=65
GET /api/v1/overlay.png?hour=2026-06-30T17:00&scale=1

A transparent PNG of the water for one hour, pale blue to deep blue as on the map. It covers the map's bounds exactly (also in the X-Bounds header, as south, west, north, east), so it drops straight onto another map:

// Google Maps
new google.maps.GroundOverlay(
  'https://flood-check.dawnstackai.com/api/v1/overlay.png?hour=65',
  { south: 6.40, west: 3.18, north: 6.72, east: 3.60 }, { opacity: 0.85 }).setMap(map);

// Leaflet
L.imageOverlay(url, [[6.40, 3.18], [6.72, 3.60]], { opacity: 0.85 }).addTo(map);

// Mapbox GL / MapLibre
map.addSource('flood', { type: 'image', url, coordinates: [[3.18, 6.72], [3.60, 6.72], [3.60, 6.40], [3.18, 6.40]] });
map.addLayer({ id: 'flood', type: 'raster', source: 'flood' });

scale is 1 to 4 pixels per 60 m square; 2 is the default. For a closer look, the map tiles are sharper.

Many points at once

POST /api/v1/depth
Content-Type: application/json

{ "points": [
  { "id": "listing-1042", "lat": 6.4432, "lon": 3.4721 },
  { "id": "listing-1043", "lat": 6.5578, "lon": 3.3378 }
] }
{
  "event": "28 June to 1 July 2026",
  "unit": "cm",
  "results": [
    { "id": "listing-1042", "covered": true, "region": "lagos", "open_water": false,
      "ground_m": 3.6, "peak_cm": 0, "peak_time": null, "band": "dry", "hours_10cm_or_more": 0 },
    { "id": "listing-1043", "covered": true, "region": "lagos", "open_water": false,
      "ground_m": 12.2, "peak_cm": 80, "peak_time": "2026-07-01T04:00", "band": "thigh deep", "hours_10cm_or_more": 38 }
  ],
  "attribution": "...", "disclaimer": "..."
}

Up to 100 points per request. A point outside the map comes back with "covered": false. band is the depth in plain words: dry, wet ground, ankle, shin, knee, thigh or waist deep, or chest deep or more.

Example: property listings

A listings site can show how each home fared in the June 2026 flood, three ways. Each works on its own.

1. A badge on every listing

When a listing is saved, send its coordinates to POST /api/v1/depth (100 at a time) and store the answer with the listing. It never changes, so there is no need to call again. Show the band and the hours:

const res = await fetch('https://flood-check.dawnstackai.com/api/v1/depth', {
  method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer lfk_your_key' },
  body: JSON.stringify({ points: listings.map((l) => ({ id: l.id, lat: l.lat, lon: l.lon })) }),
}).then((r) => r.json());

for (const r of res.results) {
  if (!r.covered) continue; // not on the map yet: show nothing
  saveFloodCheck(r.id, {
    label: r.peak_cm < 10 ? 'Stayed dry in the June 2026 flood model' : `Up to ${r.peak_cm} cm (${r.band}) in the June 2026 flood model`,
    hours: r.hours_10cm_or_more,
  });
}

2. The map on the listing page

Embed the map with a pin on the home, so a buyer can play the four days and see the street around it: ?embed=1&at=6.4432,3.4721&n=Listing%201042&t=65&imagery=0.

3. Water on your search map

Add the map tiles at the peak hour to your own map of results, so buyers see which streets flooded while they browse. With the library it is one line: LagosFlood.googleMapsLayer(map).

Say what it is

Label it as a model of one event, with the credit and a link, for example: "Modelled flood depth, 28 June to 1 July 2026, by Flood Check. A model, not a survey." A dry result is not a promise that a home never floods, and a 60 m square can hold a high plot next to a low road. For homes with a high reading, suggest a site visit or a survey.

A listings product is commercial use: embed with imagery=0. If you need more areas, more events, or a check on demand for each new listing, use the form below.

Terms

Get in touch

Building on the map or the API, using it in a product, or need more than the free API gives? Tell me about it and I will reply by email.

What you enter here goes to Derrick Ikenga by email, to answer you. It is not stored on this site or shared.

How it works and FAQ