API Reference

The GeoContext API gives your AI agent a sense of place — ranked semantic POI results, neighbourhood profiles, weather, and timezone in a single call.

Base URL https://api.YOUR_DOMAIN
Version v1
Pattern Async (fire & forget)
All endpoints use an async job pattern. Submit returns job_id (HTTP 202). Poll GET /jobs/{job_id} for the result.

Authentication

Every public endpoint (except GET /health) requires a Bearer token:

Authorization: Bearer <your_api_token>
StatusCause
401Missing or malformed Authorization header
403Invalid token

Async Job Pattern

All submit endpoints return a job_id immediately. The request is processed asynchronously by the API backend. Client polls until the result is ready.

1

Submit

POST your request. Receive job_id in a 202 response instantly.

2

Poll

GET /jobs/{id} every second. Status moves from queuedprocessing.

3

Result

Status becomes complete. Full result in the same response body.

# Submit POST /forward → 202 { "job_id": "3f8e2a1b-...", "status": "queued" } # Poll (repeat until complete) GET /jobs/3f8e2a1b-... → { "status": "processing" } GET /jobs/3f8e2a1b-... → { "status": "complete", "result": { ... } }

Job statuses

StatusMeaning
queuedAccepted, awaiting processing
processingBeing processed by the API backend
completeResult available in result field
errorExecution failed; see error field

Recommended polling strategy

1. Submit → receive job_id
2. Wait 500 ms
3. Poll GET /jobs/{job_id} every 1 s
4. Stop after 120 s (service may be unavailable)

POST /forward

Semantic geospatial POI search. The primary search endpoint. Finds ranked points of interest matching a natural-language query near a coordinate.

Request body

{
  "query": "wheelchair-accessible Italian restaurant",
  "lat": 52.5200,
  "lon": 13.4050,
  "radius_m": 1000,
  "limit": 10,
  "neighborhood": false,
  "weather": false,
  "map": false
}
FieldTypeRequiredDefaultDescription
querystringyesNatural language search query
latfloatyesLatitude (-90 to 90)
lonfloatyesLongitude (-180 to 180)
radius_mintno1000Search radius in metres
limitintno10Max results (1–50)
neighborhoodboolnofalseInclude neighbourhood POI profile
weatherboolnofalseInclude current weather
mapboolnofalseInclude static map image URL

Response

{
  "job_id": "3f8e2a1b-4c9d-4e5f-8a1b-2c3d4e5f6a7b",
  "status": "complete",
  "result": {
    "query": "wheelchair-accessible Italian restaurant",
    "location": { "lat": 52.52, "lon": 13.405, "display_name": "Berlin, Germany" },
    "results": [
      {
        "osm_id": 123456789,
        "osm_type": "node",
        "name": "Trattoria Roma",
        "description": "Italian restaurant. Wheelchair accessible. Outdoor seating.",
        "lat": 52.5198,
        "lon": 13.4048,
        "distance_m": 45,
        "relevance_score": 0.91,
        "combined_score": 0.87,
        "tags": { "amenity": "restaurant", "cuisine": "italian", "wheelchair": "yes" },
        "opening_hours": "Mo-Su 12:00-22:00",
        "is_open": true,
        "confidence": 0.95
      }
    ],
    "neighborhood": null,
    "weather": null,
    "suggested_queries": ["nearby parking", "wheelchair route to restaurant"]
  }
}

curl example

curl -X POST https://api.YOUR_DOMAIN/forward \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "wheelchair-accessible Italian restaurant",
    "lat": 52.52, "lon": 13.405
  }'
# → { "job_id": "3f8e...", "status": "queued" }

curl https://api.YOUR_DOMAIN/jobs/3f8e2a1b-... \
  -H "Authorization: Bearer $API_TOKEN"
# → { "job_id": "...", "status": "complete", "result": { ... } }

POST /neighbourhood

Returns a POI category profile for a coordinate — useful for understanding what kind of area a location is in.

Request body

{
  "lat": 52.5200,
  "lon": 13.4050,
  "radius_m": 500
}

Response result

{
  "lat": 52.52,
  "lon": 13.405,
  "radius_m": 500,
  "categories": {
    "restaurant": 12,
    "cafe": 8,
    "transit_stop": 4,
    "park": 2,
    "pharmacy": 1
  },
  "walkability": "high",
  "summary": "Dense mixed-use urban area with strong food/drink and transit presence."
}

POST /reverse

Full street-level reverse geocoding: coordinates → address + optional context components.

Request body

{
  "lat": 52.5200,
  "lon": 13.4050,
  "neighborhood": false,
  "weather": false,
  "map": false
}

Response result

{
  "lat": 52.52,
  "lon": 13.405,
  "display_name": "Unter den Linden 1, Mitte, Berlin, 10117, Germany",
  "address": {
    "road": "Unter den Linden",
    "house_number": "1",
    "suburb": "Mitte",
    "city": "Berlin",
    "postcode": "10117",
    "country": "Germany",
    "country_code": "de"
  },
  "neighborhood": null,
  "weather": null
}

POST /context

Fat context endpoint — address + timezone + weather + neighbourhood in a single parallel call. Designed for pre-loading LLM system prompts.

Request body

{
  "lat": 52.5200,
  "lon": 13.4050
}

Response result

{
  "lat": 52.52,
  "lon": 13.405,
  "address": {
    "display_name": "Mitte, Berlin, Germany",
    "country_code": "de"
  },
  "timezone": {
    "iana": "Europe/Berlin",
    "utc_offset_h": 2,
    "dst": true
  },
  "weather": {
    "current": { "temp_c": 18.5, "condition": "Partly cloudy" },
    "forecast": [ { "date": "2026-05-28", "max_c": 21, "min_c": 12 } ]
  },
  "neighborhood": {
    "categories": { "restaurant": 12, "cafe": 8 },
    "walkability": "high"
  }
}

GET /weather

Current conditions and 7-day forecast.

Query parameters

ParameterTypeRequiredDescription
latfloatyesLatitude
lonfloatyesLongitude

Example

curl "https://api.YOUR_DOMAIN/weather?lat=52.52&lon=13.405" \
  -H "Authorization: Bearer $API_TOKEN"

Response result

{
  "lat": 52.52,
  "lon": 13.405,
  "timezone": "Europe/Berlin",
  "current": {
    "temp_c": 18.5,
    "feels_like_c": 17.2,
    "humidity_pct": 62,
    "wind_kph": 14,
    "condition": "Partly cloudy",
    "is_day": true
  },
  "forecast": [
    {
      "date": "2026-05-28",
      "max_c": 21,
      "min_c": 12,
      "condition": "Sunny",
      "precip_mm": 0
    }
  ]
}

GET /timezone

IANA timezone, UTC offset, and DST status for a coordinate.

Query parameters

GET /timezone?lat=52.52&lon=13.405

Response result

{
  "lat": 52.52,
  "lon": 13.405,
  "iana": "Europe/Berlin",
  "utc_offset_h": 2,
  "dst": true
}

GET /changes

Returns entities modified since a given timestamp within a bounding box. Useful for cache invalidation or change detection.

Query parameters

ParameterTypeRequiredDescription
sinceintyesUnix timestamp (seconds)
min_latfloatyesBounding box south
max_latfloatyesBounding box north
min_lonfloatyesBounding box west
max_lonfloatyesBounding box east
limitintnoMax results (default 100)

Response result

{
  "count": 3,
  "entities": [
    {
      "osm_id": 123,
      "osm_type": "node",
      "last_modified_ts": 1748390400,
      "name": "Café Berlino"
    }
  ]
}

GET /jobs/{job_id}

Poll for job status and result. Authentication required.

Responses

{ "job_id": "3f8e...", "status": "queued" }
{ "job_id": "3f8e...", "status": "processing" }
{ "job_id": "3f8e...", "status": "complete", "result": { ... } }
{ "job_id": "3f8e...", "status": "error", "error": "upstream error" }
Jobs are retained for 24 hours after completion. After that, GET /jobs/{id} returns 404.

GET /health

Liveness check. No authentication required.
{ "status": "ok" }

Rate Limits

Each Bearer token is subject to a sliding-window rate limit:

LimitValue
Requests per token10 per 60 seconds
Window typeSliding (rolling per-second)

When the limit is exceeded the server responds with HTTP 429:

{"detail": "rate limit exceeded"}

If the global job queue exceeds 500 pending jobs, new submissions also return HTTP 429:

{"detail": "queue full, try again later"}
Retry after a short back-off of 1–5 seconds. Do not hammer the endpoint — repeated 429s will not unblock faster.

Error Codes

HTTPCause
401Missing or malformed Authorization header
403Invalid Bearer token
404GET /jobs/{id} — job not found or expired
422Request body validation error (missing required fields)
429Rate limit exceeded or queue full — back off and retry
502Backend returned an error processing this job
503Service temporarily unavailable

Polling Guide

The recommended client polling loop:

import httpx, time

BASE  = "https://api.YOUR_DOMAIN"
HDRS  = {"Authorization": "Bearer YOUR_TOKEN", "Content-Type": "application/json"}

def search(query: str, lat: float, lon: float) -> dict:
    # 1. Submit
    r = httpx.post(f"{BASE}/forward",
                   json={"query": query, "lat": lat, "lon": lon},
                   headers=HDRS)
    job_id = r.json()["job_id"]

    # 2. Poll
    time.sleep(0.5)
    for _ in range(120):
        r = httpx.get(f"{BASE}/jobs/{job_id}", headers=HDRS)
        data = r.json()
        if data["status"] == "complete":
            return data["result"]
        if data["status"] == "error":
            raise RuntimeError(data.get("error", "unknown error"))
        time.sleep(1.0)

    raise TimeoutError("job did not complete within 120 s")

MCP Tool Example

Wrap POST /context as a Model Context Protocol tool to give any LLM a sense of place before inference:

# MCP tool definition
{
  "name": "get_location_context",
  "description": "Get rich geospatial context for a coordinate: address, timezone, weather, POI profile.",
  "input_schema": {
    "type": "object",
    "properties": {
      "lat": { "type": "number", "description": "Latitude" },
      "lon": { "type": "number", "description": "Longitude" }
    },
    "required": ["lat", "lon"]
  }
}

# Handler: POST /context → poll /jobs/{id} → return result as tool output
The /context endpoint runs address, timezone, weather, and neighbourhood lookups in parallel — a single tool call gives the LLM full environmental awareness.

Pricing

TierPriceIncluded requestsOverage
Free$0 / mo3,000
Starter$49 / mo10,000$7.00 / 1,000
Growth$299 / mo100,000$3.08 / 1,000
Pro$999 / mo600,000$1.67 / 1,000
EnterpriseCustomCustom volume + SLA

The free tier requires no credit card. Overage is billed at the per-request rate implied by the plan price after subtracting the 3,000 free requests included in all tiers.