SDK · mapbase
Mapbase Engine API SDK
Resend-inspired ergonomics. { data, error, rateLimit } on every call. Types are generated from the engine's OpenAPI 3.1 contract — they cannot drift.
mapbase@1.0.1npm
terminal
bun add mapbase
# or: npm i mapbase / pnpm add mapbase / yarn add mapbase10k credit-weighted ops/mo, 1k/day — no card needed
Install
Types are generated from the engine OpenAPI 3.1 spec — they stay in sync with the published API contract.
terminal
bun add mapbase
# or: npm i mapbase / pnpm add mapbase / yarn add mapbaseGet an API key
All
/v1 routes require an API key except layers.list(). The SDK sends it as x-api-key on every request.- Sign up at dashboard.mapbase.dev (free tier available).
- Mint a key at API keys. Live keys use the
mb_live_prefix. - Pass it to the client constructor — you only see the full secret once at creation; keys are SHA-256 hashed at rest.
Optionally restrict each key to allowed domains in the dashboard. Do not ship live keys in browser bundles — call Mapbase from your server, or inject keys server-side (Next.js server action or API route).
Quickstart
The SDK exposes one namespace per OpenAPI tag —
locations, postcodes, lau, zones, zoneSubmissions, boundaries, points, autocomplete, seo, providers, and layers. Every method returns { data, error, rateLimit } — check error before using data.quickstart.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
const { data, error } = await mapbase.autocomplete.search({
q: "lis",
layers: ["locations"],
country: "PT",
limit: 5,
})
if (error) {
console.error(error.code, error.message)
} else {
for (const suggestion of data.results) {
console.log(suggestion.id, suggestion.name)
}
}Unified autocomplete
Search every layer in one call with
mapbase.autocomplete.search. Pass layers to narrow the scope, or omit it to rank across locations, postcodes, LAU, and zones.autocomplete.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
const { data, error } = await mapbase.autocomplete.search({
q: "rua do ouro",
country: "PT",
layers: ["locations", "postcodes", "lau", "zones"],
limit: 10,
})
if (error) {
console.error(error.code, error.message)
} else {
for (const suggestion of data.results) {
console.log(suggestion.id, "→", suggestion.label, suggestion.layer)
}
}Resolve a point
Turn WGS84 coordinates into administrative context with
mapbase.points.resolve — anchor location, depth-keyed hierarchy, LAU (with postcode codes), and containing zones.resolve.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
const { data, error } = await mapbase.points.resolve({
lng: -9.215,
lat: 38.691,
country: "PT",
})
if (error) {
console.error(error.code, error.message)
} else {
// Anchor is the finest administrative unit containing the point;
// hierarchy maps depth → parent chain; lau includes nested postcode codes.
if (data.anchor) {
console.log(data.anchor.id, "→", data.anchor.name)
}
console.log(data.hierarchy)
}Boundaries
Bulk-fetch encoded polylines with
mapbase.boundaries.list — filter by layer, country, level, bbox, or parent. Default output is format=polyline; pass format=geojson for GeoJSON (size-capped). For one known entity, use locations.boundary, zones.boundary, or lau.boundary instead.boundaries.ts
const { data, error } = await mapbase.boundaries.list({
layer: "locations",
country: "PT",
level: 4,
format: "polyline",
})Postcodes
Look up postal codes by code string with
mapbase.postcodes.byCode, or browse with list, get, and byPoint.postcodes.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
const { data, error } = await mapbase.postcodes.byCode("1100-062", {
country: "PT",
})
if (error) {
// `not_found` when no street_numbers row carries that postal code.
console.error(error.code, error.message)
} else {
console.log(data.code, "→", data.placeId, data.centroid)
}React hooks
Hooks live under the
mapbase/react subpath. Peer dependencies: @tanstack/react-query (required, >=5) and react (>=18, optional). Wrap your tree in MapbaseProvider once with a server-side key.| Hook | Purpose |
|---|---|
| useAutocomplete | Debounced typeahead via react-query; returns query, hits, loading, error, … |
| useBoundary | Fetch a boundary polygon by place id |
| usePointResolve | Resolve a coordinate or suggestion to taxonomy |
| useLayerCatalog | Public layer catalog with availability and count helpers |
search.tsx
import { MapbaseProvider, useAutocomplete } from "mapbase/react"
function Search() {
const { query, setQuery, hits, loading } = useAutocomplete({ country: "PT" })
return (
<div>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
{loading ? (
<span>…</span>
) : (
hits.map((h: { id: string; label: string }) => (
<div key={h.id}>{h.label}</div>
))
)}
</div>
)
}
function App() {
return (
<MapbaseProvider apiKey={process.env.MAPBASE_API_KEY!}>
<Search />
</MapbaseProvider>
)
}Place sources
Use
createPlaceSource(client, name) to build a PlaceSource for "mapbase" | "google" | "geoapify". Named factories: createMapbaseSource, createGoogleSource, createGeoapifySource. Google and Geoapify sources require the customer's provider key (googleApiKey / geoapifyApiKey).place-sources.ts
import { Mapbase, createPlaceSource } from "mapbase"
const client = new Mapbase(process.env.MAPBASE_API_KEY!)
const source = createPlaceSource(client, "mapbase")
const { hits } = await source.autocomplete("Lisb", { country: "PT" })
for (const hit of hits) {
console.log(hit.id, hit.label)
}Provider passthroughs
Call Google or Geoapify through Mapbase with your own provider keys. Google exposes autocomplete, place details, geocode, and reverse-geocode. Geoapify exposes autocomplete and boundaries. Google responses are typed as
unknown until the upstream shape is frozen. See also Google Places vs Mapbase and Geoapify vs Mapbase.providers-passthrough.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
// Google — autocomplete, place, geocode, reverseGeocode.
// Pass your Google key per request; responses are typed as unknown.
const { data: googleHits, error: googleError } =
await mapbase.providers.google.autocomplete(
{ q: "Lisbon", country: "PT", limit: 5 },
{ googleApiKey: process.env.GOOGLE_API_KEY! },
)
if (googleError) throw googleError
// Geoapify — autocomplete and boundaries.
const { data: geoapifyHits, error: geoapifyError } =
await mapbase.providers.geoapify.autocomplete(
{ q: "Lisbon", country: "PT", limit: 5 },
{ geoapifyApiKey: process.env.GEOAPIFY_API_KEY! },
)
if (geoapifyError) throw geoapifyError
console.log(googleHits, geoapifyHits.results.length)Geometry helpers
Polyline and bbox helpers ship on the main
mapbase export. Coordinates are [lng, lat] (GeoJSON order). Use decodeGooglePolyline for precision-5 encoded rings, polylinesToGeoJson to assemble polygon geometry, and bboxToPolygonCoordinates to close a bbox ring for MapLibre.geometry-helpers.ts
import {
bboxToPolygonCoordinates,
decodeGooglePolyline,
polylinesToGeoJson,
} from "mapbase"
const encoded = "encoded_google_polyline_string"
// Coordinates are [lng, lat] (GeoJSON order).
const ring = decodeGooglePolyline(encoded)
// First polygon geometry from encoded rings, or null.
const geometry = polylinesToGeoJson([encoded])
// Closed ring from a [minLng, minLat, maxLng, maxLat] bbox.
const bboxRing = bboxToPolygonCoordinates([-9.5, 38.5, -9.0, 39.0])
console.log(ring.length, geometry?.type, bboxRing[0]?.length)Retry
Retry is off by default (
attempts: 0). Opt in at client construction or per request. By default only GET requests are retried (429, 502, 503, 504, and transport failures). POST, PATCH, and DELETE require retry: { idempotent: true } — do not set that on zoneSubmissions writes unless the engine guarantees idempotency.retry.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
await mapbase.locations.get("0-EU-ES", {}, { retry: false })
await mapbase.autocomplete.search(
{ q: "lis", layers: ["locations"] },
{ retry: { attempts: 2 } },
)Cursor pagination
List namespaces expose async iterators that walk every page via
meta.next_cursor. Top-level collections use listAll on locations, postcodes, lau, and zones; id-scoped related lists use {relation}All (for example lau.postcodesAll). Unlike single-page list() methods, iterators throw MapbaseError when a page request fails.list-all.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
for await (const location of mapbase.locations.listAll({ country: "PT" })) {
console.log(location.name)
}
for await (const postcode of mapbase.postcodes.listAll({ country: "PT" })) {
console.log(postcode.code)
}Errors
HTTP errors come back as a typed
MapbaseError on the error field. Network failures surface as network_error. Switch on error.code to handle each failure mode explicitly.error-handling.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
const { data, error, rateLimit } = await mapbase.locations.get("0-EU-ES")
if (error) {
console.error(error.code, error.message, rateLimit.remaining)
} else {
console.log(data.name, data.country)
}Rate limit headers
Every response — success or error — exposes the parsed
RateLimit-* and Retry-After headers as rateLimit. Use it to pace concurrent calls or back off on 429s.rate-limit.ts
import { Mapbase } from "mapbase"
const mapbase = new Mapbase(process.env.MAPBASE_API_KEY!)
const { rateLimit } = await mapbase.autocomplete.search({
q: "lis",
layers: ["locations"],
})
console.log(rateLimit.remaining, rateLimit.limit)API surface
Each namespace maps to a tag in the engine's OpenAPI spec. Override the default base URL with { baseUrl: "https://api.mapbase.dev" } if needed.
| Namespace | Methods | Notes |
|---|---|---|
| locations | list, listAll, get, relations, boundary, zones | Administrative locations |
| postcodes | list, listAll, get, byPoint, byCode | Postal codes |
| lau | list, listAll, get, boundary, postcodes, postcodesAll | EU LAU regions |
| zones | list, listAll, get, boundary | Read-only; writes via zoneSubmissions |
| zoneSubmissions | create, list, get, update, submit, delete | User-scoped via API key; two keys for the same user share submissions |
| boundaries | list | Bulk boundary fetch with spatial and registry filters |
| points | resolve | Point-in-polygon resolve |
| autocomplete | search | Unified search across every layer |
| seo | page, tree, sitemap | SEO page bundles, directory trees, sitemap entries |
| layers | list | Registry layers with live row counts; no API key required |
| providers.google | autocomplete, place, geocode, reverseGeocode | Live passthroughs; pass your Google key via googleApiKey in request options; responses typed as unknown |
| providers.geoapify | autocomplete, boundaries | Live passthroughs; pass your Geoapify key via geoapifyApiKey in request options |
Product stack