Authentication
Two things decide how you authenticate: which API you're calling and where your code runs.
Choose your credential
| Publishable — safe to ship in a client | Secret — server-side only | |
|---|---|---|
| eMSP — driver & roaming | Publishable API key: reads public data; driver actions use the driver's sign-in | Secret API key: full eMSP read + write for a backend |
| CPMS — operator control plane | ProRanked Connect (OAuth): short-lived operator tokens, no static key | Secret API key: operator read + write for a backend |
- Publishable credentials are safe to embed in a browser, mobile app, or no-code project. On eMSP that's a read-only API key. On CPMS it's a ProRanked Connect OAuth client — not a static key, because a static operator key sitting in client code would be full operator access anyone could copy.
- Secret credentials are server-held API keys. Keep them on a backend; never ship them in a client.
Two mechanisms implement this:
- API Key (
X-API-Keyheader) — a static key, publishable (read-only) or secret (server-side). Works on both the eMSP and CPMS families. - Operator OAuth (
Authorization: Bearer) — ProRanked Connect. The browser-safe way onto the CPMS API, with no server-held secret at all.
Method 1 — API Key
Send your key as the X-API-Key header on every request:
X-API-Key: pr_…
A key encodes its class and environment in the prefix, Stripe-style: pr_pk_live_… / pr_pk_test_… for publishable keys and pr_sk_live_… / pr_sk_test_… for secret keys (pk/sk = class, live/test = environment). So a leaked key is classifiable at a glance. New keys default to test; pick the environment when you create one. The API family (emsp or cpms) is chosen when you create the key in the dashboard and shown next to it — it is not part of the key string. (Older pr_… keys without the pk/sk infix keep working.)
Test vs. live
A test key is a full-strength credential that is simply never metered: same scopes, same data, same endpoints, but its requests are not rate-limited, not counted against your plan's quota, and not billed. Use one while you build and in CI; mint a live key when you go to production. The environment is fixed at creation and visible in the key's prefix, so a test key can never be mistaken for a live one.
Publishable vs. secret
- Publishable keys are read + discovery only, on both families — safe to embed in a client because the key alone can't write.
- Secret keys are server-side credentials carrying the read/write scopes you grant them.
Never embed a Secret key in a browser, mobile app, or any client you don't control. Rotate a key immediately in the dashboard if it leaks.
eMSP driver actions
Reading public eMSP data (locations, tariffs, charger status) needs only your key. Driver write actions — starting/stopping a session, managing a driver's account — additionally require that driver's access token: sign the driver in at /emsp/v1/auth/driver/login and send their JWT as Authorization: Bearer <driver-token> alongside the X-API-Key. That two-part model is what lets a publishable eMSP key be safe in a driver app — the key identifies your app, the driver's token authorizes the action.
Scopes
Keys carry least-privilege scopes, namespaced by family: eMSP uses emsp:* names (emsp:read:chargers, emsp:write:sessions); CPMS uses cpms:* names (cpms:read:chargers, cpms:write:tariffs). Mint a key with only the scopes it needs. Publishable keys are limited to read/discovery regardless of what you request.
Keys minted before the emsp:* namespacing carry the older unprefixed names (read:chargers, write:sessions) and keep working unchanged — the API accepts both. New keys use emsp:*.
Networks (tenancy)
Tenancy comes from the key, never a client-supplied header — a key only ever sees the networks it's authorized for. First-party AllNetworks keys target a single network per request via the X-Network-Id header (a network UUID).
Every list endpoint is network-scoped server-side. The unfiltered CPMS list endpoints — GET /cpms/v1/chargers, /cpms/v1/locations, /cpms/v1/tariffs, /cpms/v1/sessions, and their per-resource reads — return only the rows belonging to the key's granted network set; there is no way to widen the result to another network. So a network-pinned sub-key (one authorized for a single network) sees exactly that one network's chargers, locations, and sessions — safe to hand to a per-network integration.
Method 2 — Operator OAuth (ProRanked Connect)
If you're shipping a pure frontend — no backend to hold an API key — sign the operator in with OAuth instead. This is ProRanked Connect: the operator authenticates once at id.proranked.com, and your SPA gets back a short-lived, role-scoped token it uses directly against /cpms/v1.
Building a platform that onboards ProRanked operators (an ERP, CRM, or fleet tool) with its own backend? Use a confidential client to connect an account and mint network-pinned API keys on the operator's behalf — see Connect a ProRanked account. The flow below is the public (browser-only, no secret) variant.
Endpoints & parameters
Everything is standard OAuth 2.1 (authorization code + PKCE, no client secret). The issuer is https://id.proranked.com — its discovery document lives at https://id.proranked.com/.well-known/openid-configuration.
| Authorization endpoint | https://id.proranked.com/connect/authorize |
| Token endpoint | https://id.proranked.com/connect/token |
Reference client_id | proranked-cpms-spa (a shared public PKCE client for local dev) |
| Redirect URI (local dev) | http://localhost:5175/auth/callback |
| PKCE | required (code_challenge_method=S256) |
| Scopes | openid profile email offline_access + the cpms:* scopes you need |
| Token audience | https://api.proranked.com/cpms/v1 (bind requests here) |
Grant the least-privilege scopes your app needs, e.g. cpms:read:chargers, cpms:read:sessions, cpms:write:tariffs. The token you get back is further intersected with the signed-in operator's own role — it can never grant more than the operator already has.
The flow
Redirect the operator to the authorization endpoint with your
client_id,redirect_uri,response_type=code,scope, and a PKCEcode_challenge(S256):https://id.proranked.com/connect/authorize?client_id=proranked-cpms-spa &response_type=code &redirect_uri=http://localhost:5175/auth/callback &scope=openid%20profile%20email%20offline_access%20cpms:read:chargers &code_challenge=<S256(verifier)>&code_challenge_method=S256The operator signs in at
id.proranked.com(or is already signed in) and authorizes your app.Exchange the code at the token endpoint (sending your PKCE
code_verifier) for an access token + refresh token. The access token is scoped to the operator's role and network memberships.Call the API with the token:
Authorization: Bearer <token>Tokens are short-lived — refresh via the standard OAuth refresh-token grant rather than minting a long-lived key.
Tokens can optionally be DPoP sender-constrained (RFC 9449): send Authorization: DPoP <token> plus a DPoP proof header, and a stolen token can't be replayed from anywhere but the browser that requested it. Recommended for anything handling payments or remote-control commands.
Register your own Connect client
The shared proranked-cpms-spa client is for local development only — it's registered for http://localhost:5175/auth/callback. To ship your SPA on your own domain, register your own Connect client (self-serve, no ticket needed):
- In app.proranked.com, open OAuth clients (or pick CPMS · operator → Publishable on the API keys page — it routes here).
- Add your production redirect URI(s) and app origin(s) — the domains your SPA runs on.
- You get your own
pr_spa_…client_id. Use it in the flow above.
Your registered origins are automatically allowlisted for CORS on /cpms/v1; unregistered domains are blocked in the browser. Common SPA hosts (*.lovable.app, *.vercel.app, *.netlify.app, *.pages.dev) are already allowed out of the box for the shared dev client, so you can prototype before registering. If you get stuck, reach us at [email protected].
API Key vs. OAuth — which one?
| API Key | Operator OAuth | |
|---|---|---|
| Where it lives | Your backend (server-held secret) | Nowhere — minted per operator session |
| Best for | Backend integrations, cron jobs, webhooks | Backend-less SPAs, no-code/low-code builders |
| Access scope | Whatever scopes you minted the key with | Whatever the signed-in operator can already do |
| Lifetime | Until you revoke it | Short-lived, refreshable |
Both authenticate the exact same /cpms/v1/* surface — this is purely a question of where the credential lives, not a difference in capability.
Errors
Errors return a stable envelope regardless of which auth method you used:
{ "error": "not_found", "code": "not_found", "message": "Charger '…' not found" }