3.8667° N · 11.5167° E · CM
Bwendi Context API
Drop in a coordinate. Get back the place name, admin region, nearby markets, currency, language and economic context — in one request.
Location Context
Resolve any lat/lng to a named place with full nested admin hierarchy and country data.
Market Intelligence
See which economic hubs a location belongs to, ranked by gravity score.
Metro Resolution
Snap any coordinate to its dominant metro city or town, with the full admin chain attached.
Country Data
Currency, calling code, language and flag — auto-detected from coordinate or IP.
Hyperlocal Pooling
Deterministic proximity channel from any coordinate — group users by location with no database.
AI Interpretation
Ask a question about a location and get a grounded, context-rich answer from Gemini.
Request
curl https://api.bwendi.com/\
context/CM/3.8667/11.5167 \
-H "x-api-key: YOUR_KEY"
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" },
"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" }
}
}
1
Get a key
Sign up at
bwendi.com and copy your API key from the dashboard.
2
Add it to every request
Send it as a header named x-api-key:
x-api-key: YOUR_API_KEY
Never expose your key in client-side code. Keep it server-side or in environment variables.
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' }
})
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, country info, and more.
Also available:
/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,
"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 |
Hierarchy:
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.
Cost breakdown: 1 cr for context + 15 cr for AI = 16 cr total. Add +1 each for ?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.
How it works: The sun's position is deterministic. Given a coordinate and UTC timestamp, we know exactly where shadows should fall. A user in Paris claiming to be in Douala will have shadows 10–20× too long. We analyse the device capture and compare it against the solar math.
Two modes:
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.
Cost: 1 credit per call. Limit: max 30 results returned.
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.
Cost: 1 credit per call. Use it when you want a country-aware address token like 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, metro anchor, anchors, and the human-readable context string — all in one authenticated call.
Cost: 2 credits per call. Combines a hyperlocal channel computation with a full country-database lookup.
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,
"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) |
| 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.
Cost: 2 credits per call. Country code must be provided in the path. Response is image/svg+xml and cacheable for 24 hours.
Scan me
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, and full country object — using Cloudflare's edge geolocation. The gateway enriches every request with x-cf-* headers before forwarding to the origin.
Coordinates are IP-based and may be inaccurate by 20–200 km depending on ISP. Use /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",
"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 |
| 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
}
| 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 |
GET /context/:cc/:lat/:lng
1 cr
Full geographic context — named place, admin hierarchy, nearby POIs, country info
↳ GET /context/:lat/:lng
2 cr
Same as above — CC auto-detected from coordinate grid (+1 cr)
↳ + ?lang=xx
+1 cr
Translate all place names and admin labels (e.g. fr, ar, sw)
↳ + ?expand=true
+1 cr
Resolve place anchors to full descriptive names instead of bare type keys
↳ + ?metrics=true
+3 cr
Expose raw gravity and weight scores on every resolved place
GET /search/:cc?q=…
1 cr
Fuzzy place-name search within a country, ranked by population — ideal for autocomplete
GET /hotspots/:lat/:lng/:type/:limit
1 cr
Top-N places near a coordinate ranked by gravity score. CC auto-resolved from edge headers.
GET /anchors/:range/:lat/:lng/:placeType/:limit
1 cr
Nearby anchors ordered by distance. CC auto-resolved from coordinates. Max 30 results.
GET /customAddress/:cc/:lat/:lng
1 cr
Default address plus a country-based custom address string with optional metro, market, and nearby-anchor prefixes.
GET /metro/:cc/:lat/:lng
1 cr
Dominant metro city or town near a coordinate, with full within admin chain
GET /markets/:tier/:cc
1 cr
All markets at or above a given tier. Use hubs for the 80/20 primary market list.
GET /country[/:cc]
1 cr
Country info from explicit CC, coordinate (0.25° grid), or IP geolocation
POST /interpret/:cc/:lat/:lng
16 cr
Context (1 cr) + Gemini AI narrative (15 cr). Add +1 each for ?lang and ?expand.
POST /verify
25 cr
Device location verification — upload a JPEG and challenge data to test whether the claimed GPS location is physically plausible.
POST /verify/challenge
400 cr
Create a signed verification challenge. This is the billable step for the hosted turnkey flow and returns a verifyUrl for verify.bwendi.com.
GET /verify/result/:id
0 cr
Poll for a turnkey verification result by challenge ID.
GET /hyperlocal/:lat/:lng
1 cr
Deterministic 8-char proximity channel shared by all callers within the same spatial cell
GET /geo
1 cr
IP-derived lat, lon, city, and full country object from Cloudflare edge geolocation
GET /health
1 cr
Server status, uptime, and loaded-country count
GET /countries[/:cc]
1 cr
List all countries with built data, or detailed stats for one
GET /languages
1 cr
Map of country codes to default languages, plus all unique language codes
| 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 |
Every error body includes a message field so you know exactly what went wrong.