Developers
Mobility Hub API
Connect your CSMS, apps and software to Mobility Hub: read stations, receive events by webhook. Vouchers, loyalty and business profiles will use the same platform.
curl http://localhost:3000/api/v1/stations?limit=10 \ -H "Authorization: Bearer mh_sk_test_…"
Authentication
Send a secret key in the Authorization: Bearer header. Live keys (mh_sk_live_…) work with real data; test keys (mh_sk_test_…) with test data, never shown to drivers. Live keys can be created once Mobility Hub has verified the organization. Never put a secret key in a mobile app or a web page.
Scopes
A key can call only what its scopes allow; without the scope, the API answers 403 insufficient_scope.
stations:read: Read stationsevents:read: Read events
Operations
get /v1/meThe key that made the request, and its organization. Any key
get /v1/stationsList the organization's stations, by id. Scope: stations:read
get /v1/stations/{id}Retrieve a station. Scope: stations:read
get /v1/eventsList the events of the last 30 days, newest first. Scope: events:read
get /v1/events/{id}Retrieve an event. Scope: events:read
Versions
API versions are dated (2026-11-01). An organization is pinned to the newest when it creates its first key; send the MH-Version header to choose another. Within a version, Mobility Hub only adds; it never changes or removes.
Errors
Every error is application/problem+json (RFC 9457) with a stable code to branch on, and a request_id to quote to Mobility Hub.
| Code | Meaning |
|---|---|
| authentication_required | No key, or a wrong, revoked or expired one (401). |
| permission_denied | The organization cannot use the API in this mode (403). |
| insufficient_scope | The key lacks the scope for this operation (403). |
| resource_missing | Not found, or another organization's or mode's (404). |
| invalid_request | A parameter or MH-Version is not valid (400). |
| rate_limited | Over the rate limit; wait Retry-After seconds (429). |
| internal_error | Something failed on Mobility Hub's side; quote the request_id (500). |
Rate limits
A live key may send 200 requests every 10 seconds, a test key 50. The RateLimit header says what is left; past the limit the API answers 429 with Retry-After.
Pagination
Lists return object: "list", data and has_more. Get the next page with starting_after set to the last id of the previous one; limit is 1 to 100.
Webhooks
Each delivery is a JSON event with three Standard Webhooks headers: webhook-id, webhook-timestamp and webhook-signature. Answer 2xx within 10 seconds; otherwise Mobility Hub retries with growing intervals for about three days. Answer 410 to have Mobility Hub disable the endpoint. Events may arrive out of order and more than once: use webhook-id to skip duplicates.
Event types
station.created: Station addedstation.updated: Station editedstation.removed: Station removed
Verifying a signature (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";
// body: the raw request body, as received, before any JSON parsing.
export function verifyWebhook(secret, headers, body) {
const id = headers["webhook-id"];
const timestamp = Number(headers["webhook-timestamp"]);
if (!id || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest();
return String(headers["webhook-signature"] ?? "").split(" ").some((part) => {
const [version, signature] = part.split(",");
const given = Buffer.from(signature ?? "", "base64");
return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
});
}