ProRankedDashboard ↗

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 clientSecret — server-side only
eMSP — driver & roamingPublishable API key: reads public data; driver actions use the driver's sign-inSecret API key: full eMSP read + write for a backend
CPMS — operator control planeProRanked Connect (OAuth): short-lived operator tokens, no static keySecret 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:

  1. API Key (X-API-Key header) — a static key, publishable (read-only) or secret (server-side). Works on both the eMSP and CPMS families.
  2. 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 endpointhttps://id.proranked.com/connect/authorize
Token endpointhttps://id.proranked.com/connect/token
Reference client_idproranked-cpms-spa (a shared public PKCE client for local dev)
Redirect URI (local dev)http://localhost:5175/auth/callback
PKCErequired (code_challenge_method=S256)
Scopesopenid profile email offline_access + the cpms:* scopes you need
Token audiencehttps://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

  1. Redirect the operator to the authorization endpoint with your client_id, redirect_uri, response_type=code, scope, and a PKCE code_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=S256
    
  2. The operator signs in at id.proranked.com (or is already signed in) and authorizes your app.

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

  4. Call the API with the token:

    Authorization: Bearer <token>
    
  5. 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):

  1. In app.proranked.com, open OAuth clients (or pick CPMS · operator → Publishable on the API keys page — it routes here).
  2. Add your production redirect URI(s) and app origin(s) — the domains your SPA runs on.
  3. 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 KeyOperator OAuth
Where it livesYour backend (server-held secret)Nowhere — minted per operator session
Best forBackend integrations, cron jobs, webhooksBackend-less SPAs, no-code/low-code builders
Access scopeWhatever scopes you minted the key withWhatever the signed-in operator can already do
LifetimeUntil you revoke itShort-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" }