Slippy map tiles covering a bounding box
Turn a longitude and latitude bounding box into the exact XYZ coordinates used by slippy maps.
Run — free
Provide west, south, east, north, and a zoom level, and the calculator returns every intersecting tile in deterministic row-major order. It also reports compact x and y ranges, whether the box crosses the antimeridian, and whether polar latitude had to be clamped to Web Mercator. This makes cache estimates, offline map downloads, render queues, and coverage checks reproducible without querying a tile provider.
Define the geographic area and zoom
Enter the bounding box as west, south, east, and north in WGS84 decimal degrees, then choose an integer zoom from 0 through 22. South must be below north, and west and east must differ. Most boxes have west less than east. When west is greater than east, the calculator deliberately interprets the area as crossing the antimeridian, so a region around Fiji or the Bering Strait does not accidentally expand across almost the entire planet. To request the whole longitudinal world, use -180 for west and 180 for east. The calculation uses the familiar XYZ slippy-map convention: x increases from west to east, y increases from north to south, and z is the selected zoom. Latitude beyond the Web Mercator limit is accepted as geographic input but projected to the nearest representable map edge. The result flags that clamp, helping you explain why polar geographic bounds cannot create additional rows beyond the top or bottom map tiles.
Understand which boundary tiles are included
The returned list contains tiles that overlap the bounding box with positive area. A tile touched only along the east or south boundary is not added, which prevents an exact tile-aligned box from receiving an unnecessary extra column or row. The west and north edges select the tiles immediately inside the area. This half-open edge treatment is especially useful for cache planning because adjacent bounding boxes can be processed without systematically inflating both sides of every shared boundary. Each tile is an object with x, y, and z, and tiles are ordered by y row from north to south. Within a row, x proceeds west to east; an antimeridian box finishes the western high-x segment before continuing at x zero. The response also includes x_ranges and y_range so callers can inspect or store the compact coverage without reconstructing it from the full list. Tile count is computed before allocation, and requests covering more than 100,000 coordinates are rejected with a clear input error.
Use the coordinates in a cache workflow
Treat the output as a deterministic plan, not as downloaded map content. The capability performs no network requests and has no knowledge of a provider, style, file extension, access token, or usage policy. Your application can map each XYZ object to a provider URL, a local directory such as z/x/y, a database key, or a rendering job. Check tile_count before scheduling work so storage, bandwidth, and provider quotas are explicit. For repeatable offline packages, save the input bounds and zoom alongside the coordinate list; another run with the same input produces the same ordering and values. If a region needs several zoom levels, call the capability once per zoom because each level has its own grid and rapidly increasing tile count. Large areas at high zoom should be divided geographically or planned at a lower level first. The API price is $0.002 per request, while any downstream tile hosting or retrieval charges remain entirely separate and depend on the provider you choose.
What you can do with it
Estimate an offline map package
Count and enumerate the XYZ files required for a field application's selected region before downloading any imagery.
Warm a regional tile cache
Generate a stable queue of tile keys for a known service area and feed them to an authorized cache warmer.
Plan render jobs
Split a bounding-box render into discrete, reproducibly ordered tile tasks that workers can process independently.
FAQ
Which tile convention does this use?
It uses XYZ slippy map coordinates: x grows eastward, y grows southward, and z is the zoom level.
Can a bounding box cross the antimeridian?
Yes. Set west greater than east, and the output covers the high-x western segment followed by the low-x eastern segment.
Why are polar latitudes clamped?
Standard Web Mercator ends at approximately 85.0511 degrees north and south. Geographic input beyond that limit maps to the nearest available tile row.
Does this download or store map tiles?
No. It returns coordinates only and makes no network requests. You choose the tile source and must follow that provider's terms.
What does a request cost?
The API price is $0.002 per request. Costs from a map tile provider, storage service, or rendering system are separate.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/geo/tiles-in-bbox \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"west":-74.02,"south":40.7,"east":-73.98,"north":40.73,"zoom":12}'const res = await fetch("https://api.kit.forhosting.com/geo/tiles-in-bbox", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"west": -74.02,
"south": 40.7,
"east": -73.98,
"north": 40.73,
"zoom": 12
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/geo/tiles-in-bbox",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"west": -74.02,
"south": 40.7,
"east": -73.98,
"north": 40.73,
"zoom": 12
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/geo/tiles-in-bbox", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"west":-74.02,"south":40.7,"east":-73.98,"north":40.73,"zoom":12}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"west":-74.02,"south":40.7,"east":-73.98,"north":40.73,"zoom":12}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/geo/tiles-in-bbox", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"west": -74.02,
"south": 40.7,
"east": -73.98,
"north": 40.73,
"zoom": 12
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "geo.tiles_in_bbox",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
min_zoom | 0 |
max_zoom | 22 |
max_tiles | 100000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |