Authentication
SM API supports three authentication paths: Google/Microsoft SSO, magic link, and API key. All portals use the same session cookie on .sprintmode.ai — users authenticated on studios.sprintmode.ai are automatically authenticated on mode.sprintmode.ai.
Auth Methods
1. CF Access (User Session)
Browser users authenticate via Cloudflare Access. After SSO login, CF adds the header cf-access-authenticated-user-email to every request. SM API trusts this header.
2. CF Access (Service Token)
Server-to-server calls (portal workers calling SM API) use CF Access service tokens. Set headers:
CF-Access-Client-Id: <client_id>
CF-Access-Client-Secret: <client_secret>These are set as environment variables SM_API_CLIENT_ID and SM_API_CLIENT_SECRET on each portal's CF Pages project.
3. API Key
Agents, MCP clients, and form proxies use an API key:
X-SM-Key: <key>
# or
Authorization: Bearer <key>Routes
GET /auth/sso/google
Initiates Google OAuth2 flow.
Auth: Public
Query params:
| Param | Type | Description |
|---|---|---|
redirect | string | URL to redirect to after login. Default: https://clients.sprintmode.ai/client/ |
product | string | Product tag (e.g. studios, mode). Stored on session. |
Behavior: Redirects to Google OAuth2 authorization URL with state containing the redirect URL and product.
GET /auth/sso/google/callback
Google OAuth2 callback. Creates or updates contact, sets JWT session cookie.
Auth: Public
Query params: code, state (set by Google)
Behavior:
- Exchanges
codefor Google user info - Looks up contact by email in CRM
- Creates contact if not found (for admin users: auto-creates with investor profile)
- Issues JWT session cookie (
sm_client) on.sprintmode.aidomain - Redirects to the URL in
state.redirect
Cookie set:
sm_client=<jwt>; Domain=.sprintmode.ai; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=604800JWT payload: { id, email, name, avatar, products[], iat, exp }
GET /auth/sso/microsoft
Initiates Microsoft OAuth2 flow.
Auth: Public
Query params: redirect, product (same as Google SSO)
GET /auth/sso/microsoft/callback
Microsoft OAuth2 callback. Same behavior as Google callback.
Auth: Public
GET /auth/sso/redirect
Cross-domain SSO redirect helper. Used when a product portal on a non-.sprintmode.ai domain (e.g. app.privacyai.com) needs to redirect to SM SSO and return with a valid session.
Auth: Public
Query params:
| Param | Type | Description |
|---|---|---|
redirect | string | Required. Final destination after auth |
product | string | Product tag |
POST /auth/sso/exchange
Exchange a one-time exchange token (set by /auth/sso/google/callback in a cross-domain flow) for a session cookie on the requesting domain.
Auth: Public
Request body:
{ "token": "<exchange_token>" }Response:
{ "ok": true }Sets sm_client cookie for the requesting origin's domain.
POST /auth/magic-link
Generate and send a magic link to a contact's email.
Auth: API key or CF Access
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | Contact email to send link to |
redirect | string | no | URL to redirect to after verification. Default: /client/ |
product | string | no | Product tag |
Response:
{ "ok": true }Sends email via Resend to the contact. Link expires in 1 hour.
Error responses:
404— Contact not found400— Email missing
GET /auth/magic-link/verify/:token
Verify a magic link token, create session cookie, redirect to destination.
Auth: Public
Path params: token — the magic link token
Behavior:
- Looks up token in
magic_sessionstable - Validates not expired
- Marks token as used
- Issues JWT session cookie
- Redirects to the stored redirect URL
GET /auth/me
Return the currently authenticated user's session data.
Auth: Session cookie or API key
Response:
{
"ok": true,
"data": {
"id": "ct_a1b2c3d4",
"email": "user@example.com",
"name": "Jane Smith",
"avatar": "https://...",
"products": ["studios"],
"iat": 1715000000,
"exp": 1715604800
}
}POST /auth/logout
Clear the session cookie.
Auth: Any
Response:
{ "ok": true }Sets sm_client cookie with Max-Age=0 to clear it.
GET /auth/logout
Same as POST /auth/logout — GET variant for redirect-based logout flows.
Session Cookie
The JWT session cookie sm_client is:
- Domain:
.sprintmode.ai(shared across all SM subdomains) - HttpOnly — not readable by JavaScript
- Secure — HTTPS only
- SameSite: None — required for cross-site iframe embeds and CF Pages functions
- Max-Age: 7 days (604800 seconds)
JWT Payload
{
id: "ct_a1b2c3d4", // CRM contact ID
email: "...",
name: "...",
avatar: "...", // Google/Microsoft profile photo URL
products: ["studios"], // products this contact has access to
iat: 1715000000, // issued at (Unix timestamp)
exp: 1715604800 // expires at (Unix timestamp)
}Verify the JWT in a CF Worker using the SESSION_SECRET environment variable:
import { verify } from 'jsonwebtoken' // or use SubtleCrypto for edge
function getSession(request, env) {
const cookie = request.headers.get('Cookie') || ''
const match = cookie.match(/sm_client=([^;]+)/)
if (!match) return null
try {
return verify(match[1], env.SESSION_SECRET)
} catch {
return null
}
}