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.

Get API key →
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.
Quick start
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.

GET /context/:cc/:lat/:lng Auth 1 cr

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

ParamTypeNote
ccstringISO 3166-1 alpha-2 (e.g. CM)
latnumber-90 to 90
lngnumber-180 to 180

Query options

ParamEffectCost
?lang=xxTranslate all labels (e.g. fr, es)+1 cr
?expand=trueResolve place anchors to full names+1 cr
?metrics=trueInclude 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"
    }
  }
}
GET /markets/:tier/:cc Auth 1 cr

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

ParamTypeNote
tierstringdominant · prime · high · integrated · moderate · peripheral · marginal · isolated · hubs
ccstringOptional — omit to detect from headers
Hierarchy: dominantprimehighintegratedmoderateperipheralmarginalisolated

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 }
  ]
}
GET /country[/:cc] Auth 1 cr

Country info — no backend round-trip. Resolves from three sources:

PathResolves from
/countryCloudflare geolocation header
/country/CMCountry code you provide
/country/3.87/11.52Coordinates (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" }
}
POST /interpret/:cc/:lat/:lng Auth 16 cr

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

ParamTypeNote
ccstringISO 3166-1 alpha-2
latnumber-90 to 90
lngnumber-180 to 180

Request body

FieldTypeNote
promptstringRequired, 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..."
}
POST /verify Auth 25 cr

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)

FieldTypeRequiredNote
imagefileyesJPEG only, max 1 MB
challengeIdstringyesUUID from your server
claimedLatnumberyes-90 to 90
claimedLngnumberyes-180 to 180
issuedAtstringyesISO 8601 UTC timestamp (server-issued)
signaturestringyesHMAC-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

VerdictMeaning
passShadows are consistent with the claimed location and time.
failShadows contradict the expected solar position — likely spoofed location.
inconclusiveCannot determine — indoor photo, overcast sky, or no visible shadows.

Error responses

StatusReason
400Missing/invalid fields, wrong content-type, file too large, invalid signature
410Challenge expired (90-second window)
422Sun below horizon at claimed location/time
500Gemini 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

POST /verify/challenge Auth 400 cr
FieldTypeNote
latnumberClaimed latitude (-90 to 90)
lngnumberClaimed longitude (-180 to 180)
callbackstringOptional. 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

GET /verify/result/:challengeId Auth 0 cr

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"]
}
GET /hotspots/:lat/:lng/:placeType/:limit Auth 1 cr

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

ParamTypeNote
latnumber-90 to 90
lngnumber-180 to 180
placeTypestringplaces for all settlement types, or a specific type (e.g. city, town, neighbourhood). Comma-separated for multiple.
limitintMax results (1–100)

Query options

ParamDefaultEffect
?wide=falsetrueRestrict to a single big-zone cell (~11 km) instead of the default 3×3 grid (~33 km)
?admin=truefalseInclude 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

FieldDescription
namePlace name
typeSettlement type (city, town, suburb, neighbourhood, …)
lat / lngCoordinates of the place
distance_kmDistance from the queried coordinate (km)
scoreGravity score used for ranking
populationPopulation count, if available
gravityRaw gravity value (same as score)
GET /anchors/:range/:lat/:lng/:placeType/:limit Auth 1 cr

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

ParamTypeNote
rangestringimmediate for the 3×3 zone bucket (~333 m), or local for the 3×3 local_zone bucket (~3.3 km)
latnumber-90 to 90
lngnumber-180 to 180
placeTypestringplaces for all settlement/place types, or a specific type such as village, town, neighbourhood. Comma-separated values are supported.
limitintMax 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

FieldDescription
rangeThe bucket tier used: immediate or local
bucket_width_mApproximate width of the searched tier in metres
place_typeThe type filter you requested
anchorsArray of matching places ordered by distance ascending
distance_mRounded distance from the query coordinate in metres
distance_kmDistance from the query coordinate in kilometres
populationIncluded when available on the source record
gravityIncluded when available on the source record
labelIncluded when available on the source record
GET /metro/:cc/:lat/:lng Auth 1 cr

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

ParamTypeNote
ccstringISO 3166-1 alpha-2 (e.g. CM)
latnumber-90 to 90
lngnumber-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

FieldDescription
nameMetro city/town name
typeSettlement type (city or town)
distanceDistance from queried coordinate in metres
directionCardinal direction from coordinate to metro (N, NE, E, …)
lat / lngMetro coordinates
populationPopulation count, if available
economic_tierdominant · prime · high · integrated · moderate · peripheral · marginal · isolated
withinNested administrative hierarchy (region → country). Present when available.
contextHuman-readable distance + direction string
GET /hyperlocal/:lat/:lng Auth 1 cr

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

ParamDefaultRangeUse case
range1100 m50–50000Pooling radius in metres

Range presets

Labelrange=Use case
Spot50Same room
Block100Building cluster
Street500Walking neighbourhood
Quarter1000Urban ward
Town5000City district
City15000City-wide
Region50000Metro region

Example response

{
  "channel": "6b099229",
  "tier": "local",
  "cell_size_m": 1100,
  "range_m": 500
}
GET /address/:cc/:lat/:lng Auth 2 cr

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

ParamTypeNote
ccstringISO 3166-1 alpha-2 (e.g. CM)
latnumber-90 to 90
lngnumber-180 to 180

Query options

ParamDefaultRangeEffect
range1100 m50–50000Pooling radius — controls address string precision tier

Range presets

Labelrange=Use case
Spot50Indoor floor, kiosk
Block200Street segment, city block
Quarter500Neighbourhood, trading cluster
Town1100Town centre, district (default)
City5000Metro area, city-wide
Region25000Province, 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

FieldDescription
addressHyperlocal channel string (precision cell key) — the coordinate's shareable address token
plus_codeOpen Location Code (10-char) — globally interoperable, compatible with Google Maps
tierHyperlocal tier used: zone, local_zone, or big_zone
range_mEffective pooling radius in metres (clamped to 50–50,000)
placeNearest named place — name, type (town / village / locality / junction etc.), label, coordinates, distance (m), and direction
demTerrain elevation in metres (from nearest resolved place)
withinNested admin hierarchy from immediate container up to region
anchorsEconomic anchor types — immediate, local, and reachable
metroDominant city/town anchor with distance and direction
contextHuman-readable location description in the country's default language
countryCountry metadata (name, code, flag, currency)
GET /qr/:cc/:lat/:lng/:range Auth 2 cr

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
Live QR code — Etoa-Meki, Yaoundé, CM

3.8667° N · 11.5167° E · CM · range 1100 m

Parameters

ParamTypeNote
ccstringISO 3166-1 alpha-2 (e.g. CM)
latnumber-90 to 90
lngnumber-180 to 180
rangenumberPooling radius in metres (50–50,000). Controls address tier precision.

Range presets

Labelrange=Use case
Spot50Indoor floor, kiosk
Block200Street segment, city block
Quarter500Neighbourhood, trading cluster
Town1100Town centre, district (default)
City5000Metro area, city-wide
Region25000Province, 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.

GET /geo Auth 1 cr

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

FieldDescription
latIP-derived latitude (4 d.p.), or null
lngIP-derived longitude (4 d.p.), or null
ccISO 3166-1 alpha-2 country code, or null
cityCity name from Cloudflare, or null
countryFull country object (name, currency, flag, lang, callingCode), or null
GET /health Auth 1 cr

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"
}
GET /countries[/:cc] Auth 1 cr

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 }
GET /languages Auth 1 cr

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
}
CodeLanguageCodeLanguageCodeLanguage
amAmharicarArabicazAzerbaijani
beBelarusianbgBulgarianbnBengali
bsBosniancaCatalancsCzech
daDanishdeDeutschelGreek
enEnglishesEspañolfaPersian
fiFinnishfrFrançaishaHausa
heHebrewhiHindihrCroatian
htHaitian CreolehuHungarianhyArmenian
idIndonesianisIcelandicitItaliano
jaJapanesekaGeorgiankkKazakh
klGreenlandickmKhmerkoKorean
lbLuxembourgishloLaoltLithuanian
lvLatvianmgMalagasymkMacedonian
mnMongolianmsMalaymtMaltese
myBurmeseneNepalinlDutch
noNorwegianomOromoplPolish
ptPortuguêsroRomanianruRussian
rwKinyarwandasiSinhalaskSlovak
slSloveniansmSamoansoSomali
sqAlbaniansrSerbiansvSwedish
swSwahilitaTamiltgTajik
thThaitiTigrinyatkTurkmen
tlFilipinotrTurkishukUkrainian
urUrduuzUzbekviVietnamese
yoYorubazhChinesezuZulu
Coordinate-to-context resolution
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 /metro/:cc/:lat/:lng
1 cr
Dominant metro city or town near a coordinate, with full within admin chain
Economic tier lists and country data
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
Interpretation, proximity pooling, and edge 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
Server status and directory endpoints
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
HeaderValue
x-credit-costCredits charged for this request (0–21)
x-balance-remainingCredits left on your key (−1 = unlimited)
x-resolved-ccAuto-detected country code (when CC was omitted)
StatusMeaning
400Invalid parameters
401Missing API key
402Insufficient credits
403Invalid or inactive key
404Country not found or no nearby places
429Rate limit exceeded (60 req/min default)
500Server error
Every error body includes a message field so you know exactly what went wrong.