Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.usetitan.app/llms.txt

Use this file to discover all available pages before exploring further.

Client tokens let you give a browser or mobile client direct access to a subset of the Titan API without putting your server API key in client-side code. You mint a token on your server, pass it to the browser, and the browser uses it exactly like a normal API key — but Titan enforces strict server-side rules that limit what the token can do, who it can send to, and how many requests it can make.

How client tokens work

A client token is a short-lived JWT signed with HMAC-SHA256. It has the prefix titan_ct_ and a default lifetime of 15 minutes (900 seconds). The token itself carries zero authority — all permissions are defined by rules you set on the server and enforced by Titan on every request. The flow has two sides:
  • Server side: you configure rules for a session and mint a token against it.
  • Browser side: the browser uses the token in the Authorization header. Titan validates the token, loads the rules from its store, and enforces them on every call.
Client tokens can only access a single named session. A token minted for session default cannot reach session support.

Step 1: Configure rules for your session

Before minting any tokens, set the rules that govern what client tokens are allowed to do on a session. Rules are stored server-side and take effect immediately — you can update them at any time without reissuing tokens.
PUT /api/sessions/{session}/client-rules
Authorization: Bearer titan_...
Content-Type: application/json

{
  "recipientMode": "conversation",
  "allowedActions": "send_message,send_reaction,send_typing",
  "rateLimit": 5,
  "maxDaily": 100,
  "allowedOrigins": "https://myapp.com,https://staging.myapp.com",
  "enabled": true
}

Rules fields

recipientMode
string
required
Controls who client tokens can send messages to. See Recipient modes below.
allowedActions
string
Comma-separated list of actions the token may perform. If omitted, no actions are allowed. See Allowed actions below.
rateLimit
integer
Maximum requests per minute per ephemeral ID (sliding window). Set to 0 for no limit.
maxDaily
integer
Maximum send actions per day per ephemeral ID. Only applies to send_message and send_reaction. Set to 0 for no limit.
allowedOrigins
string
Comma-separated list of allowed CORS origins. Leave empty to skip the origin check.
enabled
boolean
required
Whether client tokens are accepted for this session. Set to false to immediately deny all existing tokens.
You can also read or remove rules at any time:
# Read current rules
GET /api/sessions/{session}/client-rules

# Remove all rules (immediately denies all client tokens for this session)
DELETE /api/sessions/{session}/client-rules

Step 2: Mint a token on your server

Once rules are in place, mint a token from your backend for each browser session or tab. Pass an ephemeralId — any string that uniquely identifies this client, such as a browser fingerprint, user ID plus tab ID, or UUID. Titan uses it for rate limiting and correlation.
POST /api/client-tokens
Authorization: Bearer titan_...
Content-Type: application/json

{
  "session": "default",
  "ephemeralId": "user-123-browser-1",
  "ttlSeconds": 900
}

Request fields

session
string
required
The name of the WhatsApp session this token is scoped to.
ephemeralId
string
required
Your identifier for this client — used for per-minute and daily rate limiting.
ttlSeconds
integer
Token lifetime in seconds. Defaults to 900 (15 minutes). Maximum is configurable server-side.
Response:
{
  "data": {
    "token": "titan_ct_eyJhbGciOiJIUzI1NiIs...",
    "expiresAt": "2026-03-22T15:15:00Z"
  }
}
Send the token value to the browser. The expiresAt timestamp tells you when to request a new token.

Step 3: Use the token in the browser

The browser uses the client token in the Authorization header exactly like a server API key:
POST /api/messages/send
Authorization: Bearer titan_ct_eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "session": "default",
  "chatId": "[email protected]",
  "type": "text",
  "text": "Hello!"
}

SDK examples

All four SDKs accept either a server API key or a client token — you initialize the client the same way.
import { TitanClient } from '@titan-api/sdk';

// Server-side: configure rules and mint a token
const server = new TitanClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'titan_...',
});

// Set rules once per session
await server.clientTokens.setRules('default', {
  recipientMode: 'conversation',
  allowedActions: 'send_message,send_reaction,send_typing',
  rateLimit: 5,
  maxDaily: 100,
  enabled: true,
});

// Mint a token per browser session
const { token, expiresAt } = await server.clientTokens.mint({
  session: 'default',
  ephemeralId: 'user-123-tab-1',
  ttl: 900,
});

// Browser-side: use the client token
const browser = new TitanClient({
  baseUrl: 'https://api.example.com',
  clientToken: token,
});

await browser.messages.send('default', {
  chatId: '[email protected]',
  type: 'text',
  text: 'Hello!',
});

Recipient modes

The recipientMode rule controls who client tokens can send messages to. It applies only to send_message and send_reaction.
ModeBehaviorTypical use case
noneSending is disabled. Only read actions work.Read-only dashboards
conversationCan only message JIDs that have already messaged the session first.Support widgets, reply-only bots
anyCan message any JID.Internal tools with trusted browser clients
verifiedReserved for future use.

How conversation mode works

When a message arrives on the session, Titan records the sender’s JID. When a client token tries to send a message, Titan checks whether the target JID is in that set. If not, the request is denied with 403 Forbidden. This prevents client tokens from initiating new conversations — they can only reply to people who reached out first.

Allowed actions

The allowedActions rule is a comma-separated list that determines which routes a client token can access. All other routes are always denied, regardless of rules.
ActionRoutes
send_messagePOST /:session/messages/send — send text, image, video, document, audio, sticker, poll, location, contact
send_reactionPOST /:session/messages/react — react to a message with an emoji
send_typingPOST /:session/messages/typing — send a typing indicator
send_seenPOST /:session/messages/seen — mark messages as read
read_presenceGET /:session/presence, GET /:session/presence/:chatId — query online status
subscribe_presencePOST /:session/presence/:chatId/subscribe — subscribe to presence updates
read_contactGET /:session/contacts and related endpoints — query contact info and profile pictures
Routes for sessions, webhooks, groups, channels, labels, pairing, media, and admin are always denied to client tokens, regardless of allowedActions.

Rate limiting

Two independent limits are applied per ephemeralId: Per-minute sliding window — when rateLimit is set (for example, 5), each ephemeral ID can make at most 5 requests per rolling 60-second window. Applies to all actions. Daily cap — when maxDaily is set (for example, 100), each ephemeral ID can perform at most 100 send actions per 24-hour period. Only applies to send_message and send_reaction. Both limits return 429 Too Many Requests when exceeded.

Error responses

StatusWhen it occurs
400 Bad RequestInvalid request body, missing required fields, TTL out of range, or invalid recipientMode
401 UnauthorizedToken is invalid, expired, or has a bad signature; or no rules are configured for the session
403 ForbiddenRoute is not accessible to client tokens; session mismatch; action not in allowedActions; recipient not in conversation set; or origin not in allowedOrigins
429 Too Many RequestsPer-minute rate limit or daily cap exceeded