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

# Client Tokens

A client token lets the **browser** call Channel3 without your API key. It looks like `c3_ct_…`. It can only run turns and read the one conversation it is bound to. The API key that minted it pays for those turns.

Mint on your server. Never mint from the browser. Send the token as `Authorization: Bearer c3_ct_…` — see the [quickstart](/conversations/quickstart).

## Mint

Default lifetime is 30 minutes (`ttl_seconds` from 60 to 7200).

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const created = await server.conversations.clientTokens.create();
  // created.token        — c3_ct_…
  // created.token_id
  // created.expires_at
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  created = server.conversations.client_tokens.create()
  # created.token, created.token_id, created.expires_at
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://api.trychannel3.com/v1/conversations/client_tokens \
    -H "x-api-key: $CHANNEL3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{}'
  ```
</CodeGroup>

With **no** `conversation_id`, the token's first turn creates the conversation and the token is bound to it. That is the [quickstart](/conversations/quickstart) path.

## Pin a token to an existing conversation

Pass `conversation_id` when the shopper already has a chat — for example they refreshed the page and you minted a new token. The token can only continue and read that conversation. Any other id returns `404`.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const scoped = await server.conversations.clientTokens.create({
    conversation_id: conversationId,
    ttl_seconds: 600, // optional, seconds
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  scoped = server.conversations.client_tokens.create(
      conversation_id=conversation_id,
      ttl_seconds=600,
  )
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://api.trychannel3.com/v1/conversations/client_tokens \
    -H "x-api-key: $CHANNEL3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"conversation_id": "conv_123", "ttl_seconds": 600}'
  ```
</CodeGroup>

An expired or revoked token returns `401`. Mint a new one before it expires. If you already have a `conversation_id`, pass it so the new token stays on the same conversation.

## Revoke

Revoke from the server when the session ends. Put the token in the body so it stays out of URL logs.

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  await server.conversations.clientTokens.revoke({ token: "c3_ct_..." });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  server.conversations.client_tokens.revoke(token="c3_ct_...")
  ```

  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://api.trychannel3.com/v1/conversations/client_tokens/revoke \
    -H "x-api-key: $CHANNEL3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"token": "c3_ct_..."}'
  ```
</CodeGroup>
