JSON Web Tokens (JWTs) used for authentication by AI Assistant can be generated with one of the many open source libraries that are available and listed on jwt.io(opens in a new tab).
Token requirements
- It has to include the standard claim
"exp", which sets the deadline for the validity of the token. This needs to be a non-negative number using the Unix “Seconds Since the Epoch” timestamp format(opens in a new tab). - Additionally, you can restrict the documents and sessions a user can access.
"allowed_documents"restricts document access per document and, when running with Document Engine, per layer within that document.- Standalone mode — Its value is an array of
{ "document_id": string }entries listing the documents the user can access. - Document Engine mode — Its value is an array of
{ "document_id": string, "layer"?: string }entries. Each entry authorizes one document on one layer: An entry with nolayer(or withlayer: "") authorizes only that document’s default layer, and any other layer of a listed document is rejected. The same document may be listed under more than one layer. - In either mode, the value may instead be the literal string
"any", which (like omitting the claim entirely) grants access to every document, whereas an empty array ([]) grants access to no documents.
- Standalone mode — Its value is an array of
"document_ids"is deprecated; use"allowed_documents"instead. This legacy claim is an optional array of document identifiers, each granting access to that document on any layer; every requested document must be authorized, and omitted/empty values behave the same way as for"allowed_documents". A token that carries both claims is rejected with a401error."session_ids"is an array of session IDs. These IDs define the sessions a user can access. If this claim is omitted, the user can access any session data. This ID is a unique string passed to the Nutrient Web SDK configuration.
- You can identify the user making the request by including a
user_idclaim."user_id"is an optional non-empty string that identifies the user. If provided, it must match theuserIdin the client configuration — a mismatch will return a401error. Theuser_idis stored as thread metadata.
- It’s also possible to throttle a user’s usage. The
user_idclaim must be set in the JWT to implement rate limiting."request_limit"is an optional object that defines the maximum number of requests a user can make in a given time period. If this claim is omitted, the user can make an unlimited number of requests."requests"is the maximum number of requests a user can make in the given time period."time_period_s"is the time period in seconds in which the user can make the maximum number of requests.
- You can control access to saved agents with the optional
"agents"claim. Refer to the saved-agent authorization section for its shape and behavior. - You can control which model service overrides a request may supply.
"agent_configuration"is an optional object used to authorize runtime agent configuration overrides."agent_configuration.model_services"is an optional object that gates thecontext.modelServicesrequest field.- The
"models"slice controls model definitions by label. Keys are exact model labels (for example,"default-llm") or"*"as a fallback for labels that aren’t explicitly listed. - Supported model allowlist values:
- Exact model (for example,
"openai:gpt-5.4-mini") - Provider wildcard (for example,
"openai:*") - Full wildcard (
"*"), which allows any model or provider
- Exact model (for example,
- The
"providers"slice controls which provider configurations a request may supply. Use provider names as keys andtrueas the value. Avoid granting provider overrides to browser clients unless users are expected to supply their own credentials. - For either slice,
trueallows any request entry in that slice;falseor an omitted slice denies request-supplied entries in that slice.
Example:
{ "agent_configuration": { "model_services": { "models": { "default-llm": ["openai:gpt-5.4-mini", "anthropic:*"], "*": ["openai:*"] } } }}The legacy agent_configuration.model_overrides claim is deprecated in AI Assistant 2.2.0. Existing tokens continue to work through automatic translation, but new integrations should use agent_configuration.model_services.models.
Saved-agent authorization
The optional agents claim controls operations on saved agents. It accepts a Boolean or an object with this shape:
type AgentsClaim = | boolean | { create?: boolean; access?: Record< string, Array<"read" | "run" | "update" | "delete" | "*"> >; };The available permissions are:
create— Create saved agents. This is a top-level, collection-wide permission because the saved resource doesn’t exist yet, so it’s evaluated independently ofaccess.read— List or retrieve a saved agent, its schemas, or its version history.run— Execute a saved agent and access its thread state and history.update— Patch a saved agent or roll it back to an earlier version.delete— Permanently delete a saved agent and its version history.*— Grant all actions for the selected saved agent or agents.
Actions don’t imply one another. For example, run doesn’t grant read; grant both actions to clients that need to discover and execute saved agents.
Set agents to true to grant full administrative access, including creation and every action on every saved agent:
{ "agents": true}Use the "*" key under access to grant actions for every saved agent in the deployment. Reserve this deployment-wide scope for trusted or administrative clients. This example allows such a client to discover and run saved agents without modifying them:
{ "agents": { "access": { "*": ["read", "run"] } }}Prefer explicit per-agent grants. Keys in the access map are stable, product-facing agent_id slugs, such as contract-review:
{ "agents": { "access": { "contract-review": ["read", "run", "update"], "invoice-extraction": ["run"] } }}When access contains both an exact agent_id and "*", the exact entry replaces the wildcard permissions; they aren’t combined. An exact entry with an empty array ([]) is therefore an explicit deny for that saved agent.
If the claim is omitted, is set to false, or is an object without a matching access entry, the token can’t read, run, update, or delete existing saved agents. The create permission is independent: { "agents": { "create": true } } permits creation but doesn’t grant access to existing saved agents or to the newly created saved agents. Built-in presets (chat, agentic, and base) remain readable and runnable regardless of this claim, but they can’t be created, updated, or deleted.
The agents object is strictly validated. Unknown or misspelled properties, unsupported actions, and values of the wrong type cause JWT verification to fail with an HTTP 401 response instead of being ignored.
The run permission doesn’t replace session authorization. Set session_ids when clients must be isolated to specific sessions; omitting session_ids allows access to all session data.
Generating tokens
The following example shows the creation of a JWT in JavaScript using the jsonwebtoken(opens in a new tab) library.
Create a key via
ssh-keygen:Terminal window ssh-keygen -t rsa -b 4096 -f jwtRS256.key# Enter your passphrase.# Get the public key in PEM format:openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256_pub.pem# If the above command fails because newer versions of `ssh-keygen` output a different format,# convert the key to PEM like this and then repeat the `openssl` command.ssh-keygen -p -m PEM -t rsa -b 4096 -f jwtRS256.keyopenssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256_pub.pemThe private key (
jwtRS256.key) is used to sign the tokens you hand out to the clients.The public key (
jwtRS256_pub.pem) needs to be added as aJWT_PUBLIC_KEYin AI Assistant’s configuration so that the server will be able to validate the tokens’ signatures but won’t be able to create valid signatures. This example assumes you chose theRS256algorithm as theJWT_ALGORITHMin AI Assistant’s configuration.If you want to quickly test Nutrient Web SDK with your application, you can also use the key from our example apps(opens in a new tab) (passphrase: secret). Make sure to change to a self-generated key before going into production.
Install the
jsonwebtokendependency:Terminal window npm install --save jsonwebtokenRead the private key so that it can be used to sign JWTs. In the claims, pass the set of permissions you want to have, along with the expiration. You can then use the resulting token in your application:
const fs = require("fs");const jwt = require("jsonwebtoken");const key = fs.readFileSync("./jwtRS256.key");const token = jwt.sign({allowed_documents: [{ document_id: "abc" }],session_ids: ["session-abc-123"],user_id: "user-abc-123",agents: {access: {"contract-review": ["read", "run"],},},agent_configuration: {model_services: {models: {"default-llm": ["openai:gpt-5.4-mini", "anthropic:*"],"*": ["openai:*"],},},},},{key,passphrase: "YOUR_PASSPHRASE_GOES_HERE",},{algorithm: "RS256",expiresIn: 60 * 60, // 1 hour — this will set the `exp` claim for us.},);