API Reference
Complete reference for the Idumu address validation API. All endpoints require an API key passed via the Authorization header.
Authentication
Every request (except GET /health) requires a valid API key. Pass it as a Bearer token:
Authorization: Bearer idumu_abc123def456...
API keys are issued via the admin endpoint. Each key has a tier with a monthly call quota. When the quota is exceeded, the API returns 429 Too Many Requests with a Retry-After header.
Base URL
https://api.idumu.ng/v1/
All versioned endpoints are prefixed with /v1/. The health check endpoint is at the root: /health.
Error Codes
| Code | Meaning |
|---|---|
400 | Bad Request — missing or invalid parameters |
401 | Unauthorized — invalid, missing, or deactivated API key |
404 | Not Found — endpoint or resource does not exist |
429 | Too Many Requests — rate limit or monthly quota exceeded |
500 | Internal Server Error — unexpected failure (retry or contact support) |
POST /v1/validate
The primary endpoint. Accepts a raw Nigerian address string, parses it, geocodes it, validates against LGA boundaries, and returns a structured, scored response.
Request Body
| Field | Type | Description |
|---|---|---|
address_raw | string required | The raw, unstructured Nigerian address |
country | string optional | Country code (default: "NG") |
hints.lga | string optional | Known LGA to improve parsing accuracy |
hints.state | string optional | Known state to improve parsing accuracy |
options.geocode | boolean optional | Enable geocoding (default: true) |
options.confidence_threshold | number optional | Minimum confidence threshold (0.0–1.0) |
Response
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "validated",
"confidence": 0.84,
"address": {
"raw": "opposite GTBank, Wuse 2, Abuja",
"parsed": {
"house_number": null,
"street": "Aminu Kano Crescent",
"landmark": "GTBank",
"reference_landmarks": ["GTBank"],
"area": "Wuse 2",
"lga": "Abuja Municipal",
"state": "FCT",
"postcode": null,
"country": "Nigeria"
},
"standardised": "Aminu Kano Crescent, Wuse 2, Abuja Municipal, FCT",
"resolved_address": "Aminu Kano Crescent, Wuse 2, Abuja Municipal Area Council, FCT, 900271, Nigeria",
"geocode": {
"lat": 9.0634,
"lng": 7.4697,
"accuracy": "STREET_LEVEL"
}
},
"validation": {
"deliverable": true,
"residential_indicator": false,
"lga_confirmed": true,
"flags": []
},
"metadata": {
"processing_ms": 340,
"cache_hit": false,
"data_sources": ["llm_parser", "nominatim"]
}
}
Status Rules
| Confidence | Status |
|---|---|
| ≥ 0.75 | validated |
| 0.50 – 0.74 | low_confidence |
| < 0.50 | unresolvable |
Validation Flags
| Flag | Meaning |
|---|---|
LGA_MISMATCH | LGA from parser doesn't match LGA from geocoder boundary check |
LANDMARK_ONLY | No street name found — only landmarks |
STATE_INFERRED | State was not explicitly stated, was inferred by the parser |
GEOCODE_FALLBACK | Primary geocoder failed, fallback geocoder was used |
LOW_LLM_CONFIDENCE | LLM parser returned confidence below 0.6 |
LGA_ENRICHED | LGA was not in the input but was inferred from geocoded coordinates |
STATE_ENRICHED | State was not in the input but was inferred from geocoded coordinates |
POSTCODE_VERIFIED | Postcode matches NIPOST records for the detected area |
STREET_IN_CORPUS | Street name found in the validated address corpus |
WARD_VERIFIED | Geocoded location is within a known ward boundary |
Resolved Address
When geocoding succeeds, the response includes a resolved_address field — the canonical full address as known by OpenStreetMap/Nominatim. This is useful for showing users the “real” address even when they provide a partial or informal input like “lagos continental hotel VI”.
POST /v1/validate/batch
Submit up to 100 addresses for asynchronous validation. Returns a batch ID immediately.
Request Body
{
"addresses": [
{ "address_raw": "opposite GTBank, Wuse 2, Abuja" },
{ "address_raw": "15 Awolowo Road, Ikoyi, Lagos" }
],
"webhook_url": "https://your-server.com/webhook"
}
Response (202 Accepted)
{
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"total_addresses": 2,
"message": "Batch processing started. Use GET /v1/batch/{batch_id} to check status."
}
GET /v1/batch/{batch_id}
Retrieve the current status and results (partial or complete) for a batch job.
POST /v1/verify-location
Verify whether a user is physically at their claimed address by comparing GPS coordinates against the geocoded address. Ideal for fintechs, logistics, and ride-hailing apps that already have the user's location.
How it works
- The address is parsed and geocoded through the standard pipeline (Steps 1–5)
- The user-provided GPS coordinates are compared against the geocoded address using Haversine distance
- A proximity score (0–100) is calculated: ≤100m = 95–100, ≤500m = 70–90, ≤1km = 50–70, ≤5km = 20–50
- LGA boundary containment is checked for both the GPS and geocoded coordinates
When to use
- Fintech onboarding — user claims an address during KYC, your mobile app sends their GPS. Instant verification, no field agents.
- Delivery confirmation — rider arrives at a drop-off address, app confirms they’re actually there.
- Insurance claims — verify claimant is at the insured property.
Request Body
| Field | Type | Description |
|---|---|---|
address_raw | string required | The claimed address |
coordinates.lat | number required | User's GPS latitude |
coordinates.lng | number required | User's GPS longitude |
hints.state | string optional | Known state |
hints.lga | string optional | Known LGA |
Response
{
"proximity_score": 87,
"distance_m": 245,
"confidence": 0.91,
"message": "User GPS is within 500m of the geocoded address.",
"lga_match": true,
"address": {
"raw": "15 Adeola Odeku Street, Victoria Island, Lagos",
"standardised": "15, Adeola Odeku Street, Victoria Island, Eti-Osa, Lagos",
"geocode": { "lat": 6.4305, "lng": 3.4164, "accuracy": "STREET_LEVEL" }
},
"user_location": {
"lat": 6.4320,
"lng": 3.4180,
"lga_detected": "Eti-Osa",
"state_detected": "Lagos"
},
"metadata": { "processing_ms": 420 }
}
Scoring
| Distance | Score | Interpretation |
|---|---|---|
| ≤ 100m | 95–100 | At the address |
| ≤ 500m | 70–90 | Very close — same street or block |
| ≤ 1km | 50–70 | In the neighbourhood |
| ≤ 5km | 20–50 | Same general area |
| > 5km | 0–20 | Different area / likely mismatch |
POST /v1/reverse-geocode
Given GPS coordinates, return the nearest known address, ward, LGA, state, and postcode. Useful when you have a user’s location but no address — for example, a delivery driver dropping a pin on a map.
How it works
- Coordinates are sent to Nominatim for reverse geocoding
- The result is cross-referenced against Idumu’s ward database to find the nearest ward
- LGA and state are determined from our boundary dataset
- Postcode is looked up from our NIPOST database if the area matches
When to use
- Pin-drop address capture — user drops a pin on a map, you get the structured address back.
- Fleet tracking — convert vehicle GPS to human-readable locations.
- Geofencing — determine which LGA/ward a coordinate falls in.
Request Body
| Field | Type | Description |
|---|---|---|
lat | number required | Latitude |
lng | number required | Longitude |
Response
{
"address": "Adeola Odeku Street, Victoria Island, Eti-Osa, Lagos",
"components": {
"road": "Adeola Odeku Street",
"suburb": "Victoria Island",
"city": "Lagos",
"state": "Lagos"
},
"ward": { "name": "Iru/Victoria Island", "distance_m": 320 },
"lga": "Eti-Osa",
"state": "Lagos",
"postcode": "106104",
"coordinates": { "lat": 6.4305, "lng": 3.4164 },
"metadata": { "processing_ms": 180 }
}
POST /v1/standardize
Parse and standardise a raw Nigerian address without geocoding or verification. This is the cheapest and fastest endpoint — ideal for bulk data cleaning, form auto-formatting, or preprocessing before a full validation call.
How it works
- The raw address is normalised (abbreviations expanded, whitespace cleaned)
- The LLM parser extracts structured components (street, area, LGA, state, etc.)
- If the area is known, a postcode is inferred from the NIPOST database
- The standardised address string is assembled from the parsed components
No geocoding, no boundary checks, no authoritative verification. Just parsing and formatting.
When to use
- Data cleaning — normalise messy address columns in your database.
- Form auto-fill — as the user finishes typing, show the standardised version.
- Pre-processing — standardise first, then batch-validate only the ones that matter.
Request Body
| Field | Type | Description |
|---|---|---|
address_raw | string required | The raw address to standardise |
hints.state | string optional | Known state |
hints.lga | string optional | Known LGA |
Response
{
"standardised": "5, Awolowo Road, Ikeja, Ikeja, Lagos",
"parsed": {
"house_number": "5",
"street": "Awolowo Road",
"area": "Ikeja",
"lga": "Ikeja",
"state": "Lagos",
"landmark": null,
"postcode": "100211"
},
"postcode": "100211",
"metadata": { "processing_ms": 95 }
}
POST /v1/verify-identity-address
Cross-reference a claimed address against the address tied to a national identity document (BVN, NIN, or Voter ID). The endpoint looks up the address registered with the identity provider and scores how well it matches the claimed address.
How it works
- The claimed address is parsed and standardised through the LLM pipeline
- The identity number is sent to a provider (Smile ID or Prembly) to retrieve the registered address
- Both addresses are compared across four dimensions: state (30pts), LGA (25pts), street (30pts), area (15pts)
- A match score (0–100) is returned. Scores ≥ 60 are considered
verified
When to use
- KYC onboarding — user provides an address + BVN/NIN. Instantly check if the address matches their registered identity.
- Loan origination — verify the applicant actually lives where they claim, using government records.
- Insurance underwriting — confirm the insured property address matches the policyholder’s identity records.
Request Body
| Field | Type | Description |
|---|---|---|
address_raw | string required | The claimed address |
identity.type | string required | One of: bvn, nin, voter_id |
identity.number | string required | The identity document number |
hints.state | string optional | Known state |
hints.lga | string optional | Known LGA |
Response
{
"verified": true,
"match_score": 85,
"claimed_address": "15 Adeola Odeku Street, Victoria Island, Eti-Osa, Lagos",
"identity_address": {
"address": "15 Adeola Odeku St, V/I Lagos",
"state": "Lagos",
"lga": "Eti-Osa"
},
"match_details": {
"state_match": true,
"lga_match": true,
"street_match": "exact",
"area_match": true
},
"identity": {
"type": "bvn",
"number_masked": "123****890"
},
"metadata": { "processing_ms": 650, "provider": "smile_id" }
}
Match Scoring
| Dimension | Points | Description |
|---|---|---|
| State match | 30 | Claimed state matches identity state |
| LGA match | 25 | Claimed LGA matches identity LGA |
| Street match | 30 | Street name appears in both (exact = 30, partial = 15) |
| Area match | 15 | Area/neighbourhood appears in the identity address |
A score ≥ 60 returns "verified": true. Below 60 returns "verified": false.
Providers
The endpoint tries providers in order: Smile ID (if SMILE_ID_API_KEY is configured), then Prembly (if PREMBLY_API_KEY is configured). If neither is configured, the response will include an error message. The provider field in metadata tells you which was used.
GET /v1/autocomplete
Real-time address suggestions as a user types. Searches across 200,000+ entries including INEC polling units (185K named locations like schools, markets, and churches), OSM streets, validated addresses, wards, and landmarks. Supports multi-word tokenized search — each word in the query is matched independently, so "awolowo road ikeja" finds entries containing all three words in any order.
| Parameter | Type | Description |
|---|---|---|
q | string required | Search query (minimum 2 characters). Multi-word queries match each token independently. |
state | string optional | Filter by state |
lga | string optional | Filter by LGA |
limit | number optional | Max results (default: 10, max: 20) |
Response
{
"suggestions": [
{
"type": "address",
"name": "Awolowo Road, Ikeja, Ikeja, Lagos",
"street": "Awolowo Road",
"area": "Ikeja",
"lga": "Ikeja",
"state": "Lagos",
"lat": 6.601,
"lng": 3.342,
"confidence": 0.9,
"source": "osm"
}
]
}
Data Sources (200K+ entries)
| Source | Count | Description |
|---|---|---|
| INEC Polling Units | ~185,000 | Named locations across all 37 states (schools, markets, churches, community centres) |
| OSM Streets | ~12,700 | Street names from OpenStreetMap |
| Nigerian Wards | ~8,800 | Ward names with LGA/state hierarchy |
| Landmarks | ~530 | Banks, hospitals, government buildings |
| Validated Addresses | Growing | Addresses validated by real API users (self-improving corpus) |
GET /v1/verification/{request_id}
Check the status of an async field verification (Prembly) submitted via POST /v1/validate with "options": { "verify_field": true }. The initial validate call returns a request_id — poll this endpoint to get the field agent’s result once it arrives (typically 24–48 hours).
Response
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"verifications": [
{
"provider": "prembly",
"status": "completed",
"reference": "PRMB-123456",
"submitted_at": "2026-04-03T10:00:00Z",
"completed_at": "2026-04-04T14:30:00Z",
"result": {
"verified": true,
"notes": "Address exists. Building occupied."
}
}
]
}
GET /v1/usage
Returns usage statistics for the authenticated API key.
{
"calls_this_month": 1234,
"quota_remaining": 8766,
"monthly_limit": 10000,
"tier": "starter",
"daily_breakdown": [
{ "date": "2026-03-31", "count": 45 },
{ "date": "2026-03-30", "count": 62 }
],
"top_states": [
{ "state": "Lagos", "count": 520 },
{ "state": "FCT", "count": 310 }
],
"top_lgas": [
{ "lga": "Eti-Osa", "count": 180 },
{ "lga": "Abuja Municipal", "count": 145 }
]
}
GET /health
Public health check endpoint. No authentication required.
{
"status": "ok",
"version": "1.0.0",
"uptime_seconds": 86400,
"timestamp": "2026-03-31T12:00:00.000Z"
}
Nigeria Address Guide
Nigerian addresses are unique. Most people give directions using landmarks, not street numbers. Here are the common patterns Idumu handles:
| Pattern | Example |
|---|---|
| Landmark reference | "opposite GTBank, Wuse 2, Abuja" |
| Relative direction | "2nd house after the blue gate, off Awolowo Road, Ikoyi" |
| Road junction | "Allen Avenue / Adeniyi Jones junction, Ikeja" |
| Named area | "Balogun Market, Lagos Island" |
| Filling station reference | "near NNPC filling station, Aba Road, Port Harcourt" |
| Multiple landmarks | "behind the mosque, near Total petrol station, Ring Road, Benin" |
Tips for better results
- Pass
hints.stateandhints.lgawhen you know them — this significantly improves accuracy. - Include as many landmarks and references as the user provides.
- Common abbreviations (Rd, St, Ave, PH, Eko) are normalised automatically.
LGA/State Reference
Nigeria has 774 Local Government Areas across 36 states and the FCT. The API validates every parsed LGA against the official list. Common state aliases are supported:
| State | Aliases | Capital | LGA Count |
|---|---|---|---|
| Lagos | Eko, Lasgidi | Ikeja | 20 |
| FCT | Abuja | Abuja | 6 |
| Rivers | PH, Port Harcourt | Port Harcourt | 23 |
| Kano | — | Kano | 44 |
| Oyo | Ibadan | Ibadan | 33 |
| Enugu | — | Enugu | 17 |
cURL
Validate an address
curl -X POST https://api.idumu.ng/v1/validate \
-H "Authorization: Bearer idumu_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"address_raw": "opposite GTBank, Wuse 2, Abuja",
"country": "NG",
"options": { "geocode": true }
}'
GPS verification
curl -X POST https://api.idumu.ng/v1/verify-location \
-H "Authorization: Bearer idumu_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"address_raw": "15 Adeola Odeku Street, Victoria Island, Lagos",
"coordinates": { "lat": 6.4320, "lng": 3.4180 }
}'
Reverse geocode
curl -X POST https://api.idumu.ng/v1/reverse-geocode \
-H "Authorization: Bearer idumu_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "lat": 6.4305, "lng": 3.4164 }'
Standardize only
curl -X POST https://api.idumu.ng/v1/standardize \
-H "Authorization: Bearer idumu_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "address_raw": "no 5 awolowo rd ikeja lagos" }'
Identity address verification (BVN/NIN)
curl -X POST https://api.idumu.ng/v1/verify-identity-address \
-H "Authorization: Bearer idumu_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"address_raw": "15 Adeola Odeku Street, Victoria Island, Lagos",
"identity": { "type": "bvn", "number": "12345678901" }
}'
Python
import requests
BASE = "https://api.idumu.ng/v1"
HEADERS = {
"Authorization": "Bearer idumu_your_api_key_here",
"Content-Type": "application/json",
}
# ── Validate ──
resp = requests.post(f"{BASE}/validate", headers=HEADERS, json={
"address_raw": "opposite GTBank, Wuse 2, Abuja",
})
data = resp.json()
print(f"Status: {data['status']}, Confidence: {data['confidence']}")
print(f"Resolved: {data['address']['resolved_address']}")
# ── GPS Verification ──
resp = requests.post(f"{BASE}/verify-location", headers=HEADERS, json={
"address_raw": "15 Adeola Odeku Street, Victoria Island, Lagos",
"coordinates": {"lat": 6.4320, "lng": 3.4180},
})
print(f"Proximity score: {resp.json()['proximity_score']}")
# ── Reverse Geocode ──
resp = requests.post(f"{BASE}/reverse-geocode", headers=HEADERS, json={
"lat": 6.4305, "lng": 3.4164,
})
print(f"Address: {resp.json()['address']}, LGA: {resp.json()['lga']}")
# ── Standardize ──
resp = requests.post(f"{BASE}/standardize", headers=HEADERS, json={
"address_raw": "no 5 awolowo rd ikeja lagos",
})
print(f"Standardised: {resp.json()['standardised']}")
# ── Identity Verification ──
resp = requests.post(f"{BASE}/verify-identity-address", headers=HEADERS, json={
"address_raw": "15 Adeola Odeku Street, VI, Lagos",
"identity": {"type": "bvn", "number": "12345678901"},
})
print(f"Match score: {resp.json()['match_score']}, Verified: {resp.json()['verified']}")
JavaScript
const BASE = "https://api.idumu.ng/v1";
const headers = {
"Authorization": "Bearer idumu_your_api_key_here",
"Content-Type": "application/json",
};
// ── Validate ──
let res = await fetch(`${BASE}/validate`, {
method: "POST", headers,
body: JSON.stringify({ address_raw: "opposite GTBank, Wuse 2, Abuja" }),
});
let data = await res.json();
console.log(`Status: ${data.status}, Confidence: ${data.confidence}`);
console.log(`Resolved: ${data.address.resolved_address}`);
// ── GPS Verification ──
res = await fetch(`${BASE}/verify-location`, {
method: "POST", headers,
body: JSON.stringify({
address_raw: "15 Adeola Odeku Street, Victoria Island, Lagos",
coordinates: { lat: 6.4320, lng: 3.4180 },
}),
});
data = await res.json();
console.log(`Proximity: ${data.proximity_score}, Distance: ${data.distance_m}m`);
// ── Reverse Geocode ──
res = await fetch(`${BASE}/reverse-geocode`, {
method: "POST", headers,
body: JSON.stringify({ lat: 6.4305, lng: 3.4164 }),
});
data = await res.json();
console.log(`Address: ${data.address}, LGA: ${data.lga}`);
// ── Standardize ──
res = await fetch(`${BASE}/standardize`, {
method: "POST", headers,
body: JSON.stringify({ address_raw: "no 5 awolowo rd ikeja lagos" }),
});
data = await res.json();
console.log(`Standardised: ${data.standardised}`);
// ── Identity Verification ──
res = await fetch(`${BASE}/verify-identity-address`, {
method: "POST", headers,
body: JSON.stringify({
address_raw: "15 Adeola Odeku Street, VI, Lagos",
identity: { type: "bvn", number: "12345678901" },
}),
});
data = await res.json();
console.log(`Match: ${data.match_score}, Verified: ${data.verified}`);