Maimaps API · v1 · Beta

Maimaps API

Map display, place search, routing, reverse geocoding, point details, and loccode resolution for Nigerian apps — one API key across the REST and map-display surfaces.

Version

/sdk/v1

Protocol

REST / JSON + vector tiles

Auth

API key — X-Api-Key header or ?key= param

Key formats

mk_live_… / mk_test_…

Coverage

Nigeria

Pricing

Free tier (metered)

Overview

The Maimaps API powers full map experiences: render vector maps, search places and addresses, compute drive/walk/cycle routes, reverse-geocode coordinates, resolve tapped points, and look up Maiaddy loccodes — all authenticated with a single MAIMAPS API key.

Two hosts, one key

  • /sdk/v1/* on the API gateway — REST endpoints for search, routing, geocoding, point details, and loccodes.
  • /sdk/styles · /sdk/tiles · /sdk/sprites · /sdk/fonts on the map host — style documents, vector tiles, and glyphs for map display.

The SDKs hide the split behind one options object: environment: 'staging' | 'production' selects baked-in host defaults. Production hostnames are published before GA; until then, production requires explicit hosts.

Every successful REST response uses a stable envelope:

Response envelope
{ "status": "success", "message": "...", "data": { ... } }

Route responses are OSRM-shaped (routes[].distance|duration|geometry|legs); polylines use precision 5. /sdk/v1 is the stable contract — breaking changes require /sdk/v2; additive fields are non-breaking.

Quickstart

From zero to a rendered map with your first search and route in three steps.

  1. 1

    Create a MAIMAPS key

    In the console, create an API key for the Maimaps product. TEST keys (mk_test_…) are free to burn during development; LIVE keys (mk_live_…) carry the full monthly caps.

  2. 2

    Install an SDK

    Official SDKs for JavaScript, React, React Native, and Flutter wrap every endpoint, inject your key, and ship typed errors and retries.

  3. 3

    Render a map, then search and route

    Load the keyed style.json into MapLibre (one map load per fetch), then call search and route for your first results.

Replace mk_test_your_key with a key from the console.

Request
import { MaimapsClient } from "@maimaps/js";
import maplibregl from "maplibre-gl";

const client = new MaimapsClient({ apiKey: "mk_test_your_key" });

// 1. Render a map (each keyed style.json fetch = one map load)
const map = new maplibregl.Map({
  container: "map",
  style: client.styleUrl({ mode: "dark" }),
  transformRequest: (url) => ({ url: client.transformMapResource(url) }),
});

// 2. First search
const results = await client.search({
  q: "garki market",
  latitude: 9.05,
  longitude: 7.49,
});

// 3. First route (OSRM-shaped: routes[].distance|duration|geometry|legs)
const trip = await client.route({
  originLat: 9.05,
  originLng: 7.49,
  destLat: 6.45,
  destLng: 3.39,
  mode: "drive",
});

Authentication

Keys are minted in this portal for the MAIMAPS product: mk_live_… for the LIVE environment and mk_test_… for TEST. Pass the key with every request — as the X-Api-Key header (preferred for REST calls) or as a ?key= query parameter (required for map resources, which cannot set headers portably).

X-Api-Key header (REST)
GET /sdk/v1/search?q=garki%20market HTTP/1.1
Host: maps-staging-api.maiaddy.com
X-Api-Key: mk_test_your_key
?key= query param (map resources)
# Map resources can't set headers portably — pass the key as a query param.
GET /sdk/styles/maimaps/style.json?mode=dark&key=mk_test_your_key

TEST vs LIVE

TEST keys behave identically but carry ~10% of LIVE monthly limits; usage is recorded with environment=TEST. Use TEST keys for development and CI, then switch the key string for launch.

Keys are not secrets — but they are quota-bearing

Maimaps keys ship inside client apps by design. Treat them like quota handles, not credentials: rotate from the console if abused. Origin / bundle-ID restrictions arrive at GA hardening.

Rate limits & quotas

The free tier meters every billable dimension from day one. LIVE keys get the limits below; TEST keys share the same per-second rates with 10% of the monthly caps.

DimensionRate limit (per key)Monthly cap
Map loads10/min50,000
Tiles600/minnot billed; 150,000/day soft ceiling
Search10 rps, burst 20100,000
Routing5 rps, burst 1050,000
Reverse geocode10 rps, burst 20100,000
Point details10 rps, burst 20100,000
Loccode10 rps, burst 20100,000
  • Tiles are never billed — the map load is the display unit; tile limits exist purely as an abuse guard.
  • Per-second limits are enforced at the edge; monthly caps are computed from usage rollups, and an over-quota key is switched off at the next validation refresh (≤ 5 minutes).
  • Every limited route returns X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers, plus Retry-After on 429.

Errors

Failures return a stable error body with a machine-readable code:

Error body
{ "status": "error", "code": "rate_limited", "message": "Per-key rate limit hit. Retry after 2 seconds." }
HTTPCodeMeaning
401invalid_api_keyMissing, malformed, revoked, or wrong-product key.
403restriction_violation(Post-GA) origin/bundle restriction failed.
429rate_limitedPer-key rate limit hit — honor Retry-After.
429quota_exceededMonthly cap reached for the dimension.
503auth_unavailableKey validation backend unreachable and key not in stale cache.

The SDKs raise typed errors (InvalidApiKeyError, RateLimitError(retryAfter), QuotaExceededError) and automatically retry idempotent GETs only, honoring Retry-After.

REST endpoints

All REST endpoints live under /sdk/v1 on the API gateway and accept the X-Api-Key header.

MethodPathPurpose
GET/sdk/v1/searchFree-text place/address/POI/loccode search. Params: q (required), latitude, longitude, limit, bbox, type, category.
GET/sdk/v1/routeSimple A→B routing. Params: origin_lat, origin_lng, dest_lat, dest_lng, mode (drive | walk | cycle).
POST/sdk/v1/routeWaypoints + loccode origins/destinations. Body: {origin_lat/lng | origin_loccode, dest_lat/lng | dest_loccode, waypoints[], mode}.
GET/sdk/v1/reverse-geocodeCoordinates → address + loccode. Params: latitude, longitude.
GET/sdk/v1/point-detailsTapped-point resolution. Params: latitude, longitude, osm_id?, osm_type?, search_name?, search_address?.
GET/sdk/v1/loccode/{code}Loccode → coordinates/geometry/metadata.
GET/sdk/v1/loccodes/nearestNearest loccode. Params: latitude, longitude.
GET/sdk/v1/place-categoriesCategory taxonomy (tracked, unbilled).
GET

Map display endpoints

/sdk/styles · /sdk/tiles · /sdk/sprites · /sdk/fonts

Map assets are served from the map host and authenticate with the ?key= query parameter. The style.json references the /sdk/* tile/sprite/glyph URLs; the SDKs append the key via transformRequest / transformMapResource.

PathPurpose
GET /sdk/styles/{family}/style.json?mode={light|dark}&key=…Style document — each keyed fetch counts as one map load.
GET /sdk/tiles/{z}/{x}/{y}.pbf?key=…Vector tiles.
GET /sdk/tiles.json?key=…TileJSON.
GET /sdk/sprites/…?key=… · GET /sdk/fonts/…?key=…Sprites / glyphs.

Map loads, not tiles

Each keyed style.json fetch counts as one map load — the metered display unit. Tile requests are rate-limited but never billed.

SDKs

Official Maimaps SDKs for JavaScript, React, React Native, and Flutter wrap every endpoint, handle key injection on both hosts, and ship typed errors with rate-limit-aware retries.

Browse SDKs