Skip to main content
DRIFTSTACK

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 requires an API-enabled paid tier, including any Manual tier. Free remains available for interactive desktop use, but cannot approve an OAuth authorization. Existing access tokens are rejected while an account is Free and resume after upgrade only if they have not expired or been revoked.

Getting a client_id

OAuth client registration is admin-gated. Email [email protected] with:

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

ScopeWhat it grants
read:sessionsRead session metadata + history.
write:sessionsStart + stop sessions on the account.
read:profilesRead browser-profile metadata.
write:profilesCreate + update browser profiles.
admin:profilesReserved — no route enforces it; profile writes use write:profiles.
read:webhooksList webhook endpoints + delivery history.
write:webhooksReserved — endpoint management requires account_owner.
admin:webhooksReserved — secret rotation and deletion require account_owner.
read:api-keysList the account’s API keys (metadata only).
admin:api-keysReserved — minting and revoking require account_owner.
read:billingRead invoices, usage, and crypto-order history.
admin:billingInitiate checkout sessions + manage subscriptions.
read:auditRead 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.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.

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

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 paid dev account 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. The Free desktop ds_test_… credential is not an OAuth or API sandbox key.

Support

Questions, bug reports, or scope requests: [email protected]. We aim to respond within one business day.