sequenceDiagram
participant Backend as Customer backend
participant Browser as End-user browser
participant Cliodot as Cliodot OAuth Apps
participant IdP as Identity provider
Backend->>Browser: Redirect to connect URL
Browser->>Cliodot: GET/POST /oauth/apps/{appId}/connect
alt authorization_code / OIDC / SAML redirect
Cliodot->>Browser: 302 to IdP authorization URL
Browser->>IdP: User consents
IdP->>Cliodot: GET/POST /oauth/callback/{appId}/{provider}
Cliodot->>Cliodot: Exchange code, store connection
Cliodot->>Browser: 302 to customer redirect_uri ?code=ex_...&connection_id=conn_...
else client_credentials / assertion grants
Cliodot->>Cliodot: Server-side token exchange
Cliodot->>Browser: 302 or JSON with exchange_code
else device_code grant
Cliodot->>Browser: JSON with device_code + poll_state
loop Until authorized
Backend->>Cliodot: POST /oauth/apps/{appId}/connect/poll
end
Cliodot->>Backend: exchange_code + connection_id
end
Browser->>Backend: Callback with code=ex_...&connection_id=conn_...
Backend->>Cliodot: POST /oauth/token/exchange (app credentials)
Cliodot->>Backend: Connection metadata (+ tokens per policy)
Backend->>Cliodot: GET /oauth/connections/{connectionId}/token (as needed)
Summary of steps:
- Customer backend initiates connect via
GETorPOST /oauth/apps/:appId/connect. - For browser grants, Cliodot redirects to the IdP; the IdP callbacks to
/oauth/callback/:appId/:provider. - For immediate grants (
client_credentials, JWT bearer, token exchange), Cliodot returns anexchange_codein the response or redirect. - For device flow, poll
POST /oauth/apps/:appId/connect/polluntil complete. - Customer backend calls
POST /oauth/token/exchangewith app credentials to redeem the exchange code.
Provider configurations store oauth.grant_type. The connect response shape depends on the grant type.
| Grant type | Value | Connect behavior |
|---|---|---|
| Authorization Code | authorization_code (default) |
Browser redirect to authorization_url → IdP callback → exchange code |
| Client Credentials | client_credentials |
Immediate server-side token exchange; returns exchange_code + redirect_url |
| Device Authorization | urn:ietf:params:oauth:grant-type:device_code |
Returns device codes + poll_state; poll via POST /connect/poll |
| JWT Bearer | urn:ietf:params:oauth:grant-type:jwt-bearer |
Requires assertion on connect; immediate token exchange |
| SAML2 Bearer | urn:ietf:params:oauth:grant-type:saml2-bearer |
Requires assertion on connect; immediate token exchange |
| Token Exchange | urn:ietf:params:oauth:grant-type:token-exchange |
Requires subject_token on connect; optional actor/audience fields |
Connect response variants (POST)#
Authorization code (default):
{
"ok": true,
"protocol": "oauth2",
"grant_type": "authorization_code",
"authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=..."
}
Client credentials (immediate):
{
"ok": true,
"protocol": "oauth2",
"grant_type": "client_credentials",
"exchange_code": "ex_a1b2c3d4e5f6789012345678901234567890ab",
"connection_id": "conn_507f1f77bcf86cd799439014",
"redirect_url": "https://friendsconnect.com/auth/callback?code=ex_a1b2c3d4...&connection_id=conn_507f1f77bcf86cd799439014&state=csrf-token-from-your-app"
}
Device authorization:
{
"ok": true,
"protocol": "oauth2",
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": "GmRhmhcxhwAz30EkNva2BFGrNP0TOKsUyIT0lHDg-pQ",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"verification_uri_complete": "https://example.com/device?user_code=WDJB-MJHT",
"poll_interval": 5,
"expires_in": 600,
"poll_state": "a1b2c3d4e5f6789012345678901234567890abcdef"
}
SAML (POST binding):
{
"ok": true,
"protocol": "saml",
"authorization_url": "https://idp.example.com/sso",
"sso_binding": "post",
"saml_request": "PHNhbWxwOlJlcXVlc3Q...",
"relay_state": "relay-123"
}
GET /connect returns 302 redirects for browser flows. POST-only grant types return 400 on GET:
{
"ok": false,
"message": "This grant type requires POST /connect",
"grant_type": "urn:ietf:params:oauth:grant-type:device_code"
}
Start Connect (GET — browser redirect)
| Method | GET |
| Path | /oauth/apps/:appId/connect |
| Auth | None (rate limited) |
Description: Start an OAuth flow via browser redirect to the identity provider.
Query parameters:
| Param | Required | Description |
|---|---|---|
provider |
Yes | Provider alias (e.g. google) |
redirect_uri |
Yes | Customer callback URL (must be in app allowlist) |
scope |
No | Space-delimited OAuth scopes |
state |
No | CSRF token returned on customer callback |
external_user_id |
No | Your app's user key stored on the connection |
connection_storage |
No | persistent or ephemeral |
Sample success response (302 — authorization code):
Location: https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=...
Sample success response (302 — immediate grant):
Location: https://friendsconnect.com/auth/callback?code=ex_a1b2c3d4...&connection_id=conn_507f1f77bcf86cd799439014&state=csrf-token-from-your-app
Common errors: OAUTH_APP_NOT_FOUND, OAUTH_APP_DISABLED, OAUTH_REDIRECT_URI_NOT_ALLOWED, OAUTH_PROVIDER_NOT_CONFIGURED, OAUTH_SCOPE_NOT_ALLOWED, POST-required grant (400)
cURL:
curl -v "https://api.cliodot.com/oauth/apps/oauth_app_507f1f77bcf86cd799439011/connect?provider=google&redirect_uri=https%3A%2F%2Ffriendsconnect.com%2Fauth%2Fcallback&scope=openid%20email%20profile&state=csrf-token-from-your-app&external_user_id=user_12345"
Start Connect (POST — programmatic)
| Method | POST |
| Path | /oauth/apps/:appId/connect |
| Auth | None (rate limited) |
Description: Start an OAuth flow programmatically. Response shape depends on provider grant_type (see Grant Types and Connect Responses).
Request body:
{
"provider": "google",
"redirect_uri": "https://friendsconnect.com/auth/callback",
"scopes": ["openid", "email", "profile"],
"state": "csrf-token-from-your-app",
"external_user_id": "user_12345"
}
Additional fields by grant type:
| Grant type | Extra fields |
|---|---|
| JWT bearer | assertion |
| SAML2 bearer | assertion |
| Token exchange | subject_token, subject_token_type, audience, actor_token, actor_token_type, requested_token_type |
Sample success response (200 — authorization code):
{
"ok": true,
"protocol": "oauth2",
"grant_type": "authorization_code",
"authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&state=..."
}
Common errors: Same as GET connect
cURL:
curl -X POST "https://api.cliodot.com/oauth/apps/oauth_app_507f1f77bcf86cd799439011/connect" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"redirect_uri": "https://friendsconnect.com/auth/callback",
"scopes": ["openid", "email", "profile"],
"state": "csrf-token-from-your-app",
"external_user_id": "user_12345"
}'
Connect Poll (Device Flow)#
| Method | POST |
| Path | /oauth/apps/:appId/connect/poll |
| Auth | None (rate limited) |
Description: Poll device authorization after a device_code connect. Retry on 428 until an exchange_code is returned.
Request body:
{
"provider": "cli-tv",
"poll_state": "a1b2c3d4e5f6789012345678901234567890abcdef"
}
Sample success response (200):
{
"ok": true,
"protocol": "oauth2",
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"exchange_code": "ex_a1b2c3d4e5f6789012345678901234567890ab",
"connection_id": "conn_507f1f77bcf86cd799439014",
"redirect_url": "https://friendsconnect.com/auth/callback?code=ex_a1b2c3d4...&connection_id=conn_507f1f77bcf86cd799439014&state=csrf-token-from-your-app"
}
Pending response (428):
{
"ok": false,
"error": "authorization_pending",
"code": "OAUTH_PROVIDER_ERROR"
}
Wait poll_interval seconds (from the connect response) and retry.
Common errors: OAUTH_STATE_INVALID, OAUTH_PROVIDER_ERROR
cURL:
curl -X POST "https://api.cliodot.com/oauth/apps/oauth_app_507f1f77bcf86cd799439011/connect/poll" \
-H "Content-Type: application/json" \
-d '{
"provider": "cli-tv",
"poll_state": "a1b2c3d4e5f6789012345678901234567890abcdef"
}'
Callback#
Called by the identity provider after user authorization. Do not call these endpoints directly from your application.
OAuth2/OIDC Callback (GET)
| Method | GET |
| Path | /oauth/callback/:appId/:provider |
| Auth | None (rate limited) |
Description: Cliodot receives the OAuth callback from the IdP, exchanges the authorization code, stores the connection, and redirects to the customer's redirect_uri.
Path parameters:
| Param | Example |
|---|---|
appId |
oauth_app_507f1f77bcf86cd799439011 |
provider |
google |
Query parameters (from IdP): code, state
Sample success response (302):
Location: https://friendsconnect.com/auth/callback?code=ex_a1b2c3d4...&connection_id=conn_507f1f77bcf86cd799439014&state=csrf-token-from-your-app
Sample error response (302):
Location: https://friendsconnect.com/auth/callback?error=access_denied&error_description=User+denied+consent&state=csrf-token-from-your-app
Common errors: OAUTH_STATE_INVALID, OAUTH_PROVIDER_ERROR, OAUTH_CREDENTIALS_MISSING
SAML Callback (POST)
| Method | POST |
| Path | /oauth/callback/:appId/:provider |
| Auth | None (rate limited) |
Description: SAML Assertion Consumer Service (ACS) callback. The IdP POSTs SAMLResponse and RelayState.
Request body (form-urlencoded):
| Field | Description |
|---|---|
SAMLResponse |
Base64-encoded SAML response |
RelayState |
Relay state from connect flow |
Example:
POST /oauth/callback/oauth_app_507f1f77bcf86cd799439011/okta-saml
Content-Type: application/x-www-form-urlencoded
SAMLResponse=PHNhbWxwOlJlc3BvbnNl...&RelayState=relay-state-token
Sample success response (302): Same redirect shape as OAuth2 callback with code=ex_...&connection_id=conn_...
Common errors: OAUTH_STATE_INVALID, OAUTH_PROVIDER_ERROR, OAUTH_CREDENTIALS_MISSING
Token Exchange#
| Method | POST |
| Path | /oauth/token/exchange |
| Auth | App API key or app ID + secret |
Description: Redeem a one-time exchange code after the user completes OAuth. Returns connection metadata; tokens are included only per the app's token_exposure_policy. Exchange codes are single-use and expire within exchange_code_ttl_seconds (default 60).
Request body:
{
"code": "ex_a1b2c3d4e5f6789012345678901234567890ab",
"connection_id": "conn_507f1f77bcf86cd799439014"
}
Sample success response (200 — metadata only):
{
"ok": true,
"connection": {
"id": "conn_507f1f77bcf86cd799439014",
"oauth_app_id": "oauth_app_507f1f77bcf86cd799439011",
"provider": "google",
"status": "active",
"external_user_key": "user_12345",
"identity": {
"provider": "google",
"provider_user_id": "108123456789012345678",
"email": "user@example.com",
"email_verified": true,
"name": "Jane Doe",
"picture": "https://lh3.googleusercontent.com/a/..."
},
"scopes_granted": ["openid", "email", "profile"],
"expires_at": "2026-07-06T11:00:00.000Z"
}
}
Sample success response (200 — include access token):
When token_exposure_policy is include_access_token or include_refresh_token:
{
"ok": true,
"connection": { },
"access_token": "PROVIDER_ACCESS_TOKEN",
"token_type": "Bearer",
"expires_at": "2026-07-06T11:00:00.000Z"
}
Common errors: OAUTH_UNAUTHORIZED_APP, OAUTH_EXCHANGE_CODE_INVALID, OAUTH_CONNECTION_EXPIRED
cURL:
curl -X POST "https://api.cliodot.com/oauth/token/exchange" \
-H "Authorization: Bearer YOUR_APP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "ex_a1b2c3d4e5f6789012345678901234567890ab",
"connection_id": "conn_507f1f77bcf86cd799439014"
}'
Alternative authentication:
curl -X POST "https://api.cliodot.com/oauth/token/exchange" \
-H "X-Cliodot-App-Id: oauth_app_507f1f77bcf86cd799439011" \
-H "X-Cliodot-App-Secret: YOUR_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{
"code": "ex_a1b2c3d4e5f6789012345678901234567890ab",
"connection_id": "conn_507f1f77bcf86cd799439014"
}'
FriendsConnect — social login with Google:
| Step | Action |
|---|---|
| 1 | Create OAuth App in the Cliodot portal. Save app_id, app_secret, and app_api_key (oak_...). Add https://friendsconnect.com/auth/callback to the redirect URI allowlist. |
| 2 | Attach Google provider in the Cliodot portal with alias google and scopes openid, email, profile. Register the Cliodot callback URL at Google (see below). |
| 3 | Redirect user to connect |
| 4 | User completes Google consent; Cliodot redirects to your app |
| 5 | Exchange code server-side |
| 6 | Fetch token when calling Google APIs |
Register this callback at Google:
https://api.cliodot.com/oauth/callback/oauth_app_507f1f77bcf86cd799439011/google
Step 3 cURL:
curl -v "https://api.cliodot.com/oauth/apps/oauth_app_507f1f77bcf86cd799439011/connect?provider=google&redirect_uri=https%3A%2F%2Ffriendsconnect.com%2Fauth%2Fcallback&scope=openid%20email%20profile&state=abc&external_user_id=user_12345"
Step 5 cURL:
curl -X POST "https://api.cliodot.com/oauth/token/exchange" \
-H "Authorization: Bearer YOUR_APP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "ex_a1b2c3d4e5f6789012345678901234567890ab",
"connection_id": "conn_507f1f77bcf86cd799439014"
}'
Step 6 cURL:
curl "https://api.cliodot.com/oauth/connections/conn_507f1f77bcf86cd799439014/token" \
-H "Authorization: Bearer YOUR_APP_API_KEY"