FMS Geocoder API

Uganda · Kenya · Nigeria · Angola  —  reverse geocoding · Nominatim + PostgreSQL cache
https://fms-geocoding.fms-ecsafrica.com

Auth & limits

All requests require X-Api-Key: <your-key> header — except the health check.  ·  Rate limit: 250 req/s sustained, 500 burst.  ·  Default precision: P4 (11 m cell).

Measured capacity — load-tested 2 Jul 2026

Observed throughput on this server (8 vCPU / 22 GB), driven with real coordinates across all four countries. These are measured figures, not estimates. Real-world capacity depends mostly on the cache hit ratio.
Endpoint / modeThroughputNotes
/reverse — cache hit~2,500 req/sp50 6 ms
/reverse — fresh geocode~450 req/sp50 37 ms, p99 115 ms
Edge rate limit (all endpoints)250 req/sburst 500; excess → HTTP 503
/reverse/bulk — cached~30,000 pts/smax 5,000 points/request
/reverse/bulk — fresh~13 pts/smax 500 uncached/request
/reverse/jobs — enqueue~3,000 pts/smax 100,000 points/job
/reverse/jobs — processing~16 pts/sbackground drain (4 workers)

Endpoints

MethodPathWhat it does
GET /api/v1/health Check API, database, and Nominatim status. No auth required.
POST /api/v1/reverse Reverse geocode one coordinate. Returns cached result instantly; calls Nominatim on a miss and caches the result for 180 days.
POST /api/v1/reverse/bulk Geocode up to 5,000 coordinates in one synchronous call. Best when most points are already cached. Returns 413 if >500 are uncached — submit an async job instead.
POST /api/v1/reverse/jobs Submit a large batch (up to 100,000 coords) as an async job. Returns a job_id immediately; workers geocode in background at ~20/s. Cached points are resolved at submission time.
GET /api/v1/reverse/jobs/{id} Poll job progress. Compare completed_points to total_points. Poll every 3–5 seconds.
GET /api/v1/reverse/jobs/{id}/results Download results with limit + offset pagination (max 1,000 per page). Can be called while the job is still running.

When to use what

SituationRecommended endpoint
Live vehicle pingPOST /api/v1/reverse
Small batch (<5,000), mostly routes already seenPOST /api/v1/reverse/bulk
Any batch with many fresh / unseen coordinatesPOST /api/v1/reverse/jobs then poll
Historical import (10k–100k coordinates)POST /api/v1/reverse/jobs → poll → paginate results

Request body formats

EndpointJSON body
/reverse {"lat": 0.3476, "lon": 32.5825}    optional: "precision": 5
/reverse/bulk {"points": [{"lat": 0.3476, "lon": 32.5825, "id": "v001"}, ...]}
/reverse/jobs {"points": [...], "client_reference": "batch-001"}   reference is optional

Interactive tester

Ctrl+Enter