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.