Client tokens and domain restrictions
Use a client token to authenticate a chat widget in client-side code. The token is public, scopes requests to its capabilities, and restricts requests by origin.
How client tokens work
The generated widget embed code uses the client token that you select in Embed in the workbench or Ship in the classic dashboard. Client tokens have these properties:
- Public: Include a client token in client-side code. Keep API keys in server-side code.
- Capability-scoped: The token authorizes calls only to the Flows and Agents that you attach to it.
- Origin-restricted: The token accepts requests when the request origin matches its
allowedOriginslist. Test tokens also allow local origins automatically.
Choose a token environment
A client token is a test token or a live token. Every Surface accepts both:
- Test token (
ct_test_prefix): stays copyable after creation and automatically allows local origins. Use it while you build. - Live token (
ct_live_prefix): shown once when the dashboard creates it. Use it for real traffic.
Test tokens automatically allow http://localhost, http://127.0.0.1, http://0.0.0.0, and http://[::1] with any port. Add staging and tunnel origins to the allowed origins explicitly. Both environments use the same session and rate limits. For more information, see Set session and rate limits.
Create a client token
A token must name at least one Flow or Agent. In the dashboard, a token takes its capabilities from the chat Surface, so link at least one Flow or Agent to the Surface before you create the token. The API, SDK, and CLI take the Agent or Flow IDs directly and do not need a Surface. For those paths, see Create a token from code.
In the workbench, a new token takes its release target and limits from Token defaults in Access & keys. Set those defaults first, or change a single token later in its Token settings.
To create a client token in the dashboard, follow these steps:
- In the Runtype dashboard, open your Product and select the chat Surface.
- In the workbench, open Access & keys and click New token. In the classic dashboard, open Auth and click Create Token.
- Enter a name for the token.
- In the workbench, select Live or Test in Mode. The classic dashboard creates a live token.
- In Allowed origins (Allowed Origins in the classic dashboard), enter a comma-separated list of origins.
- Click Create token in the workbench or Create Token in the classic dashboard. The token appears in the token list.
Save a live token’s value when it is displayed. The workbench cannot reveal it later. Test tokens remain copyable from their row. Select the token in Embed or Ship to generate widget code; replace YOUR_CLIENT_TOKEN in a live snippet with the saved value.
To confirm that the widget uses the token, load your page and send a message. The token’s lastUsedAt and conversationCount values update, and you can read them with Runtype.clientTokens.get(id) or the Client Tokens API.
Create a token from code
To create a token for an Agent from a script or CI job, pass the Agent’s ID in agentIds. Pass Flow IDs in flowIds together with a defaultFlowId. You do not need a Product or a Surface.
Runtype.clientTokens.ensure from @runtypelabs/sdk manages a token by name. It creates the token when no token has that name, updates the token when its configuration differs, and does nothing when it matches. The result includes the token value, so a repeated run can still configure the widget:
ensure behaves as follows:
- Identity:
ensurematches on the exact token name within the account that the API key belongs to. It throws when more than one token has that name. - Always converged:
agentIds,flowIds,allowedOrigins,targetAlias, andtargetVersionId. An omitted target field removes that target from the token. The order of entries in a list does not matter. - Converged when set:
defaultFlowId, the rate limits,maxMessagesPerSession,sessionIdleTimeoutMinutes, andconfig. An omitted field keeps its current value. - Fixed at creation:
environment(defaultlive) andproductSurfaceId.ensurethrows when the existing token has a different value, because the API cannot change these fields. The API also cannot remove every Flow from a token, soensurethrows when the token has Flows and the definition has none. Delete the token and runensureagain. - Never:
ensuredoes not delete or regenerate a token.
Pass { dryRun: true } to return a plan without writing, or { expectNoChanges: true } to throw ClientTokenDriftError when the token differs from the definition.
ensure returns token: null for a live token that was created in the workbench, because the workbench does not store a live token’s value. A token that the API, SDK, or CLI creates stores its value, and Runtype.clientTokens.getToken(id) returns it later. For a workbench-created live token whose value you lost, regenerate the token.
Runtype.clientTokens also provides list, get, create, update, delete, regenerate, and getToken. The same methods are available on createClient({ apiKey }).clientTokens.
To create a token from the CLI, use runtype client-tokens create. The CLI creates a test token unless you pass --environment live, and it allows every origin unless you pass --origins:
The CLI also has list, get <id>, delete <id>, and regenerate <id> subcommands. It has no update command. To change an existing token, use the workbench, the SDK, or the Client Tokens API. The Runtype MCP server covers the same actions with the list_client_tokens, get_client_token, create_client_token, update_client_token, delete_client_token, and regenerate_client_token tools.
For a complete example that defines an Agent, creates a token, and embeds the widget, see Build the widget from code.
Set allowed origins
Set the token’s allowedOrigins value to the origins that can use the widget. Enter the scheme and hostname, and include the port when the site uses a non-default port. To change the origins on an existing token in the workbench, open Access & keys, click the token’s options menu, and select Edit token settings.
Use these origin forms:
https://example.com: an exact origin.https://www.example.com: a hostname that differs fromexample.com.http://localhost:3000: a local origin with port3000.
Do not enter a bare hostname such as example.com. The scheme must match the request origin.
In the workbench, a chat Surface can also have its own Allowed origins list under Origins. Requests must match both the token and Surface lists. A Surface list also applies to test-token localhost requests. Leave the Surface list unset if token-level restrictions are sufficient.
Check exact origin matching
For exact origin entries, Runtype compares the scheme, hostname, and port. Check these values when a request fails:
- Scheme:
https://example.comdoes not matchhttp://example.com. - Hostname:
example.comdoes not matchwww.example.com. - Port:
http://localhost:3000does not matchhttp://localhost:5173.
Allow subdomains
To match subdomains under one hostname, enter https://*.example.com. Runtype matches https://app.example.com and https://staging.example.com, but it does not match https://example.com. Add https://example.com separately when you need the apex domain.
A wildcard entry ignores the port and matches subdomains at any depth, such as https://a.b.example.com. Use exact origins when you need port-specific matching.
Allow every origin or native apps
To allow every origin, enter *. Avoid this setting on a Surface that serves live traffic because it removes origin filtering.
For a native app that sends no Origin header, enter native as an allowed origin. This entry admits originless requests and does not match a web origin. Use a separate token for native apps because this setting removes origin protection for that token.
When you create or update a token with an allowedOrigins list that contains native, the response includes a warnings array with a native_origin entry.
Set session and rate limits
Each token carries four limits. The following table lists each limit’s field, workbench label, CLI flag, default, and allowed range:
A widget session ends when it reaches its message limit or stays idle longer than the idle timeout. Each message resets the idle timer. After either event, the visitor’s next message fails and the widget must start a new session.
Client tokens have no daily cap. A test token and a live token get the same limits and the same defaults.
To change the limits in the workbench, set Limits in Token defaults for new tokens, or open a token’s Token settings to change one token. To copy the default limits onto every active token, click Apply limits to active tokens. From code, pass the fields to Runtype.clientTokens.update:
How the rate limits count requests
The per-minute and per-hour limits apply when you call a Surface’s chat endpoint, POST /v1/products/PRODUCT_ID/surfaces/SURFACE_ID/chat, or its AG-UI endpoint, POST /v1/products/PRODUCT_ID/surfaces/SURFACE_ID/ag-ui, with the token. PRODUCT_ID and SURFACE_ID are the IDs of your Product and chat Surface.
Messages from the embedded chat widget do not count against these two limits. The widget’s messages count against Messages per session instead.
Runtype counts these limits as follows:
- Fixed windows: A count resets at the start of each clock minute and each clock hour. It does not reset 60 seconds after the first request.
- Per token: Each token has its own counts. Two tokens on the same Surface do not share a budget.
- Rejected requests: A request that exceeds the per-minute limit still counts toward the hour. A client that retries in a tight loop uses up its hourly budget.
- Approximate: Concurrent requests can pass slightly over a limit.
When a request exceeds a limit, the chat endpoint returns 429 with this body:
retryAfter is the number of seconds until the exceeded window resets. The response has no Retry-After header, so read the value from the body. The AG-UI endpoint returns the same error and retryAfter fields without code.
Target a version or release alias
By default a client token names no version selector, so its runs resolve like any other run of that Agent that names no version. For more information, see What runs when you do not name a version.
A token can instead carry one of these targets, never both:
targetAlias: a release alias that the token follows. Runtype resolves it again on every request, so deploying a new version at that alias changes what the token runs without changing the token.targetVersionId: one immutable agent version. Runtype never resolves it again, so the token keeps running that exact configuration until you change it.
To set a target in the workbench, open Access & keys, click the token’s options menu, and select Edit token settings. In Release, select Follows alias and enter a Release alias, or select Pin a version and enter an Agent version ID. Token defaults sets the same choice for new tokens. From code, set targetAlias or targetVersionId through the Client Tokens API, the SDK, the create_client_token and update_client_token MCP tools, or the CLI’s --target-alias <name> and --target-version-id <id> options.
To make a token pick up a new Agent definition that you manage with Runtype.agents.ensure, deploy it to live, for example with Runtype.agents.ensure(def, { deploy: { alias: 'live' } }). This works for a token with no target and for a token with targetAlias: 'live'.
Know these rules before you set a target:
targetVersionIdrequires the token to carry exactly one Agent ID, and that version must belong to that Agent.targetAliasrequires an organization-scoped token. A personal-account token cannot follow an alias.- An alias that names nothing returns a
404that identifies the alias. It does not fall back to the live configuration. - Targeting is not available for external or Claude Managed agents, which return
422. - Realtime voice runs the targeted version. It resolves the alias once when the call connects, so a call keeps its version if the alias moves during the call. The exception is a GPT-Live call whose client runs the turns itself: those turns go through chat and follow the alias each turn. Set
targetVersionIdto keep such a call on one version. A targeted token cannot also open an editor draft, and returns422withselector_conflict. - A chat request that supplies its own
agent.aliasoragent.versionIdmust exactly restate the token’s target, or the request is rejected with400. A token with no target accepts no selector.
pinToVersion is a legacy widget setting and does not select an agent version. To pin a version, set targetVersionId.
Update permissions on existing sessions
Runtype rechecks the token’s current allowed origins on session requests. Chat messages and tool-result resumes also recheck that the session’s Flow or Agent is still attached to the token.
If you remove a Flow, Agent, or allowed origin from a token, Runtype rejects the next message in any existing conversation that depends on it. Starting a new session does not restore access. To restore access, correct the token configuration and retry.
Keep the native entry when an integration intentionally sends no Origin header. Test tokens keep their automatic local-origin allowance.
Troubleshoot rejected requests
When the widget loads but does not respond, check the browser console and the response body in the Network tab. The following table lists the errors that a client token request can return:
If none of these errors appear, confirm that the token still lists at least one Flow or Agent and, if the token belongs to a Surface, that the Surface has at least one enabled Flow or Agent.
Regenerate a client token
If someone exposes a client token, or you lose a live token’s value, regenerate it. Regeneration keeps the token’s capabilities, allowed origins, and environment while issuing a new token value. The previous value stops working immediately.
To regenerate a token in the workbench, follow these steps:
- Open Access & keys and click Regenerate on the token row.
- Click Regenerate token to confirm.
- Copy the new value from the Save this token now dialog, and update the widget embed code with it.
From code, call Runtype.clientTokens.regenerate(id) or run runtype client-tokens regenerate <id>. The response’s token field shows the new value.
Revoke or delete a client token
Revoke a token to stop every widget that uses it while keeping the token record. In the workbench, open Access & keys, click Revoke on the token row, and then click Revoke token. The row shows revoked. From code, set isActive to false with Runtype.clientTokens.update(id, { isActive: false }) or the Client Tokens API.
To remove a token permanently, delete it with Runtype.clientTokens.delete(id), runtype client-tokens delete <id>, the delete_client_token MCP tool, or the API. Unlike regeneration, revoking or deleting a token does not issue a replacement value.
Next steps
- Embedding the chat widget (script tag): add the selected client token to a website
- Setting up a chat Surface: create and configure the Surface that hosts the widget
- Conversation history in an embedded widget: let visitors return to earlier conversations
- End-user identity and credentials: tie widget conversations to your signed-in users
- Client Tokens API reference: create, update, and regenerate tokens through the API