# 嵌入令牌与用户 (/docs/developers/agent/embed/tokens)





嵌入令牌让终端用户访问指定智能体及自己名下的会话。登录用户的令牌由宿主后端签发，浏览器消费；本页说明身份、有效期、刷新和单张令牌吊销。

准备同组织的服务智能体、已保存的嵌入策略和服务端 API Key。策略在控制台的**接入与配置 → 配置 → 安全**保存，目前只能在控制台配置。示例使用 Develop；完整网页接入见[嵌入到网页](/docs/developers/agent/embed/quickstart)，一次性跳转见 [Hosted](/docs/developers/agent/embed/hosted)。

## 身份与访问范围 [#identity]

宿主后端用自己的登录系统确认用户，再以 `external_user_id` 传入稳定标识。同一组织、同一智能体、同一标识对应同一个终端用户；它不是组织成员 ID，也不是昵称。标识长 1–128 字符，仅允许字母、数字、下划线、点、冒号、`@` 和连字符。

| 凭证               | 谁使用        | 用途                |
| ---------------- | ---------- | ----------------- |
| API Key          | 宿主后端       | 签发、查看和吊销令牌；不交给浏览器 |
| 嵌入令牌             | 终端用户客户端    | 访问指定智能体下自己的会话     |
| 可公开 key          | 匿名接入页面     | 获取匿名令牌，可写入网页源码    |
| `visitor_secret` | 同一匿名访客的浏览器 | 延续访客身份；必须保密       |

刷新令牌不会改变用户身份。更换登录账户则应移除旧组件，按新身份重新创建；界面中的确认按钮不能代替工具服务端的账户授权。

## 签发令牌 [#issue]

生产签发由宿主后端调用 `POST /agent/v1/embed_tokens`。以下是服务端独立检查，测试用户固定为 `customer_123`；真实宿主必须从验证后的登录会话取值，不接受浏览器自报身份。控制台的**试用调试 → 模拟 → 重新签发**只用于调试身份。

<Tabs items="['cURL', 'TypeScript']">
  <Tab value="cURL">
    在服务端设置 `PRAXIS_API_KEY`，替换实际公开 ID。需要 cURL 和 jq。

    ```bash
    set -o pipefail
    curl --fail-with-body --silent --show-error \
      -X POST https://api-develop.deepraxis.com/agent/v1/embed_tokens \
      -H "Authorization: Bearer $PRAXIS_API_KEY" \
      -H "Content-Type: application/json" \
      --data '{"agent":"agent_REPLACE_ME","external_user_id":"customer_123"}' \
      | jq '{id, expires_at, has_token: (.token | type == "string")}'
    ```
  </Tab>

  <Tab value="TypeScript">
    保存为 `issue-token.ts`。在服务端设置 `PRAXIS_API_KEY`、实际 `PRAXIS_AGENT_ID` 和 `TEST_EXTERNAL_USER_ID=customer_123`，运行 `bun issue-token.ts`。

    ```ts
    const apiKey = process.env.PRAXIS_API_KEY;
    const agentId = process.env.PRAXIS_AGENT_ID;
    const userId = process.env.TEST_EXTERNAL_USER_ID;
    if (!apiKey || !agentId || !userId) throw new Error("Set the three server variables");
    const response = await fetch("https://api-develop.deepraxis.com/agent/v1/embed_tokens", {
      method: "POST",
      headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
      body: JSON.stringify({ agent: agentId, external_user_id: userId }),
    });
    if (!response.ok) throw new Error(`Issuance failed: ${response.status}`);
    const data = await response.json();
    if (typeof data.token !== "string" || typeof data.expires_at !== "string") {
      throw new Error("Invalid token response");
    }
    console.log({ id: data.id, expires_at: data.expires_at, has_token: true });
    ```
  </Tab>
</Tabs>

期望：HTTP 201，响应含 `id`、`token`、`expires_at`。`id` 标识本次签发，供后续吊销；`token` 是浏览器凭证；`expires_at` 是 ISO 8601 UTC 到期时间。示例只输出脱敏检查结果。每次调用都会新建签发记录，重复请求不幂等。

可选 `ttl_seconds` 省略时取策略默认值；范围从 60 秒到策略上限与 3600 秒中的较小值。`budget_cents` 不能超过策略单会话预算上限；`allowed_origins` 只能取策略已有条目的子集。版本按显式 `version`、策略固定版本、最新版本的次序选择，签发后固定。完整字段见 [API 参考](/docs/api/native/agent)。

## 刷新与结束访问 [#refresh]

Kit 调用 `getToken(current)` 时，首次传入 `null`，刷新时传入当前令牌。把以下回调放进快速开始的 `api.getToken`；宿主端点每次都验证用户仍已登录，再返回新令牌。

```js
async function getToken(current) {
  const response = await fetch("/api/praxis-token", {
    method: "POST", credentials: "same-origin", cache: "no-store",
  });
  if (!response.ok) throw new Error(`Token request failed: ${response.status}`);
  const data = await response.json();
  if (typeof data.token !== "string" || !data.token) throw new Error("Missing token");
  return data.token;
}
```

回调不需要把当前令牌发回宿主来证明身份。Kit 负责调用时机并在内存中持有令牌；不要把一个固定令牌写死在回调中。期望：刷新后仍访问同一用户的会话。

宿主退出登录时，先结束宿主会话，再移除旧 Kit 元素；移除会关闭它的连接，但不会在服务器吊销已签发的令牌。需要立即禁止原凭证访问时，另行吊销。Hosted 没有这条刷新路径，到期后必须回宿主重新进入。

## 匿名访客 [#anonymous]

公开问答不要求登录时，在**接入与配置 → 密钥 → 可公开 key → 新建可公开 key**创建 key，设置匿名限流和预算，并复制只显示一次的完整值。来源仍在**配置 → 安全**管理。这些设置目前只能在控制台配置；可公开 key 与服务端 API Key 不能互换。

以下是可独立保存的 `index.html`。替换 `PRAXIS_PUBLISHABLE_KEY` 为实际可公开 key，并从允许来源的网站提供此文件。它不需要登录用户的宿主签发端点；不要同时配置 `getToken`。

```html
<!doctype html>
<html lang="zh">
<meta charset="utf-8">
<title>Public documentation assistant</title>
<script type="module" src="https://kit-develop.praxisusercontent.com/v0.2.8/praxis-kit.js" integrity="sha384-vey+VZZcFIWJq9Fmv/nmLxspKtXRiL8XiDOfg2g2QFvwZi5MA+tkiQvPBtoMS8eU" crossorigin="anonymous"></script>
<praxis-agent style="display:block;height:600px;width:100%"></praxis-agent>
<script type="module">
  await customElements.whenDefined("praxis-agent");
  document.querySelector("praxis-agent").setOptions({
    api: {
      url: "https://api-develop.deepraxis.com",
      publishableKey: "PRAXIS_PUBLISHABLE_KEY",
    },
    agent: { name: "Documentation assistant" },
  });
</script>
</html>
```

Kit 向 `POST /agent/v1/embed_tokens/anonymous` 提交 `publishable_key`，后续带上已有的 `visitor_secret`。接口不需要 API Key，成功返回 HTTP 200、`token`、`expires_at`；仅新访客另收到 `visitor_secret`。浏览器不能选择 `external_user_id`、预算、指令或 Hosted 选项。

Kit 将访客凭据保存在本地存储，存储不可用时退回页面内存。不要公开或共享它。期望：同一存储中的访客刷新后延续身份；独立浏览器存储产生独立身份。匿名身份不授予读取实名订单等账户数据的权限。

## 查看与吊销 [#revoke]

只吊销需要停止使用的那张令牌。下面两种路径完成同一任务。

<Tabs items="['控制台', 'API']">
  <Tab value="控制台">
    进入该智能体的**运营与管理 → 签发记录**，在**终端用户**中填写 `customer_123`，点**筛选**。从有效记录行点**吊销**，核对用户后点**确认吊销**。期望：记录状态变为已吊销；失败时页面提供重试。
  </Tab>

  <Tab value="API">
    在服务端设置 `PRAXIS_API_KEY`、实际 `PRAXIS_AGENT_ID`，查询该用户的记录。响应 `data` 包含记录，不包含令牌；需要下一页时把 `next_page` 作为 `page`，保留原筛选条件。

    ```bash
    curl --fail-with-body --silent --show-error --get \
      https://api-develop.deepraxis.com/agent/v1/embed_tokens \
      -H "Authorization: Bearer $PRAXIS_API_KEY" \
      --data-urlencode "agent_id=$PRAXIS_AGENT_ID" \
      --data-urlencode "external_user_id=customer_123"
    ```

    把要吊销的记录 `id` 存为服务端变量 `EMBED_TOKEN_ID`，再调用：

    ```bash
    curl --fail-with-body --silent --show-error -X POST \
      "https://api-develop.deepraxis.com/agent/v1/embed_tokens/$EMBED_TOKEN_ID/revoke" \
      -H "Authorization: Bearer $PRAXIS_API_KEY"
    ```
  </Tab>
</Tabs>

期望：单张吊销返回带 `revoked_at` 的记录；重复吊销返回原记录。该令牌的事件流收到 `session.error`，错误码为 `EMBED_TOKEN_REVOKED`，随后关闭；其他令牌不受影响。用户级批量撤销、封禁和解封见[管理终端用户](/docs/developers/agent/end-users)。

## 错误与下一步 [#next]

到期后重新验证宿主登录；吊销后停止原访问；来源不允许时修正配置；用户被封禁时交由管理者处理。不要无限重签来绕过拒绝。具体错误码、HTTP 状态和恢复条件见[嵌入限制与错误](/docs/developers/agent/embed/limits-and-errors)。

* [传入上下文](/docs/developers/agent/embed/context)
* [管理终端用户](/docs/developers/agent/end-users)
* [打开托管对话](/docs/developers/agent/embed/hosted)
* [智能体 API 参考](/docs/api/native/agent)
