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 stations
  • events:read: Read events

Operations

  • get /v1/me

    The key that made the request, and its organization. Any key

  • get /v1/stations

    List the organization's stations, by id. Scope: stations:read

  • get /v1/stations/{id}

    Retrieve a station. Scope: stations:read

  • get /v1/events

    List 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.

CodeMeaning
authentication_requiredNo key, or a wrong, revoked or expired one (401).
permission_deniedThe organization cannot use the API in this mode (403).
insufficient_scopeThe key lacks the scope for this operation (403).
resource_missingNot found, or another organization's or mode's (404).
invalid_requestA parameter or MH-Version is not valid (400).
rate_limitedOver the rate limit; wait Retry-After seconds (429).
internal_errorSomething 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 added
  • station.updated: Station edited
  • station.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);
  });
}