Map Zone Maps API — Philippines
Reference for all three API groups: Tiles, Geocoding, and Routing. All three share one gateway and
one authentication scheme — an API key on the query string, accepted under either key or apikey on every
endpoint.
| API | Base path | Key parameter | Format |
|---|---|---|---|
| Tiles | /maps/styles, /maps/tiles, /maps/fonts, /maps/sprites |
key or apikey |
Binary (vector tiles, glyphs, sprite PNG) + JSON (style, sprite index) |
| Geocoding | /maps/api/geocode, /maps/api/place |
key or apikey |
JSON, Google Geocoding/Places-compatible |
| Routing | /maps/api/directions, /maps/api/distancematrix |
key or apikey |
JSON, Google Directions/Distance Matrix-compatible |
Authentication
Every request carries the key on the query string. key and apikey are equivalent on all three APIs — pick
whichever name is more convenient:
https://ph.map.zone/maps/tiles/PHL_latest/12/3424/1880?key=YOUR_API_KEY
https://ph.map.zone/maps/api/geocode/json?address=Rizal+Park&key=YOUR_API_KEY
https://ph.map.zone/maps/api/directions/json?origin=...&destination=...&key=YOUR_API_KEY
Your application → Gateway (key + quota check) → Upstream service
The gateway checks the key and quota before forwarding; the upstream services do not validate the key themselves. There are no redirects on any endpoint — a 3xx response from the gateway should be treated as an error, not followed.
Common conventions (Geocoding + Routing)
Both are Google-compatible APIs: HTTP is always 200, and the result or the error is reported in the JSON
status field. Field names are snake_case. A field with no value is omitted from the JSON entirely — it is
never sent as null.
Tiles API
MapLibre GL style, vector tiles, font glyphs, and sprite icons for the basemap-phl vector basemap.
style.json is the single source of truth for what may be requested. Every tiles / glyphs / sprite
URL inside the style already points back at these same endpoints (with the key stripped) — build tile/font/sprite
requests from the URLs in the style, not from your own convention. Tiles outside the Philippines coverage area,
or above the demo's zoom limit, return an empty tile (204 No Content), never an error.
Tiles, fonts and sprites are binary data (unlike Geocoding/Routing, which are always JSON).
GET /maps/styles/{name}/style.json
Full MapLibre GL style (sources, layers, sprite, glyphs) for one system style.
| Param | In | Required | Description |
|---|---|---|---|
name |
path | yes | System style name, one style per country. Example: basemap-phl |
200 — a MapLibre GL Style Spec JSON object. Key fields:
| Field | Description |
|---|---|
version |
Always 8 |
sources |
{ [sourceId]: { tiles: [urlTemplate], ... } } — tile URL template(s), already rewritten |
glyphs |
URL template pointing at the /maps/fonts/{fontstack}/{range} endpoint |
sprite |
Base URL (no extension) pointing at the /maps/sprites/{file} endpoint |
layers |
Standard MapLibre style layers |
GET /maps/tiles/{source}/{z}/{x}/{y}
One vector tile (Mapbox Vector Tile) for one source in the style.
| Param | In | Required | Description |
|---|---|---|---|
source |
path | yes | Source id exactly as it appears in style.json → sources (not the mbtiles file name). Example: PHL_latest |
z |
path | yes | Zoom level (XYZ/slippy map). Example: 12 |
x |
path | yes | Tile X coordinate. Example: 3424 |
y |
path | yes | Tile Y coordinate, optionally suffixed .pbf or .mvt (both accepted). Example: 1880 |
200 — binary vector tile (application/vnd.mapbox-vector-tile), usually gzip-compressed with
Content-Encoding passed through from upstream.
204 — empty tile (outside the demo area, or no data for this tile); MapLibre treats this as a valid, empty
tile, not an error.
GET /maps/fonts/{fontstack}/{range}
Glyph PBF for one character range of one font stack.
fontstack must be one the style actually uses (its text-font values), unless the style uses a data-driven
font expression (e.g. ["get","font"]) — in that case the font list isn't restricted.
| Param | In | Required | Description |
|---|---|---|---|
fontstack |
path | yes | One or more font names separated by commas, spaces as %20. Example: Open Sans Regular,Arial Unicode MS Regular |
range |
path | yes | A 256-character range, as start-end, optionally suffixed .pbf. Example: 0-255.pbf |
200 — binary glyph PBF (application/x-protobuf).
404 — fontstack not present in the style, or an invalid range.
GET /maps/sprites/{file}
Sprite icons for the map — JSON (icon positions in the sprite sheet) or PNG (the sprite sheet image).
| Param | In | Required | Description |
|---|---|---|---|
file |
path | yes | Sprite name + optional pixel ratio (@2x, @3x) + .json or .png extension. Examples: sprite.json, sprite@2x.png |
200 — JSON { [iconId]: { x, y, width, height, pixelRatio, sdf? } }, or a PNG image.
404 — sprite name not present in the style, or an invalid file format.
Geocoding API
Autocomplete, Place Details, and forward/reverse geocoding — compatible with Google's Geocoding API and Places API (Autocomplete, Place Details).
Shared status enum (GeoStatus): OK, ZERO_RESULTS, INVALID_REQUEST, OVER_QUERY_LIMIT,
OVER_DAILY_LIMIT, REQUEST_DENIED, UNKNOWN_ERROR.
GET /maps/api/place/autocomplete/json
Place suggestions as the user types (intended to be called debounced, client-side).
| Param | In | Required | Description |
|---|---|---|---|
input |
query | yes | Search text, 1+ characters. Example: SM Mall |
limit |
query | yes | Maximum number of suggestions, 1–20. Example: 5 |
location |
query | no | lat,lng used as a bias toward nearby results (not a hard filter). Example: 14.60,121.02 |
200 response (AutocompleteResponse):
{
"status": "OK",
"predictions": [
{
"description": "SM Mall of Asia, Seaside Boulevard, Pasay, Metro Manila, Philippines",
"place_id": "PHsmmoa01",
"structured_formatting": {
"main_text": "SM Mall of Asia",
"secondary_text": "Seaside Boulevard, Pasay, Metro Manila, Philippines",
"main_text_matched_substrings": [{ "offset": 0, "length": 2 }]
}
}
]
}
main_text_matched_substrings gives the ranges of the matched substring, for bolding/highlighting the typed
text in a UI.
GET /maps/api/place/details/json
Details for one place, by place_id (as returned by Autocomplete).
| Param | In | Required | Description |
|---|---|---|---|
place_id |
query | yes | Id returned by Autocomplete, in the form PH + base64url. Example: PHsmmoa01 |
200 response (DetailsResponse):
{
"status": "OK",
"result": {
"name": "SM Mall of Asia",
"formatted_address": "Seaside Boulevard, Pasay, 1300 Metro Manila, Philippines",
"geometry": { "location": { "lat": 14.5352, "lng": 120.9815 } },
"types": ["shopping_mall", "point_of_interest"]
}
}
GET /maps/api/geocode/json
Forward geocoding (address → coordinates) or reverse geocoding (coordinates → address), on one shared endpoint —
pass exactly one of address or latlng per request.
| Param | In | Required | Description |
|---|---|---|---|
address |
query | no | Address/place name to look up (forward geocoding). Example: Rizal Park, Manila |
latlng |
query | no | lat,lng to resolve to an address (reverse geocoding), inside the PH area. Example: 14.58290,120.97920 |
200 response (GeocodeResponse), forward:
{
"status": "OK",
"results": [
{
"formatted_address": "Rizal Park, Ermita, Manila, 1000 Metro Manila, Philippines",
"geometry": { "location": { "lat": 14.5826, "lng": 120.9787 }, "location_type": "APPROXIMATE" },
"place_id": "PHrizalpark01",
"types": ["park", "point_of_interest"]
}
]
}
reverse:
{
"status": "OK",
"results": [
{
"formatted_address": "Near Manila City Hall, Manila, Metro Manila, Philippines",
"geometry": { "location": { "lat": 14.5829, "lng": 120.9792 }, "location_type": "APPROXIMATE" },
"place_id": "PHmanilacityhall01",
"types": ["local_government_office", "point_of_interest"]
}
]
}
no match: { "status": "ZERO_RESULTS", "results": [] }
Shared Geocoding schemas
PlaceResult (used by both results[] and Place Details' result):
| Field | Description |
|---|---|
name |
Place name |
formatted_address |
Required |
geometry.location |
{ lat, lng }, required |
geometry.location_type |
ROOFTOP, RANGE_INTERPOLATED, GEOMETRIC_CENTER, or APPROXIMATE — precision of the location |
geometry.viewport |
Suggested viewport to zoom to for this result |
place_id |
Id for Place Details lookups |
types |
Array of place type strings |
address_components |
Array of address component objects |
Routing API
Directions through a series of stops (with optional TSP stop-order optimization) and a time/distance matrix — compatible with Google's Directions API and Distance Matrix API.
Polylines use Google's Encoded Polyline Algorithm, precision 1e5 (not polyline6), lat,lng order.
overview_polyline is the full route geometry, not simplified.
GET /maps/api/directions/json
Directions through a series of stops. Add optimize:true as the first element of waypoints to turn on TSP
stop-order optimization.
| Param | In | Required | Description |
|---|---|---|---|
origin |
query | yes | Starting point lat,lng (WGS84). Example: 14.60997,120.99313 |
destination |
query | yes | Destination lat,lng. A round trip = same value as origin. |
waypoints |
query | no | Intermediate stops separated by \|, URL-encoded (\|→%7C, ,→%2C, :→%3A). See TSP below. |
mode |
query | yes | Travel mode: driving, walking, or bicycling. |
Without optimization, stops are visited in the order given:
waypoints=14.68636,120.94507|14.66976,121.1236
With TSP (optimize:true as the first element), the service re-orders the stops, keeping the first
(origin) and last (destination) points fixed. The response carries the visiting order in
routes[0].waypoint_order:
waypoints=optimize:true|14.68636,120.94507|14.66976,121.1236|14.52025,120.97253
waypoint_order is a permutation of 0..n-1 (n = number of points in waypoints, not counting optimize:true,
origin, or destination). Element i = the input index of the stop visited i-th. Example: sending stops
B|C|D and getting back waypoint_order: [2, 0, 1] means the visiting order was D, B, C.
200 response (DirectionsResponse), optimized example:
{
"status": "OK",
"geocoded_waypoints": [],
"routes": [
{
"summary": "EDSA",
"waypoint_order": [2, 0, 1],
"overview_polyline": { "points": "iodxAannaVvoPv_Cem_@rjDvfByza@ttJlnX" },
"legs": [
{
"distance": { "text": "13.7 km", "value": 13664 },
"duration": { "text": "34 mins", "value": 2050 },
"start_location": { "lat": 14.60997, "lng": 120.99313 },
"end_location": { "lat": 14.52025, "lng": 120.97253 },
"steps": [
{
"html_instructions": "Head <b>south</b> on <b>Ayala Avenue</b>",
"distance": { "text": "13.7 km", "value": 13664 },
"duration": { "text": "34 mins", "value": 2050 },
"polyline": { "points": "iodxAannaVvoPv_C" }
}
]
},
{ "distance": { "text": "24.3 km", "value": 24300 }, "duration": { "text": "1 hour 1 min", "value": 3650 }, "steps": [] },
{ "distance": { "text": "25.1 km", "value": 25100 }, "duration": { "text": "1 hour 3 mins", "value": 3770 }, "steps": [] },
{ "distance": { "text": "20.2 km", "value": 20200 }, "duration": { "text": "51 mins", "value": 3030 }, "steps": [] }
]
}
]
}
no route found: { "status": "ZERO_RESULTS", "routes": [] }
DirectionsResponse fields:
| Field | Description |
|---|---|
status |
OK, ZERO_RESULTS, NOT_FOUND, INVALID_REQUEST, MAX_WAYPOINTS_EXCEEDED, MAX_ROUTE_LENGTH_EXCEEDED, OVER_QUERY_LIMIT, OVER_DAILY_LIMIT, REQUEST_DENIED, UNKNOWN_ERROR. NOT_FOUND means a point is too far from the nearest routable road (guideline: > 500 m). |
error_message |
Only present on error; one short English sentence |
routes[] |
Always present; empty when status != OK |
routes[].summary |
Name of the main road ("via <summary>") |
routes[].legs[] |
Count = number of stops + 1, in visiting order |
routes[].overview_polyline |
Full route geometry |
routes[].waypoint_order |
Required when optimize:true was sent; see above |
routes[].bounds |
{ northeast, southwest } |
legs[].distance / duration |
{ text, value } — value is meters / seconds |
legs[].steps[].html_instructions |
Simple HTML (<b>, <div> for a secondary note) |
legs[].steps[].maneuver |
Optional maneuver hint string |
GET /maps/api/distancematrix/json
Time/distance matrix between a set of origins and a set of destinations.
| Param | In | Required | Description |
|---|---|---|---|
origins |
query | yes | Origin points lat,lng\|lat,lng\|..., up to 5 points, URL-encoded, in order. |
destinations |
query | yes | Destination points, same format, up to 5 points. |
mode |
query | yes | Travel mode: driving, walking, or bicycling. |
200 response (DistanceMatrixResponse) — rows follow the order of origins, elements follow the order of
destinations:
{
"status": "OK",
"origin_addresses": ["Ayala Avenue, Makati", "Manila"],
"destination_addresses": ["Quezon City"],
"rows": [
{ "elements": [{ "status": "OK", "distance": { "text": "17.2 km", "value": 17200 }, "duration": { "text": "41 mins", "value": 2460 } }] },
{ "elements": [{ "status": "ZERO_RESULTS" }] }
]
}
DistanceMatrixResponse fields:
| Field | Description |
|---|---|
status |
OK, INVALID_REQUEST, MAX_ELEMENTS_EXCEEDED, MAX_DIMENSIONS_EXCEEDED, OVER_QUERY_LIMIT, OVER_DAILY_LIMIT, REQUEST_DENIED, UNKNOWN_ERROR |
rows[] |
Count = number of origins; always present, empty when status != OK |
rows[].elements[] |
Count = number of destinations |
elements[].status |
OK, NOT_FOUND, ZERO_RESULTS, MAX_ROUTE_LENGTH_EXCEEDED. Origin equal to destination → OK with distance/duration value 0. |
elements[].distance / duration |
{ text, value }, required when status = OK |
Error handling summary
| API group | Error style |
|---|---|
| Tiles | Standard HTTP status codes (204 empty tile, 404 not found) |
| Geocoding, Routing | Always HTTP 200; success/error reported in the JSON status field |
For Geocoding and Routing, always check status before reading the result — a non-OK status still returns
HTTP 200 with an empty (or absent) result array/rows.
Full reference
Each API's full reference (every field, every example) also has its own page:
Playground
Paste your own API key and call the real public endpoints directly from the browser, with results rendered on a live map: Open the playground →
For AI coding agents
llms.txt: a plain-markdown index of this API (specs, auth, a ready-to-use MapLibre GL JS snippet) meant for AI agents (Claude Code, Codex, Kilo Code, ...) to read directly - no HTML/JS parsing needed.llms-full.txt: the full copy-paste integration guide - working code for the basemap, autocomplete, place details, geocoding, reverse geocoding, directions (incl. TSP), and distance matrix. Paste the whole file into an agent's context (system prompt, CLAUDE.md/AGENTS.md, or straight into chat) and it has everything needed to build the integration in one pass.