OAuth2
Build third-party applications that access Joulo data on behalf of users
If you're building an application that accesses Joulo data on behalf of other users, use OAuth2 with PKCE (Proof Key for Code Exchange) to obtain scoped access tokens.
OAuth2 is for third-party applications. If you only need to access your own data, use a personal API token instead.
Overview
Joulo implements the OAuth2 Authorization Code flow with PKCE:
- Your app redirects the user to Joulo to authorize access
- The user approves the requested scopes
- Joulo redirects back with an authorization code
- Your app exchanges the code for an access token and refresh token
Scopes
Request only the scopes your application needs:
| Scope | Access |
|---|---|
chargers:read | GET /chargers — charger status and active sessions |
chargers:write | POST /chargers — register new chargers |
sessions:read | GET /sessions — charging session history |
energy:read | GET /energy — energy statistics and aggregates |
Authorization flow
Step 1: Authorize
Send a POST request to the authorization endpoint with the user's Supabase JWT:
POST https://<project>.supabase.co/functions/v1/oauth-authorizeRequest body (JSON):
| Field | Required | Description |
|---|---|---|
client_id | Yes | Your OAuth client ID |
redirect_uri | Yes | Must match a registered redirect URI for your client |
response_type | Yes | Must be code |
scope | Yes | Space-separated list of scopes (e.g. chargers:read sessions:read) |
state | Yes | Random string to prevent CSRF — verify it matches on callback |
code_challenge | Yes | Base64url-encoded SHA-256 hash of the code_verifier |
code_challenge_method | Yes | Must be S256 |
The response contains a redirect_url with the authorization code and state parameters.
Step 2: Exchange the code for tokens
POST https://<project>.supabase.co/functions/v1/oauth-tokenRequest body (JSON or form-encoded):
| Field | Required | Description |
|---|---|---|
grant_type | Yes | Must be authorization_code |
client_id | Yes | Your OAuth client ID |
client_secret | Yes | Your OAuth client secret |
code | Yes | The authorization code received in step 1 |
redirect_uri | Yes | Must match the URI used in step 1 |
code_verifier | Yes | The original PKCE code verifier |
Response:
{
"access_token": "a1b2c3d4e5f6...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "f6e5d4c3b2a1...",
"scope": "chargers:read sessions:read"
}Step 3: Use the access token
Include the access token in the Authorization header when calling the Joulo API:
curl https://api.joulo.nl/functions/v1/api/chargers \
-H "Authorization: Bearer ACCESS_TOKEN"Step 4: Refresh the token
Access tokens expire after 1 hour. Use the refresh token to obtain a new pair:
POST https://<project>.supabase.co/functions/v1/oauth-token| Field | Required | Description |
|---|---|---|
grant_type | Yes | Must be refresh_token |
client_id | Yes | Your OAuth client ID |
client_secret | Yes | Your OAuth client secret |
refresh_token | Yes | The refresh token from the previous token response |
Refresh tokens expire after 30 days and are rotated on each use — the old refresh token is revoked when a new one is issued.
Token revocation
To revoke an access or refresh token:
POST https://<project>.supabase.co/functions/v1/oauth-revoke| Field | Required | Description |
|---|---|---|
token | Yes | The access or refresh token to revoke |
client_id | Yes | Your OAuth client ID |
client_secret | Yes | Your OAuth client secret |
Security requirements
- PKCE with
S256is required for all authorization requests - Authorization codes expire after 10 minutes and can only be used once
- Redirect URIs must be pre-registered on your OAuth client
- All token endpoints use
no-storecache headers
Store client secrets and refresh tokens securely. Never expose them in client-side code or public repositories.