voltbase

API reference

Hand-written from the Worker routes. Read-only, stateless, open data only. Every error uses one typed envelope: {"error":{"code":"...","message":"..."}}.

GET /healthz

Liveness plus the build stage marker.

GET /healthz
{"ok":true,"stage":8}

GET /api/v1/sites

Search servable sites. Filters: bbox (minLon,minLat,maxLon,maxLat), connector (case-insensitive substring, max 64 chars), minPower (kW floor), openOnly (true keeps AVAILABLE rows only). Pagination: limit (1–100, default 20), offset (0–100000, default 0).

GET /api/v1/sites?bbox=4.0,52.0,5.0,53.0&connector=CCS2&minPower=50&openOnly=true&limit=20&offset=0

{
  "data": [{ "id": "OCM:900000", "source": "ocm", "licence": "CC-BY-4.0" }],
  "page": { "limit": 20, "offset": 0, "total": 35 },
  "attribution": [{ "text": "EnBW (via Open Charge Map)", "url": "https://openchargemap.io" }]
}

GET /api/v1/sites/:id

One servable site. IDs are source-prefixed (OCM:900000, OSM:node/22000101); encode the colon as %3A. Closed or unknown ids answer NOT_FOUND.

GET /api/v1/sites/OCM%3A900000
{"data": {"id": "OCM:900000", "source": "ocm", "licence": "CC-BY-4.0"}}

GET /api/v1/status/:id

Live status with a stale label against the 60-minute refresh SLO. Fixture rows predate the SLO, so expect stale:true with a staleReason (serving last-good static cut).

GET /api/v1/status/OCM%3A900000
{"data": {"id": "OCM:900000", "status": "AVAILABLE",
  "retrievedAt": "2026-09-14T06:15:00.000Z", "source": "ocm",
  "stale": true, "staleReason": "retrievedAt 2026-09-14T06:15:00.000Z is older than the 60min refresh SLO (serving last-good static cut)",
  "attribution": {"text": "EnBW (via Open Charge Map)", "url": "https://openchargemap.io"}}}

Errors

CodeStatusWhen
INVALID_ARGUMENTS400Bad bbox, connector, minPower, limit or offset
UNAUTHORIZED401Missing or invalid API key when the free-key gate is set
RATE_LIMITED429More than 60 requests per minute per IP (retry-after header set)
NOT_FOUND404Unknown route, or unknown/closed site id (never a closed signal)
UPSTREAM_BLOCKED403Non-allowlisted or private-origin request refused before any outbound call
PAYLOAD_TOO_LARGE413Request body over the cap, rejected before parsing
{"error": {"code": "NOT_FOUND", "message": "unknown site id"}}

MCP transport

The same four tools are served over Streamable HTTP at POST /mcp (use tools/list then tools/call). Non-POST methods answer the typed JSON-RPC error with status 405 — there is no session to hold. See MCP onboarding.

POST /mcp   # JSON-RPC tools/list, tools/call — 200
GET /mcp    # 405 Method not allowed: use POST /mcp for Streamable HTTP (stateless)