This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/ai-assistant/viewer-integration/client-authentication/generate-a-jwt.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Generate a JWT for AI Assistant | Nutrient

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 no layer (or with layer: "") 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.
    • "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 a 401 error.
    • "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_id claim.
    • "user_id" is an optional non-empty string that identifies the user. If provided, it must match the userId in the client configuration — a mismatch will return a 401 error. The user_id is stored as thread metadata.
  • It’s also possible to throttle a user’s usage. The user_id claim 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 the context.modelServices request 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
    • The "providers" slice controls which provider configurations a request may supply. Use provider names as keys and true as the value. Avoid granting provider overrides to browser clients unless users are expected to supply their own credentials.
    • For either slice, true allows any request entry in that slice; false or 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 of access.
  • 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.

  1. 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.key
    openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256_pub.pem

    The 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 a JWT_PUBLIC_KEY in 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 the RS256 algorithm as the JWT_ALGORITHM in 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.

  2. Install the jsonwebtoken dependency:

    Terminal window
    npm install --save jsonwebtoken
  3. Read 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.
    },
    );