Graine AI

Server tokens

The strongest way to protect an agent in your app or on your website — your server mints a short-lived token for each signed-in user, your app hands it to the SDK, and the publishable key alone opens nothing. Report-only first, then enforce.

A publishable key identifies an agent. It sits inside your app and in your web page, so anyone can read it. Server tokens close that gap: your server — which knows who signed in — mints a short-lived token for one user of one agent with your secret API key, and the SDK sends it with everything it asks of Graine. Once the agent requires them, a request without a valid token for that agent and that user is refused.

Requires SDK 0.14.4 (iOS, Android, Kotlin Multiplatform, Flutter) or React Native 0.47.3, and website loader 1.5.0.

Server tokens are the strongest of the three ways to prove who your users are, and the one we recommend for sensitive data such as health or money. To compare them with your sign-in's ID token and a signed user ID, see Which way should you prove users?

Publishable key onlyWith server tokens
Who can open a chat or a callAnyone who has your keyOnly a user your server minted a token for
Who the user isWhat the app saysWhat your server said — the token's user is a verified user
How long access lastsAs long as the keyAt most an hour per token; the SDK refreshes it in the background
What the token may doEverything the agent offersOnly the scopes you minted it with

1. Turn on Report only

In the dashboard, open the agent → Embed & Widgets → Install → Server tokens, and choose Report only. Nothing is refused yet; every request is counted by what its token said — valid, missing, expired, for another agent — so you can see when every build of your app sends one. Each check is also a row in the agent's Security log; while you are report-only, a request without a valid token says Would be refused.

Server tokens set to Report only in Embed & Widgets → Install

2. Mint tokens on your server

From your own backend, with your secret API key (Settings → API keys). Mint when your user signs in and whenever the SDK asks for a fresh one; never put the API key in an app or a page.

curl https://api.graine.ai/v2/client-tokens \
  -H "Authorization: Bearer $GRAINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_0123",
    "user_id": "cust-42",
    "scopes": ["chat", "call", "memory", "booking"],
    "ttl_seconds": 900
  }'
{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9.eyJpc3MiOiJncmFpbmUiLCJhdWQiOiJncmFpbmUtZW1iZWQiLCJzdWIiOiJjdXN0LTQyIn0.c2lnbmF0dXJl",
  "expires_at": "2026-10-27T10:15:00Z",
  "expires_in": 900,
  "agent_id": "agt_0123",
  "scopes": ["chat", "call", "memory", "booking"]
}
  • user_id is your user's identifier in the field the agent's Who is the user? setting names — exactly what your app passes to identify().
  • ttl_seconds is 60 to 3600 (default 3600). A token carries no personal data beyond the user id.
  • The key needs the calls:write scope. An agent of another workspace answers 404.

3. Give the SDK a token provider

The provider is called when the SDK starts, again shortly before each token expires (in the background, at a slightly different moment on each device), and when Graine refuses a token. Return null when no token can be had right now — the SDK tries again later. Fetch it from your own server, which mints only for the user its sign-in proved.

Graine.configure(
    GraineConfig(
        baseUrl = "https://www.graine.ai",
        publishableKey = "pk_live_…",
        tokenProvider = { myApi.graineToken() },   // suspend () -> String?
    )
)

4. Enforce

When Report only shows valid tokens and nothing missing for a few days, choose Enforce. The dashboard states the effect and asks you to confirm.

Confirming Enforce for server tokens

A token that expires never ends a chat or call in progress — only new conversations need a fresh one. A booked call your user answers within 15 minutes of their token expiring still connects.

Scopes

A token covers only what you minted it for. A missing scope refuses that action only: a token without memory still chats and calls, but cannot read what the agent remembers.

ScopeLets the app
chatopen a chat, start a new conversation
callstart or answer a voice call, register the device for calls
memoryread or erase what the agent remembers about the user
bookingbook, list and cancel calls
workflowsstart a workflow

Past sessions need chat or call. Loading the agent's settings needs any valid token.

When a token is refused

CodeWhat it meansWhat the SDK does
token_requiredThe agent requires server tokens and none was sentAsks your provider, tries once more; otherwise reports it
token_invalidThe token is not one Graine issued for this agentAsks your provider, tries once more
token_expiredThe token expiredAsks your provider, tries once more
token_scopeThe token is for another agent or user, or lacks the scope this needsReports it — a new token will not help

The error arrives as each SDK's error type, with the code and a reason (agent, user or scope for token_scope).

On this page