OAuth 2.1
Authorization-code flow with PKCE, dynamic client registration, account switching, prompts, token lifetimes, and refresh rotation.
Mouve implements OAuth 2.1: the authorization-code grant with PKCE
(S256) required for every client. Implicit and password grants do not
exist. If you have used any modern OAuth provider, nothing here will
surprise you.
Not yet live
App registration and the endpoints below open with the API launch.
Registering an app
Two ways, one registry:
- Developer portal (recommended): mouve.it/developers — create an
app, set redirect URIs, get a
client_id(and, for confidential clients, a client secret). Creating an app requires accepting the Developer Terms (see terms). - Dynamic client registration (RFC 7591): open, anonymous
POSTto the registration endpoint — this is how MCP clients such as Claude Desktop self-register. DCR apps start on therestrictedrate tier; claim your app in the portal to lift it. A registration that names noscopeis registered for the entire scope catalog. What your app can actually do is decided by the user on the consent screen, never by registration alone — in particular, health-data scopes are granted only if the user ticks the separate health-data checkbox there, so always read thescopefield of the token response rather than assuming you received what you requested.
Redirect URIs must be exact-match HTTPS (loopback excepted for native apps).
Discovery
Standard metadata endpoints, suitable for any off-the-shelf OAuth client:
https://api.mouve.it/.well-known/oauth-authorization-server
https://api.mouve.it/.well-known/oauth-protected-resourcescopes_supported in the metadata is exactly the
public scope catalog.
The flow
- Send the user to the authorization endpoint with
client_id,redirect_uri,code_challenge(+_method=S256),state, and the space-separatedscopelist. - Mouve shows a consent screen listing every requested scope in plain
language — the same wording as the scope catalog.
Health-data scopes appear in a separate opt-in section: if the user
leaves it unticked, the grant simply proceeds without them (the
token response's
scopetells you what you actually got). The grant is recorded as a consent record. - The user approves; your
redirect_urireceivescode+state. - Exchange the code at the token endpoint with your
code_verifier(and client secret, if confidential):
{
"access_token": "eyJhbGciOiJSUzI1NiIs…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "profile.read posts.write"
}Prompts and account switching
The consent screen always lets the user switch accounts ("Not you? Use another
account"). That signs the current Mouve session out and returns the user to
sign-in for the same authorization request — the original client_id,
redirect_uri, scope, state, PKCE code_challenge and resource are kept,
so your flow completes without starting over. The pending request expires after
10 minutes.
Send the standard OIDC prompt parameter (space-separated) to control the
account step:
prompt | Behaviour |
|---|---|
login | Force a fresh sign-in, even when a Mouve session already exists. A session created before the authorization request does not count. |
select_account | Show the account step even when a session exists — the user signs in again and may pick another account. |
consent | Always show the consent screen. Mouve already shows it on every authorization, so this is the default behaviour. |
https://api.mouve.it/oauth/authorize
?response_type=code
&client_id=…
&redirect_uri=https%3A%2F%2Fyour.app%2Fcallback
&code_challenge=…
&code_challenge_method=S256
&scope=profile.read%20workouts.read
&state=…
&prompt=loginprompt=none and unknown values are rejected with HTTP 400 and error code
invalid_request: a silent authorization needs a session check this endpoint
cannot perform, and ignoring prompt=login would hand back the account the user
just asked to change.
Token lifetimes and refresh rotation
- Access tokens live 1 hour. Plan long-running work (e.g. a slow thread of scheduled replies) to stay under that, or refresh between steps.
- Refresh tokens rotate on every use: each refresh returns a new refresh token and invalidates the old one. Persist the new one immediately — before acting on the new access token. A short server-side grace window absorbs crash-retry races, but reuse of a rotated token outside it revokes the whole grant (theft detection), and the user must reconnect.
- A grant whose refresh token goes unused long enough to expire (about 30 days idle) requires the user to reconnect.
Errors you must handle
| Response | Meaning | Your move |
|---|---|---|
401 with TOKEN_EXPIRED | Access token past its hour | Refresh, retry once |
401 with TOKEN_REVOKED | User disconnected, or rotation-reuse tripped | Stop; ask the user to reconnect |
invalid_grant (token endpoint) | Refresh token no longer valid | Same — reconnect |
403 | Token is valid; the action is not permitted | Do not refresh; see errors |
Revocation is checked per request — see authentication.