Bwendi Context API
Drop in a coordinate. Get back structured addressing plus decision-ready context: place name, admin region, nearby markets, local-language context, and signals AI systems can trust - in one request.
Address intelligence for teams that need more than geocoding
Bwendi is built for production teams where location error creates downstream risk. You can geocode any coordinate, but you also get the context needed to execute: structured addressing, admin truth, market gravity, and local-language place intelligence.
That means one request can power checkout addressing, onboarding checks, logistics routing, risk screening, and AI grounding - especially in markets where formal addressing is incomplete.
- Geocoding: coordinates to place and nearest known entities.
- Addressing: hyperlocal address strings, Plus Code, and admin hierarchy.
- Context: market hubs, anchors, economic tiers, and local language signals.
Deep-dive guides
curl https://api.bwendi.com/\
context/CM/3.8667/11.5167 \
-H "x-api-key: YOUR_KEY"
{
"hubs": [
{ "name": "Yaoundé", "type": "city",
"context": "574m SW of Yaoundé" }
],
"hotspot": {
"name": "CCA Marché central",
"type": "bank",
"context": "11m E of CCA Marché central"
},
"metro": { "name": "Yaoundé", "type": "city" },
"nearby": [
{ "name": "CCA Marché central", "type": "bank" },
{ "name": "Agence de Régulation des Télécommunications",
"type": "government" }
],
"name": "Pharmacie du Soleil",
"lat": 3.8671, "lng": 11.51714,
"type": "pharmacy",
"within": {
"name": "Centre Commercial", "type": "quarter",
"within": {
"name": "Yaoundé I", "type": "district",
"within": {
"name": "Mfoundi", "type": "department",
"within": { "name": "Centre", "type": "region" }
}
}
},
"dem": 734,
"context": "Dans l'orbite commerciale principale de Yaoundé...",
"country": {
"name": "Cameroon", "code": "CM", "flag": "🇨🇲",
"lang": "fr",
"currency": { "symbol": "F", "code": "XAF", "name": "CFA" }
}
}
Frequently searched questions
Is Bwendi only a geocoder?
No. Bwendi includes geocoding, but also returns structured addressing and decision context in one response.
Can I use Bwendi for addressing workflows?
Yes. The /address endpoint is designed for operational addressing, not just map display.
Can this feed AI and retrieval systems?
Yes. Bwendi returns structured and human-readable context that can be fed directly into prompts, retrieval pipelines, and agents.
Authentication
All endpoints require a valid API key. There are no public, unauthenticated routes.
x-api-key:x-api-key: YOUR_API_KEY
Examples
curl https://api.bwendi.com/context/CM/3.8667/11.5167 \
-H "x-api-key: YOUR_API_KEY"
fetch('https://api.bwendi.com/context/CM/3.8667/11.5167', {
headers: { 'x-api-key': 'YOUR_API_KEY' }
})
Try it
Fire a live request from your browser. Responses appear below.
Tip: click Test on any endpoint page to pre-fill and auto-send.
Full geographic context for a coordinate. Returns the named place, admin region, nearby markets, timezone, country info, and more.
/context/:lat/:lng — skip the country code; we auto-detect it (+1 cr).
Parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 (e.g. CM) |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
Query options
| Param | Effect | Cost |
|---|---|---|
| ?lang=xx | Translate all labels (e.g. fr, es) | +1 cr |
| ?expand=true | Resolve place anchors to full names | +1 cr |
| ?metrics=true | Include raw gravity and weight on every place in the response | +3 cr |
Example request
GET /context/CM/3.8667/11.5167
Example response
{
"hubs": [
{
"name": "Yaoundé",
"type": "city",
"context": "574m SW of Yaoundé"
}
],
"hotspot": {
"name": "CCA Marché central",
"type": "bank",
"context": "11m E of CCA Marché central"
},
"metro": {
"name": "Yaoundé",
"type": "city",
"context": "574m SW of Yaoundé"
},
"nearby": [
{
"name": "CCA Marché central",
"type": "bank",
"context": "11m E of CCA Marché central"
},
{
"name": "Agence de Régulation des Télécommunications",
"type": "government",
"context": "85m NE of Agence de Régulation des Télécommunications"
}
],
"name": "Pharmacie du Soleil",
"ascii_name": "Pharmacie du Soleil",
"lat": 3.8671,
"lng": 11.51714,
"type": "pharmacy",
"within": {
"name": "Centre Commercial",
"label": "Chefferie",
"type": "quarter",
"level": 10,
"within": {
"name": "Yaoundé I",
"label": "Arrondissement",
"type": "district",
"level": 8,
"within": {
"name": "Communauté urbaine de Yaoundé",
"label": "Communauté Urbaine",
"type": "council",
"level": 7,
"within": {
"name": "Mfoundi",
"label": "Département",
"type": "department",
"level": 6,
"within": {
"name": "Centre",
"label": "Région",
"type": "region",
"level": 4
}
}
}
}
},
"dem": 734,
"timezone": "Africa/Douala",
"anchors": {
"immediate": ["mall", "marketplace", "supermarket", "pharmacy"],
"local": ["city"],
"reachable": ["town"]
},
"context": "Dans l'orbite commerciale principale de Yaoundé. Accès immédiat à centre commercial, marché, supermarché et pharmacie.",
"country": {
"name": "Cameroon",
"localName": "Cameroun",
"code": "CM",
"callingCode": "237",
"flag": "🇨🇲",
"lang": "fr",
"currency": {
"symbol": "F",
"code": "XAF",
"name": "CFA",
"localName": "FCFA"
}
}
}
Fuzzy place-name search within a country. Results ranked by population — great for autocomplete inputs.
Parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 |
| q | string | Required, min 2 chars |
| limit | int | 1–50 (default 10) |
| types | string | city, town, village (comma-separated) |
| lang | string | Translate labels (+1 cr) |
Example request
GET /search/CM?q=Douala
Example response
{
"country_code": "CM",
"query": "Douala",
"count": 3,
"results": [
{
"name": "Douala", "type": "city",
"lat": 4.0483, "lng": 9.7043,
"population": 2768400,
"economic_tier": "dominant",
"within": "Littoral"
}
]
}
List all markets at or above an economic tier for a country. Use hubs as the tier to get the top market hubs by gravity (80/20 rule).
Parameters
| Param | Type | Note |
|---|---|---|
| tier | string | dominant · prime · high · integrated · moderate · peripheral · marginal · isolated · hubs |
| cc | string | Optional — omit to detect from headers |
dominant › prime › high › integrated › moderate › peripheral › marginal › isolated
Example request
GET /markets/prime/CM
Example response
{
"tier": "prime",
"country_code": "CM",
"count": 8,
"markets": [
{ "name": "Douala", "type": "city", "lat": 4.0483, "lng": 9.7043,
"population": 2768400, "economic_tier": "dominant", "market_score": 11.5 }
]
}
Country info — no backend round-trip. Resolves from three sources:
| Path | Resolves from |
|---|---|
| /country | Cloudflare geolocation header |
| /country/CM | Country code you provide |
| /country/3.87/11.52 | Coordinates (0.25° grid) |
Example response
{
"name": "Cameroon",
"localName": "Cameroun",
"code": "CM",
"callingCode": "+237",
"flag": "🇨🇲",
"lang": "fr",
"currency": { "symbol": "FCFA", "code": "XAF", "name": "Central African CFA Franc" }
}
Send a question about a location. We resolve the full context first, then pass it to Gemini for a grounded answer. Great for analysis, reports, or smart location descriptions.
?lang and ?expand.
Parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
Request body
| Field | Type | Note |
|---|---|---|
| prompt | string | Required, max 500 chars |
Example request
POST /interpret/CM/3.8667/11.5167
Content-Type: application/json
{ "prompt": "What is the commercial potential?" }
AI response field
"ai": {
"prompt": "What is the commercial potential?",
"response": "Yaoundé is Cameroon's political capital (pop. 2.8M). The dominant tier indicates strong commercial integration. Douala, the economic hub, is 194km SW..."
}
Device location verification using solar shadow analysis. Upload a capture and challenge data — we compute the expected sun position, analyse the visual evidence, and return a pass, fail, or inconclusive verdict on whether the device was actually at the claimed GPS location.
Raw (this endpoint): You handle capture yourself, send one image, get the verdict. 25 credits.
Turnkey: Use
POST /verify/challenge to create a challenge, redirect users to verify.bwendi.com, poll GET /verify/result/:id. We handle the camera, frame capture, and analysis. 400 credits total for the hosted flow.
Request body (multipart/form-data)
| Field | Type | Required | Note |
|---|---|---|---|
| image | file | yes | JPEG only, max 1 MB |
| challengeId | string | yes | UUID from your server |
| claimedLat | number | yes | -90 to 90 |
| claimedLng | number | yes | -180 to 180 |
| issuedAt | string | yes | ISO 8601 UTC timestamp (server-issued) |
| signature | string | yes | HMAC-SHA256 of challengeId + claimedLat + claimedLng + issuedAt |
Example request
curl -X POST https://api.bwendi.com/verify \
-H "x-api-key: YOUR_KEY" \
-F "image=@shadow.jpg" \
-F "challengeId=550e8400-e29b-41d4-a716-446655440000" \
-F "claimedLat=4.05" \
-F "claimedLng=9.7" \
-F "issuedAt=2026-03-22T12:34:56Z" \
-F "signature=abc123..."
Example response
{
"challengeId": "550e8400-e29b-41d4-a716-446655440000",
"verdict": "pass",
"solarElevation": 86.2,
"solarAzimuth": 175.3,
"geminiAssessment": {
"shadows_visible": "yes",
"shadow_length": "short",
"shadow_direction_consistent": true,
"outdoor_natural_light": true,
"artificial_light_signs": {
"detected": false,
"indicators": []
},
"signs_of_manipulation": false,
"confidence": 0.85,
"notes": "Very short shadows consistent with near-overhead sun."
}
}
Verdicts
| Verdict | Meaning |
|---|---|
pass | Shadows are consistent with the claimed location and time. |
fail | Shadows contradict the expected solar position — likely spoofed location. |
inconclusive | Cannot determine — indoor photo, overcast sky, or no visible shadows. |
Error responses
| Status | Reason |
|---|---|
| 400 | Missing/invalid fields, wrong content-type, file too large, invalid signature |
| 410 | Challenge expired (90-second window) |
| 422 | Sun below horizon at claimed location/time |
| 500 | Gemini API failure |
Turnkey Flow
Don't want to build the capture flow yourself? Use the turnkey flow — create a challenge, redirect users to verify.bwendi.com, and poll for the result. The hosted flow currently costs 400 credits, which is as little as 18 cents USD on the Star plan.
Step 1 — Create challenge
| Field | Type | Note |
|---|---|---|
| lat | number | Claimed latitude (-90 to 90) |
| lng | number | Claimed longitude (-180 to 180) |
| callback | string | Optional. URL to redirect user after verification. |
Example response
{
"challengeId": "550e8400-e29b-41d4-a716-446655440000",
"verifyUrl": "https://verify.bwendi.com/c/550e8400-e29b-41d4-a716-446655440000",
"issuedAt": "2026-03-22T12:34:56.000Z",
"expiresAt": "2026-03-22T12:36:26.000Z"
}
Step 2 — Redirect user
Send your user to the verifyUrl. Append ?callback=https://yourapp.com/done to redirect back after verification.
Or embed it: <iframe src="https://verify.bwendi.com/c/UUID">
Step 3 — Poll for result
Example response (pending)
{ "challengeId": "550e8400...", "status": "pending" }
Example response (complete)
{
"challengeId": "550e8400-e29b-41d4-a716-446655440000",
"verdict": "pass",
"solarElevation": 86.2,
"solarAzimuth": 175.3,
"frames": 3,
"frameVerdicts": ["pass", "pass", "pass"]
}
Returns the top-N places near a coordinate, ranked by gravity score. Country code is auto-resolved from the coordinate by the gateway — no CC param needed. Searches a ~33 km radius (3×3 big-zone grid) by default.
Path parameters
| Param | Type | Note |
|---|---|---|
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
| placeType | string | places for all settlement types, or a specific type (e.g. city, town, neighbourhood). Comma-separated for multiple. |
| limit | int | Max results (1–100) |
Query options
| Param | Default | Effect |
|---|---|---|
| ?wide=false | true | Restrict to a single big-zone cell (~11 km) instead of the default 3×3 grid (~33 km) |
| ?admin=true | false | Include an admin array of administrative levels derived from the nearest record's within chain |
Example request
GET /hotspots/3.8667/11.5167/places/5
Example response
{
"lat": 3.8667,
"lng": 11.5167,
"country_code": "CM",
"place_type": "places",
"limit": 5,
"count": 5,
"hotspots": [
{
"name": "Yaoundé",
"type": "city",
"lat": 3.8667,
"lng": 11.5167,
"distance_km": 0.412,
"score": 0.9823,
"population": 2765568,
"gravity": 0.9823
},
{
"name": "Mballa II",
"type": "suburb",
"lat": 3.8741,
"lng": 11.5093,
"distance_km": 1.127,
"score": 0.4102
}
]
}
With ?admin=true
GET /hotspots/3.8667/11.5167/places/5?admin=true
// Additional field in response:
"admin": [
{ "name": "Centre", "label": "Région", "type": "region", "level": 1 },
{ "name": "Cameroon", "label": "Country", "type": "country", "level": 0 }
]
Hotspot fields
| Field | Description |
|---|---|
| name | Place name |
| type | Settlement type (city, town, suburb, neighbourhood, …) |
| lat / lng | Coordinates of the place |
| distance_km | Distance from the queried coordinate (km) |
| score | Gravity score used for ranking |
| population | Population count, if available |
| gravity | Raw gravity value (same as score) |
Returns nearby anchor candidates ordered strictly by distance. Country code is auto-resolved from the coordinate by the gateway, so there is no CC parameter in the path.
Path parameters
| Param | Type | Note |
|---|---|---|
| range | string | immediate for the 3×3 zone bucket (~333 m), or local for the 3×3 local_zone bucket (~3.3 km) |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
| placeType | string | places for all settlement/place types, or a specific type such as village, town, neighbourhood. Comma-separated values are supported. |
| limit | int | Max results (1–30) |
Example requests
GET /anchors/local/2.888527176989689/9.89871021523016/village/10
GET /anchors/immediate/2.888527176989689/9.89871021523016/places/30
Example response
{
"lat": 2.888527176989689,
"lng": 9.89871021523016,
"country_code": "CM",
"range": "local",
"bucket_width_m": 3300,
"place_type": "village",
"limit": 10,
"count": 3,
"anchors": [
{
"name": "Bwambé",
"type": "village",
"lat": 2.88846,
"lng": 9.90369,
"distance_m": 553,
"distance_km": 0.553,
"gravity": 287.34
},
{
"name": "Mbeka'a",
"type": "village",
"lat": 2.88317,
"lng": 9.90009,
"distance_m": 615,
"distance_km": 0.615,
"gravity": 287.34
},
{
"name": "Lobé",
"type": "village",
"lat": 2.88193,
"lng": 9.89304,
"distance_m": 967,
"distance_km": 0.967,
"gravity": 2254.71
}
]
}
Response fields
| Field | Description |
|---|---|
| range | The bucket tier used: immediate or local |
| bucket_width_m | Approximate width of the searched tier in metres |
| place_type | The type filter you requested |
| anchors | Array of matching places ordered by distance ascending |
| distance_m | Rounded distance from the query coordinate in metres |
| distance_km | Distance from the query coordinate in kilometres |
| population | Included when available on the source record |
| gravity | Included when available on the source record |
| label | Included when available on the source record |
Returns the dominant metro (city or town) nearest to a coordinate, scored by a gravity-weighted population model. Unlike /context, this is a focused single-object response that always includes the full within administrative chain — useful for resolving the regional hierarchy of any point without the overhead of full context resolution.
Parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 (e.g. CM) |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
Example request
GET /metro/CM/3.9500/11.4800
Example response
{
"lat": 3.95,
"lng": 11.48,
"country_code": "CM",
"metro": {
"name": "Yaoundé",
"type": "city",
"distance": 11240,
"direction": "SE",
"lat": 3.8667,
"lng": 11.5167,
"population": 2765568,
"economic_tier": "dominant",
"within": {
"name": "Centre",
"label": "Région",
"type": "region",
"within": {
"name": "Cameroon",
"label": "Country",
"type": "country"
}
},
"context": "11240m SE of Yaoundé"
}
}
Metro fields
| Field | Description |
|---|---|
| name | Metro city/town name |
| type | Settlement type (city or town) |
| distance | Distance from queried coordinate in metres |
| direction | Cardinal direction from coordinate to metro (N, NE, E, …) |
| lat / lng | Metro coordinates |
| population | Population count, if available |
| economic_tier | dominant · prime · high · integrated · moderate · peripheral · marginal · isolated |
| within | Nested administrative hierarchy (region → country). Present when available. |
| context | Human-readable distance + direction string |
Returns an 8-character hex channel that two nearby users will share. Use it as a pub/sub topic, WebSocket room, or proximity bucket.
Query options
| Param | Default | Range | Use case |
|---|---|---|---|
| range | 1100 m | 50–50000 | Pooling radius in metres |
Range presets
| Label | range= | Use case |
|---|---|---|
| Spot | 50 | Same room |
| Block | 100 | Building cluster |
| Street | 500 | Walking neighbourhood |
| Quarter | 1000 | Urban ward |
| Town | 5000 | City district |
| City | 15000 | City-wide |
| Region | 50000 | Metro region |
Example response
{
"channel": "6b099229",
"tier": "local",
"cell_size_m": 1100,
"range_m": 500
}
Country-based address composer. Returns the default hyperlocal address, a formatted customAddress, and the resolved metro, market, and nearby anchor used to build it.
CM-KRI-BEN-MBEKA-f935cdb5 while still keeping the original address key separately.
Path parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 (e.g. CM) |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
Query options
| Param | Default | Effect |
|---|---|---|
| includeCountry | false | Prepend the country code, e.g. CM- |
| includeMetro | false | Prepend the metro code, e.g. KRI |
| metroLength | 3 | Metro code length (1–8) |
| includeMarket | false | Prepend the market / hub code, e.g. BEN |
| marketLength | 3 | Market code length (1–8) |
| range | 50 m | Hyperlocal address precision range in metres (50–50000) |
Example request
GET /customAddress/CM/2.888527176989689/9.89871021523016?includeCountry=true&includeMetro=true&metroLength=3&includeMarket=true&marketLength=3&range=50
Example response
{
"address": "f935cdb5",
"customAddress": "CM-KRI-BEN-MBEKA-f935cdb5",
"metro": {
"code": "KRI",
"name": "Kribi",
"type": "city",
"distance": 6297,
"direction": "S"
},
"market": {
"code": "BEN",
"name": "Bengandwé",
"type": "village",
"distance": 4628,
"direction": "S"
},
"nearby_anchor": {
"code": "MBEKA",
"name": "Mbeka'a",
"type": "village",
"distance": 615,
"direction": "N"
}
}
Response fields
| Field | Description |
|---|---|
| address | The original default hyperlocal address string |
| customAddress | The composed address string built from the selected country, metro, market, and nearby-anchor prefixes plus the default address |
| metro | Resolved metro component used for the metro segment, when available |
| market | Resolved market / hub component used for the market segment, when available |
| nearby_anchor | Nearest anchor component used for the anchor segment, when available |
| code | Normalized prefix segment generated from the component name |
Structured address for any coordinate. Returns a hyperlocal address string, an Open Location Code (Plus Code), the full admin hierarchy, elevation, timezone, metro anchor, anchors, and the human-readable context string — all in one authenticated call.
Parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 (e.g. CM) |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
Query options
| Param | Default | Range | Effect |
|---|---|---|---|
| range | 1100 m | 50–50000 | Pooling radius — controls address string precision tier |
Range presets
| Label | range= | Use case |
|---|---|---|
| Spot | 50 | Indoor floor, kiosk |
| Block | 200 | Street segment, city block |
| Quarter | 500 | Neighbourhood, trading cluster |
| Town | 1100 | Town centre, district (default) |
| City | 5000 | Metro area, city-wide |
| Region | 25000 | Province, wide-area |
Example request
GET /address/CM/3.8667/11.5167
Example response
{
"lat": 3.8667,
"lng": 11.5167,
"country_code": "CM",
"address": "6b099229",
"plus_code": "6FMHVG88+MM",
"tier": "local_zone",
"range_m": 1100,
"timezone": "Africa/Douala",
"place": {
"name": "Etoa-Meki",
"type": "locality",
"label": "Localité",
"lat": 3.8621,
"lng": 11.5203,
"distance": 540,
"direction": "SE"
},
"dem": 734,
"within": {
"name": "Centre Commercial",
"type": "quarter",
"within": {
"name": "Yaoundé I",
"type": "district",
"within": {
"name": "Mfoundi",
"type": "department",
"within": { "name": "Centre", "type": "region" }
}
}
},
"anchors": {
"immediate": ["mall", "marketplace", "pharmacy"],
"local": ["city"],
"reachable": ["town"]
},
"metro": {
"name": "Yaoundé",
"type": "city",
"context": "574m SW of Yaoundé"
},
"context": "Dans l'orbite commerciale principale de Yaoundé...",
"country": {
"name": "Cameroon",
"code": "CM",
"flag": "\uD83C\uDDE8\uD83C\uDDF2",
"lang": "fr",
"currency": { "symbol": "F", "code": "XAF", "name": "CFA" }
}
}
Response fields
| Field | Description |
|---|---|
| address | Hyperlocal channel string (precision cell key) — the coordinate's shareable address token |
| plus_code | Open Location Code (10-char) — globally interoperable, compatible with Google Maps |
| tier | Hyperlocal tier used: zone, local_zone, or big_zone |
| range_m | Effective pooling radius in metres (clamped to 50–50,000) |
| timezone | IANA timezone for the resolved coordinate, usually from the nearest resolved place |
| place | Nearest named place — name, type (town / village / locality / junction etc.), label, coordinates, distance (m), and direction |
| dem | Terrain elevation in metres (from nearest resolved place) |
| within | Nested admin hierarchy from immediate container up to region |
| anchors | Economic anchor types — immediate, local, and reachable |
| metro | Dominant city/town anchor with distance and direction |
| context | Human-readable location description in the country's default language |
| country | Country metadata (name, code, flag, currency) |
Returns an SVG QR code for a coordinate. The code encodes the hyperlocal address channel, the nearest place name, lat/lng, and the dominant metro anchor — everything needed to identify and share a physical location in a single scan.
image/svg+xml and cacheable for 24 hours.
3.8667° N · 11.5167° E · CM · range 1100 m
Parameters
| Param | Type | Note |
|---|---|---|
| cc | string | ISO 3166-1 alpha-2 (e.g. CM) |
| lat | number | -90 to 90 |
| lng | number | -180 to 180 |
| range | number | Pooling radius in metres (50–50,000). Controls address tier precision. |
Range presets
| Label | range= | Use case |
|---|---|---|
| Spot | 50 | Indoor floor, kiosk |
| Block | 200 | Street segment, city block |
| Quarter | 500 | Neighbourhood, trading cluster |
| Town | 1100 | Town centre, district (default) |
| City | 5000 | Metro area, city-wide |
| Region | 25000 | Province, wide-area |
QR payload (newline-separated)
6b099229 ← hyperlocal address channel
Etoa-Meki ← nearest place name
3.8667,11.5167 ← lat,lng
Yaoundé ← metro anchor (omitted if none)
Example request
GET /qr/CM/3.8667/11.5167/1100
Example usage in HTML
<img src="https://api.bwendi.com/qr/CM/3.8667/11.5167/1100"
alt="Location QR code"
width="200" height="200">
Response
An image/svg+xml document. Embed directly in <img src="...">, <object>, or save as .svg. Cached at the edge for 24 hours.
Returns the caller's IP-derived geolocation — coordinates, country code, city, timezone, and full country object — using Cloudflare's edge geolocation. The gateway enriches every request with x-cf-* headers before forwarding to the origin.
/country/:lat/:lng with device GPS for precise country resolution.Example request
GET /geo
Example response
{
"lat": 47.2017,
"lng": 7.5665,
"cc": "CH",
"city": "Zuchwil",
"timezone": "Europe/Zurich",
"country": {
"name": "Switzerland",
"localName": "Schweiz",
"code": "CH",
"callingCode": "41",
"flag": "🇨🇭",
"lang": "de",
"currency": {
"symbol": "Fr.",
"code": "CHF",
"name": "Franc",
"localName": "Schweizer Franken"
}
}
}
Fields
| Field | Description |
|---|---|
| lat | IP-derived latitude (4 d.p.), or null |
| lng | IP-derived longitude (4 d.p.), or null |
| cc | ISO 3166-1 alpha-2 country code, or null |
| city | City name from Cloudflare, or null |
| timezone | IANA timezone from Cloudflare edge geolocation, or null |
| country | Full country object (name, currency, flag, lang, callingCode), or null |
Server health check. Returns process uptime, memory, and loaded-country count.
{
"status": "ok",
"uptime": 84210,
"countries_loaded": 47,
"memory_mb": 128,
"timestamp": "2026-03-12T12:00:00.000Z"
}
List all available countries, or get detailed info for one.
// GET /countries
{ "built": ["AD","AE","CM","NG", ...], "built_count": 47 }
// GET /countries/CM
{ "country_code": "CM", "has_data": true, "db_size_mb": 12.4 }
Returns a map of every built country code to its default language, plus the sorted list of unique language codes.
{
"languages": {
"CM": "fr",
"NG": "en",
"ET": "am",
"...": "..."
},
"country_count": 174,
"unique_languages": ["am", "ar", "de", "en", "es", "fr", "..." ],
"unique_count": 72
}
Language Codes
Pass any of these codes as ?lang=xx on /context to translate all place names and admin labels. Each translation costs +1 credit.
| Code | Language | Code | Language | Code | Language |
|---|---|---|---|---|---|
am | Amharic | ar | Arabic | az | Azerbaijani |
be | Belarusian | bg | Bulgarian | bn | Bengali |
bs | Bosnian | ca | Catalan | cs | Czech |
da | Danish | de | Deutsch | el | Greek |
en | English | es | Español | fa | Persian |
fi | Finnish | fr | Français | ha | Hausa |
he | Hebrew | hi | Hindi | hr | Croatian |
ht | Haitian Creole | hu | Hungarian | hy | Armenian |
id | Indonesian | is | Icelandic | it | Italiano |
ja | Japanese | ka | Georgian | kk | Kazakh |
kl | Greenlandic | km | Khmer | ko | Korean |
lb | Luxembourgish | lo | Lao | lt | Lithuanian |
lv | Latvian | mg | Malagasy | mk | Macedonian |
mn | Mongolian | ms | Malay | mt | Maltese |
my | Burmese | ne | Nepali | nl | Dutch |
no | Norwegian | om | Oromo | pl | Polish |
pt | Português | ro | Romanian | ru | Russian |
rw | Kinyarwanda | si | Sinhala | sk | Slovak |
sl | Slovenian | sm | Samoan | so | Somali |
sq | Albanian | sr | Serbian | sv | Swedish |
sw | Swahili | ta | Tamil | tg | Tajik |
th | Thai | ti | Tigrinya | tk | Turkmen |
tl | Filipino | tr | Turkish | uk | Ukrainian |
ur | Urdu | uz | Uzbek | vi | Vietnamese |
yo | Yoruba | zh | Chinese | zu | Zulu |
Credits
One credit ≈ one API call. Modifiers stack on top of the base cost. Buy credits →
fr, ar, sw)gravity and weight scores on every resolved placewithin admin chainhubs for the 80/20 primary market list.?lang and ?expand.verifyUrl for verify.bwendi.com.Response Headers
Every response includes these headers so you can track usage in real time.
| Header | Value |
|---|---|
| x-credit-cost | Credits charged for this request (0–21) |
| x-balance-remaining | Credits left on your key (−1 = unlimited) |
| x-resolved-cc | Auto-detected country code (when CC was omitted) |
Error Codes
All errors return JSON with a message field.
| Status | Meaning |
|---|---|
| 400 | Invalid parameters |
| 401 | Missing API key |
| 402 | Insufficient credits |
| 403 | Invalid or inactive key |
| 404 | Country not found or no nearby places |
| 429 | Rate limit exceeded (60 req/min default) |
| 500 | Server error |
message field so you know exactly what went wrong.