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

CodeMeaning
400Bad Request — missing or invalid parameters
401Unauthorized — invalid, missing, or deactivated API key
404Not Found — endpoint or resource does not exist
429Too Many Requests — rate limit or monthly quota exceeded
500Internal 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

FieldTypeDescription
address_rawstring requiredThe raw, unstructured Nigerian address
countrystring optionalCountry code (default: "NG")
hints.lgastring optionalKnown LGA to improve parsing accuracy
hints.statestring optionalKnown state to improve parsing accuracy
options.geocodeboolean optionalEnable geocoding (default: true)
options.confidence_thresholdnumber optionalMinimum 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

ConfidenceStatus
≥ 0.75validated
0.50 – 0.74low_confidence
< 0.50unresolvable

Validation Flags

FlagMeaning
LGA_MISMATCHLGA from parser doesn't match LGA from geocoder boundary check
LANDMARK_ONLYNo street name found — only landmarks
STATE_INFERREDState was not explicitly stated, was inferred by the parser
GEOCODE_FALLBACKPrimary geocoder failed, fallback geocoder was used
LOW_LLM_CONFIDENCELLM parser returned confidence below 0.6
LGA_ENRICHEDLGA was not in the input but was inferred from geocoded coordinates
STATE_ENRICHEDState was not in the input but was inferred from geocoded coordinates
POSTCODE_VERIFIEDPostcode matches NIPOST records for the detected area
STREET_IN_CORPUSStreet name found in the validated address corpus
WARD_VERIFIEDGeocoded 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

  1. The address is parsed and geocoded through the standard pipeline (Steps 1–5)
  2. The user-provided GPS coordinates are compared against the geocoded address using Haversine distance
  3. A proximity score (0–100) is calculated: ≤100m = 95–100, ≤500m = 70–90, ≤1km = 50–70, ≤5km = 20–50
  4. LGA boundary containment is checked for both the GPS and geocoded coordinates

When to use

Request Body

FieldTypeDescription
address_rawstring requiredThe claimed address
coordinates.latnumber requiredUser's GPS latitude
coordinates.lngnumber requiredUser's GPS longitude
hints.statestring optionalKnown state
hints.lgastring optionalKnown 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

DistanceScoreInterpretation
≤ 100m95–100At the address
≤ 500m70–90Very close — same street or block
≤ 1km50–70In the neighbourhood
≤ 5km20–50Same general area
> 5km0–20Different 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

  1. Coordinates are sent to Nominatim for reverse geocoding
  2. The result is cross-referenced against Idumu’s ward database to find the nearest ward
  3. LGA and state are determined from our boundary dataset
  4. Postcode is looked up from our NIPOST database if the area matches

When to use

Request Body

FieldTypeDescription
latnumber requiredLatitude
lngnumber requiredLongitude

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

  1. The raw address is normalised (abbreviations expanded, whitespace cleaned)
  2. The LLM parser extracts structured components (street, area, LGA, state, etc.)
  3. If the area is known, a postcode is inferred from the NIPOST database
  4. 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

Request Body

FieldTypeDescription
address_rawstring requiredThe raw address to standardise
hints.statestring optionalKnown state
hints.lgastring optionalKnown 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

  1. The claimed address is parsed and standardised through the LLM pipeline
  2. The identity number is sent to a provider (Smile ID or Prembly) to retrieve the registered address
  3. Both addresses are compared across four dimensions: state (30pts), LGA (25pts), street (30pts), area (15pts)
  4. A match score (0–100) is returned. Scores ≥ 60 are considered verified

When to use

Request Body

FieldTypeDescription
address_rawstring requiredThe claimed address
identity.typestring requiredOne of: bvn, nin, voter_id
identity.numberstring requiredThe identity document number
hints.statestring optionalKnown state
hints.lgastring optionalKnown 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

DimensionPointsDescription
State match30Claimed state matches identity state
LGA match25Claimed LGA matches identity LGA
Street match30Street name appears in both (exact = 30, partial = 15)
Area match15Area/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.

ParameterTypeDescription
qstring requiredSearch query (minimum 2 characters). Multi-word queries match each token independently.
statestring optionalFilter by state
lgastring optionalFilter by LGA
limitnumber optionalMax 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)

SourceCountDescription
INEC Polling Units~185,000Named locations across all 37 states (schools, markets, churches, community centres)
OSM Streets~12,700Street names from OpenStreetMap
Nigerian Wards~8,800Ward names with LGA/state hierarchy
Landmarks~530Banks, hospitals, government buildings
Validated AddressesGrowingAddresses 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:

PatternExample
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

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:

StateAliasesCapitalLGA Count
LagosEko, LasgidiIkeja20
FCTAbujaAbuja6
RiversPH, Port HarcourtPort Harcourt23
KanoKano44
OyoIbadanIbadan33
EnuguEnugu17

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}`);