Authentication
OAuth apps
For apps acting on behalf of other Gravl users. Standard OAuth 2.0 authorization-code with PKCE: users grant your app specific scopes, you get short-lived access tokens with a rotating refresh token, and the grant is revocable at any time.
1. Register your app
Register your app yourself. Sign in with your Gravl account at app.gravl.ai/settings/developer and open the OAuth apps section, next to your personal access tokens.
Create an OAuth appYou’ll enter:
- Your app’s name and a short description of what it does — the name can’t contain “Gravl”
- An optional logo — a square PNG, JPEG or WebP (no SVG), at least 128×128 px and at most 1 MB. It appears on the consent screen next to your app’s name
- Your app’s homepage — an
httpsURL - Up to 5 redirect URIs — absolute
httpsURLs (http://localhostallowed for development) - The scopes your app needs — request the minimum; users see exactly what you ask for
- The client type — confidential for server-side apps that can keep a
client_secret, or public for browser and native apps, which get no secret and must use PKCE
You get a client_id (gci_…) at once. Confidential clients also get a client_secret (gcs_…, shown once — store it like a password). You can rotate the secret later from the same page. Each account can have up to 5 OAuth apps.
Development mode and review
A new app starts in development mode. It works immediately, but only your own Gravl account can authorize it — other users see a notice on the consent screen and can’t connect. Use it to build and test the whole flow.
- When your app is ready, click Submit for review. We check the name, description, logo, homepage, redirect URIs and scopes, and email you the decision
- After approval, any Gravl user can connect your app
- If we reject the app, the email and the settings page show the reason — edit the app and submit it again
- Approved apps, including their logo, can’t be edited from the settings page
To change an approved app, or for any question about registration or review, email developers@gravl.ai.
2. Send the user to the consent screen
https://api.gravl.ai/oauth/authorize
?client_id=gci_…
&redirect_uri=https://yourapp.example/callback // must be registered exactly
&scope=workouts:read stats:read // omit for all allowed scopes
&state=<random value you verify on return>
&code_challenge=<S256(code_verifier)>
&code_challenge_method=S256The user signs in with their Gravl account, sees your app’s name and the requested scopes in plain language, and approves — their browser comes back to your redirect_uri with ?code=gac_…&state=… (or error=access_denied if they cancel).
- Codes are prefixed
gac_, valid for 10 minutes, single use, and bound to your app and theredirect_urithey were issued for - PKCE (S256) is required for public clients (browser or native apps, which have no
client_secret) and strongly recommended for everyone - Always verify
statematches what you sent — it’s your CSRF protection
Public clients omit client_secret at the token endpoint — PKCE is their client authentication. You choose the client type when you create the app.
3. Exchange the code
curl -X POST https://api.gravl.ai/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=gac_… \
-d client_id=gci_… \
-d client_secret=gcs_… \
-d code_verifier=<your PKCE verifier> \
-d redirect_uri=https://yourapp.example/callback
{
"access_token": "gat_…",
"token_type": "Bearer",
"expires_in": 21600,
"refresh_token": "grt_…",
"scope": "workouts:read stats:read"
}| Token | Lifetime | Notes |
|---|---|---|
access_token | 6 hours | Bearer token for /api/v1 requests. |
refresh_token | Up to 180 days | Single use — each refresh revokes it and issues a new pair (rotation). |
4. Refresh
curl -X POST https://api.gravl.ai/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=grt_… \
-d client_id=gci_… \
-d client_secret=gcs_…The response has the same shape as the code exchange. The presented refresh token is revoked in the same transaction — always persist the new refresh_token before discarding the old one. Replaying a used refresh token returns invalid_grant.
5. Revoke
curl -X POST https://api.gravl.ai/oauth/revoke \
-d token=grt_… \
-d client_id=gci_… \
-d client_secret=gcs_…- Revoking an access token kills that token only
- Revoking a refresh token revokes the entire grant — every outstanding token for that user + app pair
- Per RFC 7009 the endpoint returns
200even for unknown tokens — only bad client credentials produce an error - Users can also revoke your app’s access from their account
Token endpoint errors
Errors follow RFC 6749 — { "error": "…", "error_description": "…" }:
| Error | Status | Meaning |
|---|---|---|
invalid_request | 400 | A required parameter is missing (e.g. code, refresh_token). |
invalid_client | 401 | Unknown client_id, wrong client_secret, or the app is suspended. |
invalid_grant | 400 | Code/refresh token is expired, revoked, already used, belongs to another app, or PKCE/redirect_uri validation failed. |
unsupported_grant_type | 400 | grant_type must be authorization_code or refresh_token. |