Auth

Every request to Stream Agents is authenticated in one of two ways:

Side Runs in Holds Can reach
Server side Your backend, workers, scripts API key and API secret Everything in your app
Client side The browser, iOS, Android and Flutter API key and a user token That user's own sessions

The API key is public. The secret is not: never ship it in an app or a web page. Instead, your backend uses the secret to mint a token for each signed-in user, and the app connects with the key and that token.

Server side

On a server, set your key and secret in the environment:

export STREAM_API_KEY=your_api_key
export STREAM_API_SECRET=your_api_secret

The client reads them, so it needs no arguments:

from vision_agents.plugins import stream

client = stream.Client()

The SDK signs a short-lived server token with the secret on every request. A server-side client can configure and sync agents, and read and manage every session in your app.

Client side

An app holds only the public API key. Once the user has signed in, hand the SDK a token your backend minted for them:

import { Client } from "@stream-io/calypso";

const client = new Client({ apiKey: "your_api_key" });
await client.setUser("jlahey", () =>
  fetch("/api/agent-token").then((response) => response.text()),
);

The token can be a string, or a function that fetches one from your backend. The Kotlin and Swift SDKs ask the function once and keep the token, then ask again when the router refuses it with a 401 and send the request once more. The JavaScript SDK does the same in releases after 0.3.1, where it also asks again shortly before the token's exp and when a session's socket is refused; 0.3.1 and earlier ask the function before every request.

When the user signs out, call clearUser(): the SDK forgets them and their token, and asks for a user again before its next request.

A client-side caller only reaches that user's own sessions. It can open, watch, ask, search, fork, rewind, rename, close and delete them. Configuring agents, syncing, and reading call records are server side only. Each page lists what a device may call, such as Sessions.

Guests

A guest lets somebody talk to an agent before they sign up. The app asks for one with the public API key alone, and acts as it with setUser(guest), which takes the token the guest was minted with. In JavaScript:

import { Client } from "@stream-io/calypso";

const client = new Client({ apiKey: "your_api_key" });

// Remembered in a cookie, else localStorage, so a reload is the same guest with the same
// conversations. An expired guest is refreshed rather than replaced.
const guest = await client.guestUser({ name: "Guest" });
await client.setUser(guest);

const session = await client.agent("support").sessions.create();

guestUser({ fresh: true }) starts over as a new guest, for a "not me" button. When the guest signs up or signs in, your backend moves their conversations onto the account, and the app forgets the guest and sets the real user:

// On your backend, with STREAM_API_KEY and STREAM_API_SECRET in the environment,
// once it has authenticated the account the guest turned out to be.
import { Client } from "@stream-io/calypso";

const server = new Client();
await server.claimGuestUser(guestId, "jlahey");

In the page, client.forgetGuest() clears the remembered guest before setUser names the account. Claiming is server side only: a page able to claim could take anybody's conversations by guessing a guest id. An app that has turned guests off refuses guestUser with a 403, from a page and from your backend alike. A router that admits no anonymous callers refuses only the page: guestUser throws a ConfigurationError, and your backend mints the guest with guestUser and hands it to the page for setUser.

Generating a token

For testing, mint one with the CLI:

getstream token jlahey --ttl 2h

In production, your backend mints the token after it has signed the user in, and returns it to the app. A token is a JWT signed with your API secret (HS256), naming the user in user_id. It must carry an expiry: a token without exp is refused.

Stream's server SDKs mint one for you:

from getstream import Stream

stream = Stream(api_key=os.environ["STREAM_API_KEY"], api_secret=os.environ["STREAM_API_SECRET"])

token = stream.create_token("jlahey", expiration=3600)

The same token signs that user in to Stream Chat and Video, so one endpoint on your backend serves all three.