The viewport request
Every parameter, how zoom picks the detail level, and the caching headers.
Base URL https://api.offendersearch.appThe request
GET /v1/map/viewport answers one map view. Call it when the map first loads and each time it stops moving. One call is one map request.
curl -G https://api.offendersearch.app/v1/map/viewport \
-H "X-API-Key: $OFFENDERSEARCH_KEY" \
-d bbox=-83.2,39.8,-82.8,40.1 \
-d zoom=10Parameters
| Parameter | Meaning |
|---|---|
bbox | west,south,east,north in degrees. Any size. A box that crosses the date line has west greater than east. |
center + radiusMiles | Instead of bbox: lat,lng and a distance from 0.1 to 100 miles. See Near an address. |
zoom | Optional. 0 to 22; a fraction is rounded down. Send the map’s current zoom. |
format | json (the default) or geojson. |
detail | full (the default) or count for numbers only. |
housing | housed, homeless, incarcerated. Separate several with commas. |
precision | street returns only people with an exact street address. any (the default) returns everyone. |
sandbox | true puts the request in test mode. |
Send bbox, or center with radiusMiles. Not both.
The optional X-End-User header carries any id you choose for the person using the map. It is read on this route and on POST /v1/map/area. See Limits & errors.
How the zoom picks the detail level
| Effective zoom | mode | Returned |
|---|---|---|
| 3–12 | clusters | A count for each grid cell, with its extent and a breakdown. |
| 13–14 | pins | One pin for each address: a position, with no name. |
| 15+ | markers | Pins with a name label and basic fields. |
The effective zoom is worked out like this:
- It starts as the
zoomyou sent. If you sent none, it is the zoom at which the box is about four map tiles wide. - If the box covers more than 64 map tiles at that zoom, the zoom is lowered until it fits.
- It is never lower than 3.
The response states the zoom that was used, in zoom, and the level, in mode. Draw from mode, not from your own zoom.
Any size of area
There is no largest box. A view of a whole country is answered as clusters, in one map request, at the same price as a view of one street. A wide box is simply answered at a lower zoom.
The 300-pin rule
A response holds at most 300 pins. A view at zoom 13 or closer with more than that is answered as clusters on a finer grid, with mode: "clusters". Each cluster’s expansionZoom says how far to zoom for it to come apart.
The response
{
"mode": "clusters",
"zoom": 10,
"bbox": [-83.2, 39.8, -82.8, 40.1],
"datasetVersion": "2026-10-12",
"total": 508,
"breakdown": {
"street": 508, "housed": 247, "homeless": 96, "incarcerated": 0
},
"clusters": [
{
"id": "c10_1103_1551",
"lat": 39.9612, "lng": -82.9988,
"count": 412,
"bbox": [-83.06, 39.91, -82.97, 39.98],
"expansionZoom": 11,
"breakdown": {
"street": 412, "housed": 201, "homeless": 77, "incarcerated": 0
}
}
],
"markers": [],
"areas": [],
"unplaced": {"count": 96, "states": ["OH"]},
"notice": "A pin shows a registered address, not a person's current location.",
"sandbox": true
}| Field | Meaning |
|---|---|
mode | clusters, pins, markers, or count when detail=count. |
zoom | The effective zoom the view was answered at. |
bbox | The box that was answered. |
datasetVersion | The version of the map data that answered. |
total | People in the view. In a clusters answer: in the clusters returned. With a radius: people inside the circle. |
breakdown | Of total: how many have an exact street address, and the housing status where the registry states it. |
clusters, markers, areas | What to draw. See Clusters & markers. |
unplaced | People in the states this view touches who have no mappable address. |
notice | A sentence to show on the map. |
center, radiusMiles, withinRadius | Only when the request used a radius. withinRadius is exact. |
Markers are sorted by distance from the centre of the view.
Caching
Every answer carries Cache-Control: private, max-age=300 and an ETag.
- Send the
ETagback inIf-None-Match. An unchanged view answers304, with no body. A304is not billed. - The
ETagchanges when the map data changes, and at least every 12 hours, so marker ids are never kept past their life. - Keep the body with its
ETag. On a304, draw the body you kept. - The answer is private to your account. Do not put it in a shared cache.
Errors
A bad parameter answers 422 with a code that names it: invalid_bbox, invalid_center, invalid_radius, invalid_zoom, invalid_filter, invalid_format, invalid_detail. All codes are in Limits & errors.