OAuth 2.0 integration
Driftstack uses OAuth 2.0 with PKCE-S256 for third-party app access. This page documents the authorization code flow your app uses to act on a Driftstack customer's behalf.
Customer authorization is available on every plan, the free trial included.
Getting a client_id
OAuth client registration is admin-gated. Email [email protected] with:
- Your app name and a one-paragraph description
- Your redirect URI(s) — HTTPS only in production,
http://localhost:<port>is accepted for development - Which scopes you need (see the table below); we'll push back if the request is broader than your use case justifies
We turn around new client registrations within one business
day. You get back a client_id (prefixed
oac_) and a client_secret (prefixed
oas_). The secret is shown to you ONCE — we only
store the hash on our side, so we cannot recover it for you.
Treat it like a Stripe secret key.
Available scopes
| Scope | What it grants |
|---|---|
read:sessions | Read direct sessions and AI (agent) sessions: list, get, history. |
write:sessions | Create and close direct sessions on /v1/sessions. It does not start or drive AI (agent) sessions. |
read:profiles | Read browser-profile metadata. |
write:profiles | Create + update browser profiles. |
admin:profiles | Reserved — no route enforces it; profile writes use write:profiles. |
read:webhooks | List webhook endpoints + delivery history. |
write:webhooks | Reserved — endpoint management requires account_owner. |
admin:webhooks | Reserved — secret rotation and deletion require account_owner. |
read:api-keys | List the account’s API keys (metadata only). |
admin:api-keys | Reserved — minting and revoking require account_owner. |
read:billing | Read invoices, usage, and crypto-order history. |
admin:billing | Reserved — checkout, the billing portal and orders are done in the dashboard, and OAuth tokens are refused them (dashboard-only). |
read:audit | Read the account audit log. |
Scopes follow the verb:resource pattern. Always request the narrowest scope set your app actually needs — broad scopes get pushed back during the human review step.
AI sessions are not available to OAuth apps
OAuth apps can't run AI sessions today; use an API key for
that. Creating an AI (agent) session, sending it a task,
stopping it and closing it all need the broad write
scope. OAuth grants only the granular scopes in the table above, so
an OAuth token gets 403 on
POST /v1/agent-sessions and on every write that starts
or drives an AI session (message, steps, stop, close, reopen).
One documented agent-session write takes a granular scope: the profile notes.
PUT /v1/agent-sessions/{id}/profile-notes needs
write:profiles. It edits the notes kept with the
profile and does not start or drive anything.
With write:sessions an OAuth app can create and close
direct sessions on /v1/sessions. On hosted Driftstack a
direct session cannot load or drive a page, so it is not a way to
browse. With read:sessions an OAuth app can read the
AI sessions the customer ran with their own key or in the dashboard.
Authorization flow
1. Mint a PKCE verifier
Generate a high-entropy random string (RFC 7636 §4.1, 43–128
characters from [A-Za-z0-9-._~]). Compute the
code_challenge as the URL-safe base64 of
SHA-256(verifier). Keep the verifier in your app
until step 3 — never send it to anyone except Driftstack at
the token endpoint.
2. Redirect the customer to Driftstack
GET https://app.driftstack.io/oauth/authorize/
?client_id=oac_…
&redirect_uri=https://yourapp.com/oauth/callback
&state=<csrf-token>
&code_challenge=<sha256(verifier) base64url>
&code_challenge_method=S256
&scope=read:sessions write:sessions
The customer signs into Driftstack if they aren't already, then
sees the app label, requested scopes, and callback host before
choosing Approve or Cancel. Approval returns them to your
redirect_uri with ?code=…&state=…;
cancellation returns ?error=access_denied&state=….
Verify state matches what you sent (CSRF guard) in
either case.
The Dashboard validates and stages this request through
GET /v1/oauth/authorize, then performs approved consent
through POST /v1/oauth/authorize/complete. Those are
provider-internal steps: your integration never receives or handles
the intermediate authorization_id.
3. Exchange the code for an access token
POST /v1/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "...",
"code_verifier": "<your pkce verifier>",
"client_id": "oac_…",
"client_secret": "oas_…",
"redirect_uri": "https://yourapp.com/oauth/callback"
} The response carries the token:
{
"access_token": "oat_…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": ["read:sessions", "write:sessions"]
} Tokens live for one hour. There are no refresh tokens — when the token expires, run the full authorize → exchange dance again. This is intentional: it makes consent re-confirmation a regular event rather than a forever-grant.
4. Use the token
Bearer-token authentication against the Driftstack API:
Authorization: Bearer oat_…
The token can only call API surfaces whose required scope is a
subset of the token's scope. A token with
read:sessions can call GET /v1/sessions
but not POST /v1/sessions. A token with
write:sessions can call POST /v1/sessions
but not POST /v1/agent-sessions: no OAuth scope reaches
the AI session writes (see
AI sessions are not available to OAuth apps).
Introspection (RFC 7662)
Check whether a token is still active + what scopes it carries:
POST /v1/oauth/introspect
Content-Type: application/json
{
"token": "oat_…",
"client_id": "oac_…",
"client_secret": "oas_…"
}
→ { "active": true, "client_id": "oac_…", "account_id": "acc_…",
"scope": ["read:sessions"], "exp": 1736600000 }
After your client credentials are authenticated, a revoked,
expired, unknown, or foreign-client token returns
{"active": false}. Invalid or revoked client
credentials return 401 before token lookup. Use introspection
sparingly and only from server-side code — the request contains
your client_secret.
Revocation (RFC 7009)
Invalidate a token before its 1-hour expiry — useful when the customer signs out of your app:
POST /v1/oauth/revoke
Content-Type: application/json
{
"token": "oat_…",
"client_id": "oac_…",
"client_secret": "oas_…",
"token_type_hint": "access_token"
}
→ 200 OK
After client authentication succeeds, the endpoint returns 200
for an owned, unknown, or foreign-client token, but only a token
issued to your client_id is revoked. This preserves
RFC 7009 anti-enumeration behavior without allowing cross-client
revocation. Invalid or revoked client credentials return 401.
token_type_hint is optional and informational.
Security expectations
- Always validate
stateon the redirect back. Without it, your callback is wide open to CSRF. - Generate a fresh PKCE verifier per authorization flow. Re-using a verifier defeats its purpose.
- HTTPS-only redirect_uri in production. Driftstack rejects HTTP redirect URIs that aren't localhost.
- Store client_secret server-side. Every supported client is confidential and the token, introspection, and revocation endpoints require the secret. A browser UI must exchange through its own trusted backend; there is no public-client flow.
- Tokens are bearer tokens. Anyone who reads the token can act as the customer. Treat them like passwords.
Rate limits
Token-issued requests count against the customer's account rate limits, not your app's. If you're going to mint many tokens for many customers, be aware that high per-customer QPS will hit the tier limits.
Sandbox
Driftstack has no separate sandbox environment. Test with a dev account (the free trial works) on the production API and use least-privilege scopes. Read-only scopes cannot create sessions, but the account still uses production data, limits, and rate-limit budgets. No OAuth scope can run an AI session, in testing or otherwise.
Support
Questions, bug reports, or scope requests: [email protected]. Reply times follow your plan's support on the pricing page: a 48-hour reply target on Starter and Pro.