Fency.ai

Sessions and client tokens

How the React SDK uses short-lived client tokens created by your server.

The React SDK uses client tokens to authenticate with the Fency.ai API. Client tokens are short-lived credentials that your server creates by calling the Fency API. Sessions control access to memories: you decide which memories each session can access when creating it via guardrails. This architecture keeps your secret key secure on the server while allowing your React app to make authenticated requests.

How it works

  1. Client SDK initiates the request: When the React SDK needs to make API requests (e.g. for creating a new stream), it calls the fetchCreateStreamClientToken function you pass to FencyProvider, which requests a client token from your server endpoint (e.g. /api/stream-client-token).
  2. Server creates a session: Your backend receives the request and calls the Fency API with your secret key. The API returns a session object that includes a clientToken, which your endpoint returns to the frontend.
  3. SDK uses the token: The React SDK receives the clientToken and uses it to authenticate with the Fency.ai API.

Session types

Different session types support different features. A stream session enables basic streaming. An agent task session is required for chat completions (streaming, structured, or memory-based). Your server endpoint should create the appropriate session type for the features your React app needs.

Stream session

The React SDK uses a short-lived stream client token to authenticate real-time task updates in the browser. Your server creates this token by calling the Fency sessions API and returning the result to the client. Pass it to FencyProvider via the fetchCreateStreamClientToken callback.

// server route handler (e.g. POST /api/stream-session)
const secretKey = process.env.FENCY_SECRET_KEY

if (!secretKey) {
    throw new Error('FENCY_SECRET_KEY is not defined.')
}

export async function POST() {
    const response = await fetch('https://api.fency.ai/v1/sessions', {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${secretKey}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ createStream: {} }),
    })

    // data: { id, createdAt, type, clientToken }
    const data = await response.json()
    return new Response(JSON.stringify(data), {
        status: response.status,
        headers: { 'Content-Type': 'application/json' },
    })
}
import { loadFency } from '@fencyai/js'
import { FencyProvider } from '@fencyai/react'

const fency = loadFency({
    publishableKey: 'pk_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
})

async function fetchCreateStreamClientToken() {
    const res = await fetch('/api/stream-session', { method: 'POST' })
    if (!res.ok) {
        throw new Error('Failed to create stream session')
    }
    const data = await res.json()
    if (!data.clientToken) {
        throw new Error('No clientToken in session response')
    }
    return { clientToken: data.clientToken }
}

export default function Home() {
    return (
        <FencyProvider
            fency={fency}
            fetchCreateStreamClientToken={fetchCreateStreamClientToken}
        >
            <App />
        </FencyProvider>
    )
}

Agent task session

Each call to createAgentTask in the React SDK requires a short-lived agent task client token. Your server creates this token by calling POST https://api.fency.ai/v1/sessions with a createAgentTask body. The task type and any access restrictions (such as which memories are available) are set here on the server, never on the client.

// server route handler (e.g. POST /api/agent-task-session)
const secretKey = process.env.FENCY_SECRET_KEY

if (!secretKey) {
    throw new Error('FENCY_SECRET_KEY is not defined.')
}

export async function POST() {
    const response = await fetch('https://api.fency.ai/v1/sessions', {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${secretKey}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            createAgentTask: {
                taskType: 'STREAMING_CHAT_COMPLETION',
            },
        }),
    })

    const data = await response.json()
    return new Response(JSON.stringify(data), {
        status: response.status,
        headers: { 'Content-Type': 'application/json' },
    })
}

Pass a callback that fetches the agent task token to useAgentTasks:

const fetchCreateAgentTaskClientToken = async () => {
    const res = await fetch('/api/agent-task-session', { method: 'POST' })
    if (!res.ok) {
        throw new Error('Failed to create agent task session')
    }
    const data = await res.json()
    if (!data.clientToken) {
        throw new Error('No clientToken in session response')
    }
    return { clientToken: data.clientToken }
}

const { createAgentTask } = useAgentTasks({
    fetchCreateAgentTaskClientToken,
})

Access restrictions such as guardRails.memoryTypes for memory-based tasks and allowedActions for Explore Product are set here on the server. See Guardrails and allowed actions for how they work per task type. For complete, task-type-specific routes, see the integration examples.

Security

Client tokens are scoped to a single session and expire after use or when the session ends. They never expose your secret key. By creating sessions on your server, you maintain full control over who can obtain client tokens and can enforce your own authentication and rate limiting before creating sessions.

On this page