Authentication
Linkstacked supports three authentication paths, each suited to a different integration shape:
| Path | Use it for |
|---|---|
| OAuth bearer | Server-side apps and CLIs acting on behalf of a user. |
| Session cookie | First-party browser sessions (the dashboard itself). |
| Service account | Enterprise back-office automation (planned, not GA yet). |
OAuth bearer flow
The recommended path for any non-browser integration. The flow follows RFC 6749 with PKCE — most OAuth client libraries handle it without modification.
- Direct the user to
https://api.linkstacked.com/oauth/authorizewith yourclient_id,redirect_uri, and a generated PKCE pair. - After they consent, Linkstacked redirects to your
redirect_uriwith an?code=parameter. - Exchange the code for a token by calling the GraphQL
oauthLoginmutation (see below). Tokens come back as a short-lived access token plus a long-lived refresh token.
mutation OAuthLogin($input: OAuthInput!) {
oauthLogin(input: $input) {
accessToken
refreshToken
expiresAt
user { _id email subscriptionTier }
}
}{
"input": {
"provider": "LINKSTACKED",
"code": "ac_...",
"codeVerifier": "...",
"redirectUri": "https://your-app.example/cb"
}
}Refreshing tokens
Access tokens expire in 60 minutes. Use the refreshToken mutation to
mint a new pair without sending the user through the consent screen
again:
mutation Refresh($refreshToken: String!) {
refreshToken(token: $refreshToken) {
accessToken
refreshToken
expiresAt
}
}Refresh tokens rotate
Each call to refreshToken returns a new refresh token and invalidates
the old one. Persist the latest token your client receives, and treat
the rotation as the canonical "is the user still logged in?" signal.
Two-factor authentication
If a user enables 2FA, the regular login flow returns a partial response
with requiresTwoFactor: true and a temporary twoFactorToken. Complete
authentication with verifyTwoFactorLogin:
mutation Verify($input: VerifyTwoFactorInput!) {
verifyTwoFactorLogin(input: $input) {
accessToken
refreshToken
user { _id }
}
}Session management
The same auth context exposes a session-management surface so users can audit and revoke active sessions from your app:
activeSessions— list every session.revokeSession(sessionId)— kill a single session.revokeAllSessions— kill everything except the caller's current session (useful for "log out everywhere else").