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.
Getting a client_id
OAuth client registration is administered by Driftstack. 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 session metadata + history. |
write:sessions | Start + stop sessions on the account. |
read:profiles | Read browser-profile metadata. |
write:profiles | Create + update browser profiles. |
admin:profiles | Delete + manage shared profiles. |
read:webhooks | List webhook endpoints + delivery history. |
write:webhooks | Create webhook endpoints + send test events. |
admin:webhooks | Rotate webhook secrets + delete endpoints. |
read:api-keys | List the account’s API keys (metadata only). |
admin:api-keys | Mint + revoke API keys. |
read:billing | Read invoices, usage, and crypto-order history. |
admin:billing | Initiate checkout sessions + manage subscriptions. |
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.
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.dev/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.
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. Browser apps cannot keep it secret; if your app is browser-based, talk to support about a public-client variant (no secret, PKCE-only).
- 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
We don't yet operate a separate sandbox environment. Test
against your own dev account on the production API; use the
read:*-only scopes if you don't want your dev
account's session usage to count against billing.
Support
Questions, bug reports, or scope requests: [email protected]. We aim to respond within one business day.