OAuth 2.0
Delegated, revocable account access for third-party apps - Authorization Code + PKCE.
When to use this instead of an API key
An API key is a long-lived secret you hold yourself - right for your own scripts and server-to-server integrations. OAuth is for a third-party app acting on a Reeloop user's behalf: the user approves access on a Reeloop-hosted consent screen, your app never sees their password or a static secret, and they can revoke access at any time from Settings β Connected apps.
Reeloop implements Authorization Code with PKCE (RFC 6749 + RFC 7636) - the standard flow for web and native apps. There is one scope, full_access, matching exactly what an API key already grants (no granular per-route scopes yet). An OAuth access token works against both the public v1 REST API and the MCP server - pass it the same way you'd pass an API key.
Getting a client registered: from Settings β Your OAuth apps, register your app's name and redirect URI(s) (1-5, https:// only) and you'll get a client_id/client_secret pair back - the secret is shown once, so copy it immediately. Growth/Pro plans only, same gate as API keys and the MCP server.
1. Send the user to the consent screen
GET https://reeloop.ai/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&code_challenge=BASE64URL(SHA256(code_verifier))
&code_challenge_method=S256
&scope=full_access
&state=RANDOM_OPAQUE_VALUEGenerate code_verifier as a random string per RFC 7636 and keep it client-side until step 2. redirect_uri must exactly match one registered for your client. The user signs in (if needed), sees your app's name, and clicks Allow or Deny.
2. Exchange the code for tokens
Reeloop redirects to your redirect_uri with ?code=...&state=... (or ?error=access_denied if the user declined). Exchange the code immediately - it expires in 60 seconds and can only be used once:
POST https://reeloop.ai/api/oauth/token
Content-Type: application/x-www-form-urlencodedgrant_type=authorization_code &code=THE_CODE &redirect_uri=https://yourapp.com/callback &code_verifier=THE_ORIGINAL_VERIFIER &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET ```
{ "access_token": "oat_β¦", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "oar_β¦", "scope": "full_access" }3. Call the API
Use the access token exactly like an API key:
Authorization: Bearer oat_your_access_tokenIt expires after 1 hour - use the refresh token to get a new pair before then:
POST https://reeloop.ai/api/oauth/token
Content-Type: application/x-www-form-urlencodedgrant_type=refresh_token &refresh_token=oar_your_refresh_token &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET ```
Refreshing rotates the refresh token - the old one stops working the moment you use it, so always store the new one from the response.
Revoking access
Either side can end a grant: the user from Settings β Connected apps, or your app via the revocation endpoint (RFC 7009):
POST https://reeloop.ai/api/oauth/revoke
Content-Type: application/x-www-form-urlencodedtoken=THE_ACCESS_OR_REFRESH_TOKEN &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET ```
Always returns 200 with an empty body, whether or not the token was valid.
Errors
Token/refresh/revoke errors use the standard OAuth shape (not the {error:{code,message}} envelope the rest of the API uses):
{ "error": "invalid_grant", "error_description": "The request was rejected - the code/token may be invalid, expired, or already used." }What's not built yet
Granular per-route scopes (today it's all-or-nothing, same as an API key) and implicit/client-credentials/device-code grants - Authorization Code with PKCE is the only flow.