Skip to main content

Which Method Should I Use?

ScenarioMethodWhy
React/Next.js appJWT SessionsUser is logged in, token comes from auth flow
Mobile appJWT SessionsSame as web—user session based
Backend serviceM2M OAuth 2.0No user session, server-to-server calls
CLI toolM2M OAuth 2.0Automated scripts, no browser
Cron jobM2M OAuth 2.0Background tasks, no user context

JWT Session Tokens

For frontend apps where users authenticate via Stytch B2B (magic link, SSO, or password).

Usage

curl -X POST https://api.cuadra.ai/v1/chats \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "modelId": "model_123",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

With React UI Kit

import { CuadraChat } from '@cuadra-ai/uikit';

function App() {
  const sessionToken = useSessionToken();

  return (
    <CuadraChat
      connection={{ baseUrl: "https://api.cuadra.ai", sessionToken }}
      chat={{ modelId: "model_123" }}
    />
  );
}

M2M OAuth 2.0

For backend services using OAuth 2.0 Client Credentials flow.

Step 1: Get Credentials

Create M2M client credentials in Dashboard → SettingsAPI Access. Assign scopes based on what the service needs.

Step 2: Exchange for Token

Exchange your client credentials for an access token: Token Endpoint:
https://auth.cuadra.ai/v1/oauth2/token
curl -X POST "https://auth.cuadra.ai/v1/oauth2/token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "m2m-client-xxx",
    "client_secret": "secret-xxx",
    "grant_type": "client_credentials"
  }'
Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 3600
}

Step 3: Use the Token

curl -X POST https://api.cuadra.ai/v1/chats \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"modelId": "model_123", "messages": [{"role": "user", "content": "Hello"}]}'

Token Caching

M2M tokens expire after 1 hour. Cache and refresh before expiry:
from datetime import datetime, timedelta
import httpx

class TokenManager:
    TOKEN_URL = "https://auth.cuadra.ai/v1/oauth2/token"
    
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret
        self._token = None
        self._expires_at = None

    async def get_token(self):
        if self._token and self._expires_at > datetime.utcnow():
            return self._token
        
        async with httpx.AsyncClient() as client:
            response = await client.post(
                self.TOKEN_URL,
                json={
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                    "grant_type": "client_credentials"
                }
            )
            data = response.json()
            self._token = data["access_token"]
            # Refresh 5 minutes before expiry
            self._expires_at = datetime.utcnow() + timedelta(seconds=data["expires_in"] - 300)
            return self._token

Scopes

ScopePermission
chat:invokeCreate chat completions (billable)
chat:readRead chat history
chat:writeCreate, update, delete chats
models:readList and view models
models:writeCreate, update, delete models
datasets:readView datasets
datasets:writeCreate, update, delete datasets
files:readView and download files
files:writeUpload, update, delete files
particles:readView particles
particles:writeCreate, update, delete particles
system-prompts:readView system prompts
system-prompts:writeCreate, update, delete system prompts
connections:readView external connections (Google Drive, Notion)
connections:writeCreate, update, delete connections
usage:readView usage and credit balance
org:adminOrganization admin access
Request minimum necessary scopes for each service.

Security Best Practices

  1. Never expose tokens in client-side code — Use a backend proxy for production
  2. Store secrets in environment variables — Not in code or version control
  3. Rotate M2M credentials every 90 days — Dashboard → Settings → API Access
  4. Use HTTPS only — The API rejects HTTP requests
  5. Scope credentials minimally — Only request permissions you need

Troubleshooting

ErrorCauseSolution
401 UnauthorizedInvalid or expired tokenRefresh M2M token; verify JWT session
403 ForbiddenMissing scopeRequest credentials with required scopes
Token ExpiredM2M token past 1 hourRequest new token

FAQ

Can I use API keys instead of OAuth?

No. Cuadra AI uses OAuth 2.0 for server-side authentication. M2M credentials function similarly to API keys but require a token exchange step for security.

How do I revoke a compromised token?

For M2M: Rotate the client secret in Dashboard → SettingsAPI Access. For JWT: Revoke the user session in your Stytch dashboard.

What’s the token lifetime?

M2M access tokens expire after 1 hour. JWT session tokens follow your Stytch session configuration (typically 24 hours to 30 days).

Do I need separate credentials for staging vs production?

Recommended. Create separate M2M clients per environment to isolate access and simplify rotation.