Using the API
The Application Marketplace is under active development and may change without long deprecation windows. See the Application Marketplace overview.
Once your application has tokens (see Authenticating your application), it calls the Road API on the user's behalf with the access token.
Calling the API
Send the access token as a Bearer token:
GET https://api.road.io/1/...
Authorization: Bearer {access_token}
Each call acts as the user the token was issued for, and returns only what that user's scopes and data sharing allow.
Available data
The scoped data available today is the ERE charging data: the user's charging sessions and charging-station metadata. These are covered in full, including pagination and delta sync, on the ERE integration page.
Account and identity data is available from the userinfo endpoint (below). More scoped APIs are being added over time.
Token lifetime and expiry
Access tokens are short lived (expires_in seconds, typically one hour). Every rejected token carries a standard WWW-Authenticate challenge (RFC 6750) alongside the status code, so you can tell the scenarios apart without parsing error bodies:
| Response | Challenge | Meaning |
|---|---|---|
401 | error="invalid_token" | The token is expired, revoked, or no longer matches what the user has granted. Refresh and retry. |
403 | error="insufficient_scope" | The token does not hold the scopes this endpoint requires. Re-authorise requesting the missing scopes. |
A 401 on its own never means the user has disconnected your application: an expired token and a revoked one deliberately look identical at the resource. The refresh outcome tells you which it was.
Detecting revocation
A user can revoke your application at any time from Settings → Personal → Connected apps, and a provider administrator can remove or narrow the installation. Both take effect on the next API call, not at token expiry. What your application sees:
- A scoped call fails with
401anderror="invalid_token":
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="road-api", error="invalid_token", error_description="application installation or grant is no longer active"
- Attempt a refresh, exactly as for normal expiry. The outcome disambiguates:
| Refresh outcome | Meaning | What to do |
|---|---|---|
| Succeeds | The token had merely expired, or the granted scopes changed (the new token carries the currently granted scopes). | Retry with the new access token. |
400 with invalid_grant | The user revoked your application. | Stop polling, discard the stored tokens, and offer the user a way to reconnect. |
401 with invalid_client | The provider administrator removed the installation. | Same handling; reconnecting requires the installation to be restored first. |
If a freshly refreshed token then fails with 403 and error="insufficient_scope", the granted scopes were narrowed below what the endpoint needs (for example, a provider administrator removed a data scope from the installation). Re-run the authorisation flow requesting the scopes; the user sees a consent screen for them.
A public (browser) client without a refresh token uses the silent prompt=none flow instead; an error redirect (login_required or consent_required) is the equivalent disconnection signal.
Distinguish all of the above from an empty 200: that means the grant is intact and the user has simply shared no data (see ERE integration). Treat 401 plus a failed refresh as "disconnected", never as an empty share.
Refreshing tokens
If the user granted offline_access, the token response includes a refresh token. Exchange it at the token endpoint for a new access token:
POST {token_endpoint}
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token={refresh_token}&
client_id={client_id}&
client_secret={client_secret}
Confidential clients include client_secret; public clients omit it.
Every successful refresh issues a new refresh token and invalidates the previous one. Always store the new refresh_token from the response and discard the old one.
Silent refresh in the browser
A public (browser) client with no refresh token can obtain a fresh code without showing UI by re-running the authorisation request with prompt=none in a hidden iframe. If the user can be auto-approved a code is returned; otherwise you get a standard error redirect (login_required, consent_required, or interaction_required) and should fall back to an interactive flow.
UserInfo
GET {userinfo_endpoint}
Authorization: Bearer {access_token}
Returns scope-filtered claims. sub, provider_id, and account_id are always present. The email scope adds email and email_verified; the profile scope adds name, given_name, family_name, locale, and zoneinfo. The same identity claims are embedded in the ID token when those scopes are granted.
Common error cases
| Scenario | Symptom |
|---|---|
Unregistered redirect_uri | 400 from authorize/details with invalid_request. |
| Requested scope exceeds what the client may request | 400 with invalid_scope. |
| Bad PKCE verifier | 400 from /token with invalid_grant. |
| Replayed authorisation code | 400 from /token; codes are single use and short lived. |
prompt=none but interaction needed | Redirect with ?error=login_required, consent_required, or interaction_required. |
| User denies consent | Redirect with ?error=access_denied. |
| Expired or revoked access token | 401 with WWW-Authenticate: Bearer error="invalid_token"; refresh to disambiguate (above). |
| User revoked the application | 401 with error="invalid_token" on calls; the refresh then fails with 400 invalid_grant. |
| Token lacks a scope the endpoint requires | 403 with WWW-Authenticate: Bearer error="insufficient_scope". |
| Application holds a data scope but the user shared nothing | Scoped data endpoints return an empty list. Not an error. |
| Application token bound to a user with no account | 403 from the scoped data endpoints. |
Operational notes
- Secrets: a confidential client's secret is shown once at registration and again only on rotation. Never expose it on a frontend.
- Refresh token rotation: refresh tokens are single use; record the new one on every refresh.
- ID token verification: verify
iss,aud,exp, and the signature against the JWKS. - State: pass a CSRF-resistant
stateon the authorisation request and verify it on the callback.