API reference
Every endpoint of the N-Ways Maps Platform, its parameters, its answer and its errors, generated from our OpenAPI files.
On this page
N-Ways Maps Platform — Maps, Places and Routes API
The stable partner surface of the N-Ways Maps Platform (/maps/v1). It is the source of truth for the developer docs and the client libraries; the server's golden-file contract tests (backend/internal/partnerapi/testdata/golden) pin every answer described here.
Auth. Every call needs a partner API key: X-NWays-Api-Key: nw_… (servers, apps) or ?key=nw_… (browsers, map styles). A key's API restrictions name products; its application restriction (websites, iOS bundle ids, Android package + SHA-1, server IP addresses) says where it may be used. Apps send X-Ios-Bundle-Identifier, or X-Android-Package and X-Android-Cert. Keys work only while N-Ways has the platform switched on: while it is off every call answers 503 PLATFORM_DISABLED (with Retry-After; your key is fine), and while N-Ways keeps it to invited organisations, a key of any other organisation answers 403 PLATFORM_PREVIEW_ONLY.
Products and billing (SKUs). tiles (map tiles; SKUs tiles per tile and map_loads per style read), static_maps, geocode (geocoding, reverse, postcodes, GhanaPost, Plus Codes; a batch counts each code), autocomplete (billed per session), address_validation, search (places; SKUs search basic, places_contact, places_atmosphere by the fields asked for), routing (SKUs routing, and routing_advanced for traffic-aware routes, okada/keke/danfo/motorbike/van/boat/train, more than 10 intermediates or fares), route_matrix (billed per element), route_optimization, fares, traffic. Fonts are free with any Maps key.
Conventions. Coordinates go in as {"lat": 6.45, "lng": 3.39} (or lat,lng in a query) and come out as GeoJSON [lng, lat]. Distances in metres, durations in seconds, money in the currency's main unit. lang=en|fr|pt (pt is European Portuguese). fields= (a comma list; a.b reaches into objects) keeps only those fields of each result and, for places, sets the SKU. Fields are only ever added within v1; breaking changes come as /maps/v2 with 12 months' overlap.
Errors. One envelope for key, quota and product errors: {"error": {"code": 400, "status": "INVALID_ARGUMENT", "reason": "LAT_LNG_REQUIRED", "message": "…", "details": […]}}. reason is stable (act on it); message says exactly what happened and how to fix it; details holds Google's ErrorInfo (the reason, domain: nways.ng, and metadata with the facts: docs, and per reason what was seen and what's allowed, which cap and when it resets) and a Help link to the reason's entry on https://nways.ng/developers/errors. Refusals carry CORS headers for the calling page, so browser code can read them. Key, platform and quota reasons, in the order they are checked: PLATFORM_DISABLED (503), API_KEY_MISSING, API_KEY_INVALID (a key N-Ways never issued), API_KEY_REVOKED (deleted, or the old value of a regenerated key) (401); PLATFORM_PREVIEW_ONLY, ORGANISATION_CLOSED, ORGANISATION_SUSPENDED, API_ACCESS_OFF (the organisation runs its deliveries on N-Ways without the API), API_TURNED_OFF (N-Ways turned the organisation's API off; the message gives the reason), PROJECT_SUSPENDED, API_KEY_PAUSED, API_KEY_SERVICE_BLOCKED, API_KEY_HTTP_REFERRER_BLOCKED, API_KEY_IP_ADDRESS_BLOCKED, API_KEY_IOS_APP_BLOCKED, API_KEY_ANDROID_APP_BLOCKED (403); RATE_LIMIT_EXCEEDED, QUOTA_EXCEEDED, TEMPORARILY_BLOCKED (429); BILLING_DISABLED (402: the free monthly allowance is used up and there's no credit left: add credit in the console, Billing); BACKEND_ERROR (503). Product reasons are listed with each operation. Any operation may also answer NOT_FOUND (an unknown path), INVALID_JSON and BODY_TOO_LARGE (a request body), INTERNAL (500: send the X-Request-Id) and, where routes are planned, ROUTING_UNAVAILABLE (503, with Retry-After). Okada, motorbike, keke and danfo trips the law keeps off answer 422 with OKADA_BANNED_ORIGIN, OKADA_BANNED_DESTINATION, OKADA_BANNED_STOP, OKADA_BANNED_ROUTE; MOTORBIKE_BANNED_ORIGIN, MOTORBIKE_BANNED_DESTINATION, MOTORBIKE_BANNED_STOP, MOTORBIKE_BANNED_ROUTE; KEKE_BANNED_ORIGIN, KEKE_BANNED_DESTINATION, KEKE_BANNED_STOP, KEKE_BANNED_ROUTE; DANFO_BANNED_ORIGIN, DANFO_BANNED_DESTINATION, DANFO_BANNED_STOP, DANFO_BANNED_ROUTE (the ban is in the message).
Headers. Every answer carries X-Request-Id (send it to support). Allowed calls carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds); a 429 and PLATFORM_DISABLED carry Retry-After.
Hosts. api.nways.ng/maps/v1 for everything. Hosted styles read the PMTiles archives and fonts from the CDN tiles.nways.ng/maps/v1 (/tiles/{tileset}.pmtiles, /tiles/countries.json, /fonts/…, same paths): a public copy, with no key there and nothing counted; your key is checked and the map load counted when the style is read. One-tile .mvt URLs stay on the API, keyed. When the routing engine is down, routes, matrices and optimisation answer 503 ROUTING_UNAVAILABLE with Retry-After: send the same request again.
Rate limits (defaults per key and minute): tiles 6,000, static_maps 600, geocode 600, autocomplete 1,200, address_validation 300, search 600, routing 300, route_matrix 3,000 elements, route_optimization 60, fares 120, traffic 60.
Attribution. Answers built on OpenStreetMap carry attribution; show "© OpenStreetMap contributors" wherever you show the data (the ODbL requires it). See https://nways.ng/developers/terms.
| Base address | https://nways.ng/maps/v1 |
| Version | 1.0.0 |
| OpenAPI file | partner-v1.openapi.yaml · JSON |
| Endpoints | Description |
|---|---|
GET /styles/{style}.json | A hosted map style (one map load) |
GET /tiles/countries.json | Every country's tile archives |
GET /tiles/{tileset}.pmtiles | Read a country's PMTiles archive (range requests) |
GET /tiles/{tileset}/{z}/{x}/{y}.mvt | One vector tile |
GET /fonts/{stack}/{range}.pbf | Font glyphs for map labels (free) |
GET /staticmap | A map image |
GET /geocode | Search for places, addresses and codes |
GET /reverse | What's at a point |
GET /postcodes/{code} | A NIPOST postcode |
POST /postcodes/batch | Up to 100 NIPOST postcodes at once |
GET /ghanapost/{code} | A GhanaPost GPS address |
GET /pluscodes/{code} | A Plus Code's area and what's there |
GET /autocomplete | Suggestions as someone types (billed per session) |
POST /addresses/validate | Is this address real, where is it, what is it properly? |
GET /places/nearby | Businesses and places near a point |
GET /places/search | Businesses and places by text |
GET /places/{id} | One place's details |
POST /routes/compute | A route, with up to 25 intermediates |
POST /routes/matrix | Travel times from many origins to many destinations (per element) |
POST /routes/optimize | Which vehicle visits which stops, in what order |
GET /fares/estimate | What a ride costs |
GET /traffic | Live traffic in an area |
N-Ways Maps Platform — Fleet and logistics API
The logistics suite of the N-Ways Maps Platform (partner products fleet and traffic_feed). Every call needs a partner API key that includes the product; keys work only while N-Ways has partner access switched on for you.
Auth. Send the key as X-NWays-Api-Key: nw_… (servers, rider apps) or ?key=nw_…. Fleet calls write your own data (vehicles, tasks, webhooks), so use a key restricted to your servers' IP addresses or your rider app (Android package + SHA-1, iOS bundle id). Browsers: only the public tracking page.
Errors. Key and quota errors and product errors share one envelope: {"error": {"code": 404, "status": "NOT_FOUND", "reason": "TASK_NOT_FOUND", "message": "…", "details": […]}} (details: Google's ErrorInfo with the facts in metadata and a Help link to the reason on https://nways.ng/developers/errors). Platform and key reasons: PLATFORM_DISABLED (503, Retry-After: the platform is switched off, your key is fine), PLATFORM_PREVIEW_ONLY, API_KEY_MISSING, API_KEY_INVALID, API_KEY_REVOKED, ORGANISATION_CLOSED, ORGANISATION_SUSPENDED, API_ACCESS_OFF, API_TURNED_OFF, PROJECT_SUSPENDED, API_KEY_PAUSED, API_KEY_SERVICE_BLOCKED, API_KEY_*_BLOCKED, BILLING_DISABLED, RATE_LIMIT_EXCEEDED, QUOTA_EXCEEDED, TEMPORARILY_BLOCKED, BACKEND_ERROR. BILLING_DISABLED is a 402: the free monthly allowance is used up and there's no credit left (add credit in the console, Billing).
Features N-Ways turned off. N-Ways can turn one feature off for an organisation (403, the message says why and whom to contact; nothing else changes): DELIVERIES_OFF (new tasks), CONTRACTORS_OFF (sending tasks to N-Ways drivers, inviting or hiring them), SMART_PRICE_OFF (pay suggestions and Smart Price: send payMax yourself), WEBHOOKS_OFF (no webhook sends; adding or testing an endpoint is refused), ERP_OFF (the events export), TRACKERS_OFF (adding trackers, and their positions and alarms; the trackers stay registered). A suspended organisation's new tasks are refused with ORGANISATION_SUSPENDED, like its keys; tasks under way carry on.
Rate limits. Defaults per key and minute: fleet 6,000, traffic_feed 60. Answers carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
Ids. Vehicles, drivers and tasks are addressed by your own ids (1–64 of A–Z a–z 0–9 _ . : -), unique per project. What N-Ways makes (webhooks, alert rules and alerts, rider links and invites, proofs, webhook deliveries) has an opaque string id: a prefix, _ and 10 letters or digits (wh_Ub3yH9sLk2, rule_…, alr_…, lnk_…, rin_…, prf_…, dlv_…). Treat ids as strings and never parse them. The older numbered forms (wh_12, rule_7, inv_3…) are still accepted in paths until 1 April 2027. Times are RFC 3339 in UTC.
Data terms. Positions you send are kept as trip history for retentionDays (30 by default, 1–90), then deleted. ETA and traffic figures serve the trip only: they are cleared when a task ends. Your positions feed N-Ways' public live traffic only if you opt in (shareTraffic, off by default); then they are used anonymously, like any phone's (a session per vehicle and day, never your ids). The traffic feed is aggregated over many devices and never contains a trace.
| Base address | https://nways.ng/maps/v1/fleet |
| Version | 1.0.0 |
| OpenAPI file | partner-fleet.openapi.yaml · JSON |
| Endpoints | Description |
|---|---|
GET /settings | Get the project's fleet settings |
PUT /settings | Change fleet settings (fields left out keep their value) |
GET /drivers | List drivers |
POST /drivers | Add a driver |
GET /drivers/{id} | Get a driver |
PATCH /drivers/{id} | Change a driver |
DELETE /drivers/{id} | Delete a driver (vehicles keep running without one) |
GET /vehicles | List vehicles with their last known positions |
POST /vehicles | Add a vehicle |
GET /vehicles/{id} | Get a vehicle and its last known position |
PATCH /vehicles/{id} | Change a vehicle (driverId "" removes the driver) |
DELETE /vehicles/{id} | Delete a vehicle and its trip history |
GET /vehicles/{id}/history | A vehicle's trip history (oldest first) |
GET /vehicles/{id}/events | A vehicle's timeline from its tracker (ignition on and off, power cut and back), newest first |
POST /locations | Send positions (batched) |
GET /tasks/{id}/proofs | The task's proof of delivery (photos, signatures) and the rider's note |
GET /proofs/{proofId} | A proof's image (JPEG, PNG or WebP), kept as long as its task |
GET /tasks | List tasks, newest first |
POST /tasks | Create a delivery |
GET /tasks/{id} | Get a task with its live ETA |
PATCH /tasks/{id} | Edit a delivery |
POST /tasks/{id}/assign | Assign (or reassign) a vehicle — created|assigned → assigned; event task.assigned |
POST /tasks/{id}/pickup | Mark picked up — assigned → picked_up |
POST /tasks/{id}/complete | Mark delivered — assigned|picked_up|arriving → completed; event task.completed |
POST /tasks/{id}/fail | Mark failed — any open status → failed; event task.failed |
POST /tasks/{id}/cancel | Cancel — any open status → cancelled |
POST /tasks/{id}/pin-email | Send the customer's delivery PIN to their email |
GET /whatsapp/number | Whether a delivery PIN can go to a number on WhatsApp |
POST /tasks/{id}/area | Set or clear a delivery's own area |
GET /places | List your pickup places (the default first) |
POST /places | Add a pickup place (up to 200) |
GET /places/{placeId} | Get a pickup place |
PATCH /places/{placeId} | Edit a pickup place |
DELETE /places/{placeId} | Delete a pickup place |
GET /customers | List your customers, most recent delivery first |
GET /customers/{customerId} | Get a customer |
DELETE /customers/{customerId} | Forget a customer and their addresses |
GET /customers/{customerId}/addresses | List a customer's addresses, last used first |
POST /tasks/{id}/share | The recipient's tracking link (a new one if it expired; none once the task ended) |
GET /webhooks | List webhook endpoints |
POST /webhooks | Add an endpoint (up to 10); the answer has its signing secret, shown once |
PATCH /webhooks/{id} | Change an endpoint's url, events or active |
DELETE /webhooks/{id} | Delete an endpoint and its delivery log |
POST /webhooks/{id}/rotate-secret | A new signing secret (shown once); the old one stops at once |
POST /webhooks/{id}/test | Queue a ping event to the endpoint |
GET /webhooks/{id}/deliveries | The endpoint's delivery log, newest first (kept 30 days) |
POST /webhooks/{id}/deliveries/{deliveryId}/retry | Send a delivery again now (failed, or delivered and wanted again); its attempts are kept |
GET /alert-rules | List alert rules |
POST /alert-rules | Add an alert rule (up to 100) |
GET /alert-rules/{ruleId} | Get a rule |
PATCH /alert-rules/{ruleId} | Change a rule (the kind stays); active false pauses it and resolves its open alerts |
DELETE /alert-rules/{ruleId} | Delete a rule (its open alerts are resolved) |
GET /alert-events | Alerts raised, newest first |
GET /alert-events/{alertId} | Get an alert |
POST /alert-events/{alertId}/acknowledge | Acknowledge an open alert (no more reminders; it stays until it clears or is resolved) |
POST /alert-events/{alertId}/resolve | Resolve an alert (sends asset.alert_resolved) |
GET /events/export | Download every event (CSV for Excel or Google Sheets, or JSON) |
GET /traffic-feed | Road speeds in a box (product traffic_feed; enterprise) |
GET /offline/regions | Regions offline packs are made for |
GET /offline/packs/{region} | A region's pack manifest (202 while it's built; ask again after Retry-After) |
GET /offline/packs/{region}/{file} | Download a pack file (Range requests supported; ETag = its hash) |
GET /trackers | List your trackers, with counts by state |
POST /trackers | Add a tracker by its IMEI (first claim wins) |
GET /trackers/catalog | The device catalogue, your country's SIM networks and their APNs, and where devices report |
POST /trackers/import | Add many trackers from a CSV (check it first with dryRun) |
GET /trackers/setup.csv | The installers' sheet, every tracker's setup SMS in order (CSV) |
GET /trackers/forwarding | Forwarding from your tracking platform, its addresses, and the units it sends |
POST /trackers/forwarding | Turn forwarding on, or give it a new token (the old one stops at once) |
DELETE /trackers/forwarding | Turn forwarding off (the units stay listed) |
POST /trackers/forwarding/units/{unitId}/add | Add a forwarded unit as one of your trackers (a new vehicle named after it, unless you give one) |
DELETE /trackers/forwarding/units/{unitId} | Forget a forwarded unit (it comes back if your platform sends it again) |
GET /tracker-transfers | Your requests to move trackers here, and trackers that moved away |
POST /tracker-transfers | Ask for a tracker another organisation holds (owners and admins) |
POST /tracker-transfers/{transferId}/start | You're making the device log in afresh now (its restart SMS); a fresh login within 3 minutes moves it |
DELETE /tracker-transfers/{transferId} | Withdraw a request |
GET /trackers/{trackerId} | Get a tracker, its setup steps and what's wrong |
PATCH /trackers/{trackerId} | Edit a tracker (its vehicle, model, SIM) |
DELETE /trackers/{trackerId} | Remove a tracker (it can then be added by anyone) |
GET /trackers/{trackerId}/log | The device log (connections, raw frames, decoded fields, answers sent, errors), oldest first |
GET /trackers/{trackerId}/stream | The device log and state as they change (server-sent events) |
POST /trackers/{trackerId}/virtual | Drive a virtual tracker for this one (its own protocol, through N-Ways' gateway) |
DELETE /trackers/{trackerId}/virtual | Stop its virtual tracker |
GET /trackers/{trackerId}/commands | Commands sent to a tracker (the last 30), and which it takes |
POST /trackers/{trackerId}/commands | Send a command (owners and admins); its answer comes in the device log |
GET /live | Every vehicle now, with its rider, state, current delivery and zones |
GET /alerts | What needs a look now, most urgent first |
GET /routes | Where each vehicle on a delivery is going |
GET /zones | The project's zones (circles), each with how many vehicles are in it now |
POST /zones | Add a zone (at most 200 a project) |
GET /zones/{id} | Get a zone |
PATCH /zones/{id} | Rename, move or resize a zone (lat and lng together) |
DELETE /zones/{id} | Delete a zone |
GET /vehicles/{id}/links | The vehicle's rider links (never their addresses), newest first |
POST /vehicles/{id}/links | A link a rider opens on their phone to share its position for this vehicle (no app needed) |
DELETE /links/{linkId} | Stop a rider link at once (its page then says it has ended) |
GET /drivers/{id}/invites | The rider's invitations to N-Ways Business (never their codes), newest first |
POST /drivers/{id}/invites | Invite the rider to N-Ways Business |
DELETE /invites/{inviteId} | Withdraw an invitation (its code stops working) |
DELETE /drivers/{id}/app | Take the rider's N-Ways account off this driver |
Files
The same reference as machine-readable files, to generate your own client or import into an API tool. The Postman collection has every request with {{apiKey}} and {{baseUrl}} variables.