> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xuna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Secure your agent with authentication and access control

> Use signed URLs or conversation tokens to gate agent access. Generate them server-side so your XUNA AI API key stays out of the browser.

By default, agents created on XUNA AI require authentication to start a conversation. This prevents unauthorized callers from consuming your quota or accessing your agent's capabilities. Authentication is enforced at the connection layer — clients must present a valid signed URL or conversation token to open a session.

<Warning>
  Never expose your XUNA AI API key in client-side code. All authentication artifacts (signed URLs and conversation tokens) must be generated on your server and passed to the client.
</Warning>

## Signed URLs

A signed URL is a time-limited URL generated server-side that the client uses to open a WebSocket connection. It embeds your agent ID and authentication credentials so the client never needs your API key.

**Use signed URLs when** you are connecting via the WebSocket API or need fine-grained control over the connection parameters.

### Generate a signed URL

<CodeGroup>
  ```python python theme={null}
  from xuna_ai import XunaAI

  client = XunaAI()

  response = client.conversational_ai.conversations.get_signed_url(
      agent_id="your-agent-id"
  )

  signed_url = response.signed_url
  # Pass this URL to your client
  ```

  ```typescript typescript theme={null}
  import { XunaAIClient } from "@xuna-ai/xuna-ai-js";

  const client = new XunaAIClient();

  const response = await client.conversationalAi.conversations.getSignedUrl({
    agentId: "your-agent-id",
  });

  const signedUrl = response.signedUrl;
  // Pass this URL to your client
  ```

  ```python python-with-fastapi.py theme={null}
  from fastapi import FastAPI
  from xuna_ai import XunaAI

  app = FastAPI()
  client = XunaAI()

  @app.get("/get-signed-url")
  async def get_signed_url(current_user = Depends(get_current_user)):
      # Authenticate the user before issuing a signed URL
      response = client.conversational_ai.conversations.get_signed_url(
          agent_id="your-agent-id"
      )
      return {"signed_url": response.signed_url}
  ```
</CodeGroup>

### Use the signed URL on the client

Pass the signed URL to the XUNA AI client SDK instead of the agent ID:

```javascript browser.js theme={null}
import { Conversation } from "@xuna-ai/client";

// Fetch the signed URL from your server
const { signed_url } = await fetch("/get-signed-url").then(r => r.json());

const conversation = await Conversation.startSession({
  signedUrl: signed_url,
});
```

## Conversation tokens

A conversation token is a short-lived token generated server-side that the client uses to initiate a WebRTC session. Conversation tokens also support [dynamic variables](/configure/personalization) and [session overrides](/configure/personalization#session-overrides) at creation time.

**Use conversation tokens when** you are using the React SDK, mobile SDKs, or any WebRTC-based deployment, or when you want to attach per-session personalization data.

### Generate a conversation token

<CodeGroup>
  ```python python theme={null}
  from xuna_ai import XunaAI

  client = XunaAI()

  token_response = client.conversational_ai.conversations.get_token(
      agent_id="your-agent-id",
      # Optionally attach dynamic variables
      dynamic_variables={
          "user_name": "Jordan",
          "plan_name": "Pro",
      }
  )

  conversation_token = token_response.token
  # Pass this token to your client
  ```

  ```typescript typescript theme={null}
  import { XunaAIClient } from "@xuna-ai/xuna-ai-js";

  const client = new XunaAIClient();

  const tokenResponse = await client.conversationalAi.conversations.getToken({
    agentId: "your-agent-id",
    // Optionally attach dynamic variables
    dynamicVariables: {
      user_name: "Jordan",
      plan_name: "Pro",
    },
  });

  const conversationToken = tokenResponse.token;
  // Pass this token to your client
  ```

  ```typescript typescript-with-express.ts theme={null}
  import express from "express";
  import { XunaAIClient } from "@xuna-ai/xuna-ai-js";

  const app = express();
  const client = new XunaAIClient();

  app.get("/conversation-token", requireAuth, async (req, res) => {
    const user = req.user;

    const tokenResponse = await client.conversationalAi.conversations.getToken({
      agentId: "your-agent-id",
      dynamicVariables: {
        user_name: user.name,
        account_tier: user.tier,
      },
    });

    res.json({ token: tokenResponse.token });
  });
  ```
</CodeGroup>

### Use the conversation token on the client

Pass the token to the React SDK or client SDK:

<CodeGroup>
  ```tsx react.tsx theme={null}
  import { useConversationControls } from "@xuna-ai/react";

  function VoiceAgent() {
    const { startSession } = useConversationControls();

    const handleStart = async () => {
      // Fetch the token from your server
      const { token } = await fetch("/conversation-token").then(r => r.json());

      await startSession({ conversationToken: token });
    };

    return <button onClick={handleStart}>Start</button>;
  }
  ```

  ```javascript browser.js theme={null}
  import { Conversation } from "@xuna-ai/client";

  const { token } = await fetch("/conversation-token").then(r => r.json());

  const conversation = await Conversation.startSession({ token });
  ```
</CodeGroup>

## Choosing between signed URLs and conversation tokens

|                       | Signed URL                                       | Conversation token                                 |
| --------------------- | ------------------------------------------------ | -------------------------------------------------- |
| **Transport**         | WebSocket                                        | WebRTC                                             |
| **SDK support**       | WebSocket API, `@xuna-ai/client`                 | React SDK, iOS SDK, Android SDK, `@xuna-ai/client` |
| **Session overrides** | At connection time                               | At token creation time                             |
| **Dynamic variables** | At connection time                               | At token creation time                             |
| **Typical use**       | Custom WebSocket clients, low-level integrations | All SDK-based deployments                          |

## Public agents

If you want anyone to start a conversation without server-side authentication — for example, a public demo — you can mark the agent as public in the dashboard. Public agents accept connections without a signed URL or token.

<Warning>
  Public agents consume your XUNA AI quota for every conversation started. Set a [max conversation duration](/configure/conversation-flow) and monitor usage closely to avoid unexpected charges.
</Warning>

## Custom authentication middleware

For advanced use cases — such as integrating with your own identity provider or enforcing business-specific access rules — you can implement custom authentication middleware on your server. The pattern is the same for both signed URLs and conversation tokens: your server authenticates the user through whatever mechanism you choose, then issues the XUNA AI credential only if authentication passes.

See the server-side examples above for how to integrate user authentication with token generation.
