# Using the API

> Calling the API with an access token, refreshing tokens, and handling revocation and errors.

> **Preview / beta**
>
> The Application Marketplace is under active development and may change without long deprecation windows. See the [Application Marketplace overview](/docs/platform/integrations/marketplace).

Once your application has tokens (see [Authenticating your application](/docs/platform/integrations/marketplace/authentication)), 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:

```http
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](/docs/platform/charge-point-operation/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:

1. A scoped call fails with `401` and `error="invalid_token"`:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="road-api", error="invalid_token", error_description="application installation or grant is no longer active"
```

2. 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](/docs/platform/charge-point-operation/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:

```http
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.

> **Refresh tokens rotate**
>
> 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

```http
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 `state` on the authorisation request and verify it on the callback.
