{"openapi":"3.1.0","info":{"title":"MapScale API","version":"1.0.0"},"servers":[{"url":"https://api.mapscale.io"},{"url":"http://localhost:3110"}],"components":{"schemas":{"RouteResponse":{"type":"object","properties":{"code":{"type":"string","enum":["Ok"]},"routes":{"type":"array","items":{"type":"object","properties":{"distance":{"type":"number","description":"Meters"},"duration":{"type":"number","description":"Seconds"},"geometry":{"$ref":"#/components/schemas/RouteGeometry"},"legs":{"type":"array","items":{"type":"object","properties":{"distance":{"type":"number"},"duration":{"type":"number"}},"required":["distance","duration"]}}},"required":["distance","duration","geometry","legs"]}},"waypoints":{"type":"array","items":{"type":"object","properties":{"location":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]}},"required":["location"]}}},"required":["code","routes","waypoints"]},"RouteGeometry":{"type":"object","properties":{"type":{"type":"string","enum":["LineString"]},"coordinates":{"type":"array","items":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]}}},"required":["type","coordinates"]},"RoutingError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]},"RouteRequest":{"type":"object","properties":{"coordinates":{"type":"array","items":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]},"minItems":2},"profile":{"type":"string","enum":["driving","walking","cycling"],"default":"driving","description":"Routing costing profile.","example":"driving"},"alternatives":{"type":"boolean"},"lang":{"type":"string"}},"required":["coordinates"]},"MatrixResponse":{"type":"object","properties":{"code":{"type":"string","enum":["Ok"]},"durations":{"type":"array","items":{"type":"array","items":{"type":"number","nullable":true,"description":"null when Valhalla reports this source/target pair unreachable"}},"description":"Seconds"},"distances":{"type":"array","items":{"type":"array","items":{"type":"number","nullable":true,"description":"null when Valhalla reports this source/target pair unreachable"}},"description":"Meters"},"sources":{"type":"array","items":{"type":"object","properties":{"location":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]}},"required":["location"]}},"destinations":{"type":"array","items":{"type":"object","properties":{"location":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]}},"required":["location"]}}},"required":["code","durations","distances","sources","destinations"]},"MatrixRequest":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]},"minItems":1},"targets":{"type":"array","items":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order.","example":[100.5018,13.7563]},"minItems":1},"profile":{"type":"string","enum":["driving","walking","cycling"],"default":"driving","description":"Routing costing profile.","example":"driving"}},"required":["sources","targets"]},"IsochroneResponse":{"type":"object","properties":{"type":{"type":"string","enum":["FeatureCollection"]},"features":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["Feature"]},"properties":{"type":"object","properties":{"contour":{"type":"number"},"metric":{"type":"string","enum":["time","distance"]}},"required":["contour","metric"]},"geometry":{"type":"object","properties":{"type":{"type":"string","enum":["Polygon","MultiPolygon"]},"coordinates":{"type":"array","items":{"nullable":true}}},"required":["type","coordinates"]}},"required":["type","properties","geometry"]}}},"required":["type","features"]},"GeocodeFeatureCollection":{"type":"object","properties":{"type":{"type":"string","enum":["FeatureCollection"]},"features":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeFeature"}}},"required":["type","features"]},"GeocodeFeature":{"type":"object","properties":{"type":{"type":"string","enum":["Feature"]},"id":{"type":"string"},"text":{"type":"string"},"place_name":{"type":"string"},"geometry":{"type":"object","properties":{"type":{"type":"string","enum":["Point"]},"coordinates":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2,"description":"[lng, lat], GeoJSON coordinate order."}},"required":["type","coordinates"]},"properties":{"$ref":"#/components/schemas/PlaceDetails"}},"required":["type","id","text","place_name","geometry","properties"]},"PlaceDetails":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"},"formattedAddress":{"type":"string"},"street":{"type":"string"},"neighborhood":{"type":"string"},"subdistrict":{"type":"string"},"district":{"type":"string"},"province":{"type":"string"},"postalCode":{"type":"string"},"country":{"type":"string"}},"required":["latitude","longitude","formattedAddress","street","neighborhood","subdistrict","district","province","postalCode","country"]},"GeocodeError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]},"AutocompleteResponse":{"type":"object","properties":{"predictions":{"type":"array","items":{"$ref":"#/components/schemas/AutocompletePrediction"}}},"required":["predictions"]},"AutocompletePrediction":{"type":"object","properties":{"place_id":{"type":"string"},"description":{"type":"string"},"structured_formatting":{"type":"object","properties":{"main_text":{"type":"string"},"secondary_text":{"type":"string"}},"required":["main_text","secondary_text"]}},"required":["place_id","description","structured_formatting"]},"AdminAreaResponse":{"type":"object","properties":{"subdistrict":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"}},"required":["id","name"]},"district":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"}},"required":["id","name"]},"province":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"}},"required":["id","name"]},"country":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"}},"required":["id","name"]}},"required":["subdistrict","district","province","country"]}},"parameters":{}},"paths":{"/v1/health":{"get":{"tags":["health"],"summary":"Liveness probe — always 200, no dependency checks","responses":{"200":{"description":"The service is running","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]}},"required":["status"]}}}}}}},"/v1/status":{"get":{"tags":["health"],"summary":"Aggregate Valhalla/Photon/Postgres dependency status","responses":{"200":{"description":"Aggregate status — 200 even when a dependency is down; check the body","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"valhalla":{"type":"string","enum":["ok","down","unconfigured"]},"photon":{"type":"string","enum":["ok","down","unconfigured"]},"postgres":{"type":"string","enum":["ok","down","unconfigured"]}},"required":["status","valhalla","photon","postgres"]}}}}}}},"/v1/route":{"get":{"tags":["routing"],"description":"Route between 2+ waypoints. Backed by Valhalla /route. Billing SKU: routing.","parameters":[{"schema":{"type":"string","description":"Semicolon-separated \"lng,lat\" pairs, e.g. \"100.50,13.75;100.57,13.74\".","example":"100.5018,13.7563;100.5685,13.7367"},"required":true,"description":"Semicolon-separated \"lng,lat\" pairs, e.g. \"100.50,13.75;100.57,13.74\".","name":"coordinates","in":"query"},{"schema":{"type":"string","enum":["driving","walking","cycling"],"default":"driving","description":"Routing costing profile.","example":"driving"},"required":false,"description":"Routing costing profile.","name":"profile","in":"query"},{"schema":{"type":"boolean","nullable":true},"required":false,"name":"alternatives","in":"query"},{"schema":{"type":"string"},"required":false,"name":"lang","in":"query"}],"responses":{"200":{"description":"Route computed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}},"502":{"description":"Upstream routing engine failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}}}},"post":{"tags":["routing"],"description":"Route between 2+ waypoints. Backed by Valhalla /route. Billing SKU: routing.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteRequest"}}}},"responses":{"200":{"description":"Route computed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}},"502":{"description":"Upstream routing engine failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}}}}},"/v1/matrix":{"post":{"tags":["routing"],"description":"Travel-time/distance matrix, capped at 25x25 in v1. Backed by Valhalla /sources_to_targets. Billing SKU: matrix (per element).","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixRequest"}}}},"responses":{"200":{"description":"Matrix computed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatrixResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}},"502":{"description":"Upstream routing engine failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}}}}},"/v1/isochrone":{"get":{"tags":["routing"],"description":"Time/distance reachability polygons around a point. Backed by Valhalla /isochrone. Billing SKU: routing.","parameters":[{"schema":{"type":"string","description":"\"lng,lat\"","example":"100.5018,13.7563"},"required":true,"description":"\"lng,lat\"","name":"point","in":"query"},{"schema":{"type":"string","enum":["driving","walking","cycling"],"default":"driving","description":"Routing costing profile.","example":"driving"},"required":false,"description":"Routing costing profile.","name":"profile","in":"query"},{"schema":{"type":"string","description":"Comma-separated contour values. Minutes by default; suffix a value with \"km\" for a distance contour. e.g. \"5,10,15\" or \"1km,2km\".","example":"5,10,15"},"required":true,"description":"Comma-separated contour values. Minutes by default; suffix a value with \"km\" for a distance contour. e.g. \"5,10,15\" or \"1km,2km\".","name":"contours","in":"query"}],"responses":{"200":{"description":"Isochrone computed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IsochroneResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}},"502":{"description":"Upstream routing engine failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingError"}}}}}}},"/v1/geocode":{"get":{"tags":["geocode"],"description":"Forward geocode. Backed by Photon /api. Billing SKU: geocode.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"q","in":"query"},{"schema":{"type":"string"},"required":false,"name":"lang","in":"query"},{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":50},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","example":"100.3,13.6,100.9,14.0"},"required":false,"name":"bbox","in":"query"},{"schema":{"type":"string","example":"100.5018,13.7563"},"required":false,"name":"proximity","in":"query"},{"schema":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2"},"required":false,"description":"ISO 3166-1 alpha-2","name":"country","in":"query"}],"responses":{"200":{"description":"Geocode results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeFeatureCollection"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}}}}},"/v1/reverse":{"get":{"tags":["geocode"],"description":"Reverse geocode. Backed by Photon /reverse, enriched with PostGIS admin-area names. Billing SKU: geocode.","parameters":[{"schema":{"type":"string","example":"100.5018,13.7563"},"required":true,"name":"point","in":"query"},{"schema":{"type":"string"},"required":false,"name":"lang","in":"query"},{"schema":{"type":"integer","nullable":true},"required":false,"name":"zoom","in":"query"}],"responses":{"200":{"description":"Reverse geocode result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeFeatureCollection"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}}}}},"/v1/autocomplete":{"get":{"tags":["geocode"],"description":"Autocomplete suggestions. Backed by Photon (v1) -> ES phonetic (v1.5). Billing SKU: autocomplete (per session, not per keystroke).","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"q","in":"query"},{"schema":{"type":"string"},"required":false,"name":"lang","in":"query"},{"schema":{"type":"string","description":"Client-generated session id. Billed once per session, not per keystroke."},"required":false,"description":"Client-generated session id. Billed once per session, not per keystroke.","name":"session_token","in":"query"},{"schema":{"type":"string"},"required":false,"name":"bbox","in":"query"},{"schema":{"type":"string"},"required":false,"name":"proximity","in":"query"},{"schema":{"type":"string"},"required":false,"name":"types","in":"query"}],"responses":{"200":{"description":"Autocomplete predictions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutocompleteResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}}}}},"/v1/place/{id}":{"get":{"tags":["geocode"],"description":"Place details, by a photon:... id (from /v1/geocode or /v1/autocomplete) or a mapscale places.places uuid. Billing SKU: geocode (free if same session).","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string"},"required":false,"name":"lang","in":"query"}],"responses":{"200":{"description":"Place details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceDetails"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}}}}},"/v1/admin-areas/reverse":{"get":{"tags":["geocode"],"description":"Point-in-polygon admin-area lookup over geography.* (PostGIS ST_Contains). Billing SKU: geocode.","parameters":[{"schema":{"type":"string","example":"100.5018,13.7563"},"required":true,"name":"point","in":"query"},{"schema":{"type":"string"},"required":false,"name":"lang","in":"query"}],"responses":{"200":{"description":"Admin area containing the point","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAreaResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeError"}}}}}}},"/tiles/v1/{tileset}/{z}/{x}/{yFile}":{"get":{"tags":["tiles"],"parameters":[{"schema":{"type":"string","example":"world","description":"Tileset id. Only `world` is available in v1."},"required":true,"description":"Tileset id. Only `world` is available in v1.","name":"tileset","in":"path"},{"schema":{"type":"integer","nullable":true,"minimum":0,"maximum":14,"example":10},"required":false,"name":"z","in":"path"},{"schema":{"type":"integer","nullable":true,"minimum":0,"example":817},"required":false,"name":"x","in":"path"},{"schema":{"type":"string","example":"479.mvt","description":"\"{y}.mvt\" — the y coordinate and literal extension in one path segment."},"required":true,"description":"\"{y}.mvt\" — the y coordinate and literal extension in one path segment.","name":"yFile","in":"path"}],"responses":{"200":{"description":"A Mapbox Vector Tile from the requested PMTiles archive.","content":{"application/vnd.mapbox-vector-tile":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Tile or tileset not found.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"PMTiles archive is not configured.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/styles/v1/{styleId}":{"get":{"tags":["styles"],"parameters":[{"schema":{"type":"string","example":"streets"},"required":true,"name":"styleId","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":8,"example":"th","description":"Locale for label text (per-locale name:{lang} coalesce)."},"required":false,"description":"Locale for label text (per-locale name:{lang} coalesce).","name":"lang","in":"query"},{"schema":{"type":"string","example":"0c4a4a","description":"Brand accent retint, as a 6-digit hex color (# optional)."},"required":false,"description":"Brand accent retint, as a 6-digit hex color (# optional).","name":"accent","in":"query"},{"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}],"example":"transit,water","description":"Layer groups to hide — repeatable or comma-separated."},"required":false,"description":"Layer groups to hide — repeatable or comma-separated.","name":"hide","in":"query"}],"responses":{"200":{"description":"A MapLibre style document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"MapLibre StyleSpecification JSON document."}}}}}}},"/fonts/v1/{fontstack}/{rangeFile}":{"get":{"tags":["fonts"],"parameters":[{"schema":{"type":"string","example":"Rubik Regular,Mitr Regular"},"required":true,"name":"fontstack","in":"path"},{"schema":{"type":"string","example":"0-255.pbf","description":"\"{range}.pbf\" — the range and the literal extension in one path segment."},"required":true,"description":"\"{range}.pbf\" — the range and the literal extension in one path segment.","name":"rangeFile","in":"path"}],"responses":{"302":{"description":"Redirect to the glyph asset host."},"503":{"description":"Asset host not configured yet.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/sprites/v1/{styleId}/{filename}":{"get":{"tags":["sprites"],"parameters":[{"schema":{"type":"string","example":"streets"},"required":true,"name":"styleId","in":"path"},{"schema":{"type":"string","example":"sprite@2x.png"},"required":true,"name":"filename","in":"path"}],"responses":{"302":{"description":"Redirect to the sprite asset host."},"503":{"description":"Asset host not configured yet.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/v1/static/{lonLatZoom}/{sizeFile}":{"get":{"tags":["static"],"parameters":[{"schema":{"type":"string","example":"100.523,13.736,14","description":"\"{lon},{lat},{zoom}\" as a single comma-separated path segment."},"required":true,"description":"\"{lon},{lat},{zoom}\" as a single comma-separated path segment.","name":"lonLatZoom","in":"path"},{"schema":{"type":"string","example":"600x400@2x.png","description":"\"{w}x{h}{@2x}.png\" as a single path segment."},"required":true,"description":"\"{w}x{h}{@2x}.png\" as a single path segment.","name":"sizeFile","in":"path"},{"schema":{"type":"string","example":"streets"},"required":false,"name":"styleId","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":8},"required":false,"name":"lang","in":"query"},{"schema":{"type":"string"},"required":false,"name":"accent","in":"query"},{"schema":{"type":"string","maxLength":4096},"required":false,"name":"markers","in":"query"},{"schema":{"type":"string","maxLength":4096},"required":false,"name":"path","in":"query"}],"responses":{"200":{"description":"A rendered PNG static map."},"400":{"description":"Invalid coordinates, zoom, or size.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"404":{"description":"Unknown styleId.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"502":{"description":"The render service returned an error.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"The render service is unreachable or not configured.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}}}},"/v1/trial-key":{"post":{"tags":["keys"],"summary":"Create a limited publishable trial key","description":"Creates a trial account and a browser-safe `pk_test_*` key for client-side MapScale integration. The key is restricted to tiles, autocomplete and static maps.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"companyName":{"type":"string","minLength":2,"maxLength":120},"allowedOrigin":{"type":"string","format":"uri"}},"required":["email","companyName"]}}}},"responses":{"201":{"description":"Trial account and key created. The raw key is returned once.","content":{"application/json":{"schema":{"type":"object","properties":{"accountId":{"type":"string"},"key":{"type":"string"},"keyPrefix":{"type":"string"},"enabledProducts":{"type":"array","items":{"type":"string"}},"limits":{"type":"object","properties":{"rateLimitTier":{"type":"string","enum":["trial"]},"note":{"type":"string"}},"required":["rateLimitTier","note"]},"examples":{"type":"object","properties":{"styleUrl":{"type":"string"},"autocompleteUrl":{"type":"string"}},"required":["styleUrl","autocompleteUrl"]}},"required":["accountId","key","keyPrefix","enabledProducts","limits","examples"]}}}},"400":{"description":"Invalid request body"},"429":{"description":"Too many trial key requests from this IP"}}}}}}