# 管理终端用户 (/docs/developers/agent/end-users)



按宿主应用的用户 ID 查找终端用户，并在需要时停止或恢复其嵌入访问。操作范围是当前组织、当前智能体下的这个用户。



## 准备与选择操作 [#prepare]

准备已有嵌入策略的服务智能体、组织管理权限，以及签发时使用的 `external_user_id`。先用隔离测试用户验证操作；撤销和封禁会中断其正在进行的对话。

| 目的        | 操作   | 后续访问                  |
| --------- | ---- | --------------------- |
| 结束当前凭证的访问 | 吊销令牌 | 撤销该用户的全部现有令牌；宿主仍可签发新的 |
| 持续拒绝用户访问  | 封禁   | 撤销现有令牌，拒绝新签发          |
| 恢复签发资格    | 解封   | 允许新签发；已撤销的旧令牌不会恢复     |

只想撤销一张令牌时，使用[令牌管理](/docs/developers/agent/embed/tokens)，不要执行用户级批量撤销。

## 查找并管理用户 [#manage]

<Tabs items="['控制台', 'API']">
  <Tab value="控制台">
    1. 打开「服务智能体 → 选择智能体 → 运营与管理 → 终端用户」。控件名称对应当前中文控制台。
    2. 在「按终端用户 ID 前缀搜索」输入 ID。列表显示首次出现、最后活跃、会话数、花费与状态。
    3. 点击用户 ID 打开详情；通过「最近会话」检查是否找对了用户。
    4. 在该用户行或详情中点击「吊销令牌」「封禁」或「解封」，核对确认框中的用户 ID，再点击对应确认按钮。

    **预期结果：** 撤销显示「已吊销 N 张」；封禁后状态为「已封禁」；解封后为「正常」。列表中的花费和会话数据不会因这些操作被删除。
  </Tab>

  <Tab value="API">
    在服务器准备支持原生 `fetch` 的 Node.js。设置 `PRAXIS_API_BASE` 为 API 源站、`PRAXIS_API_KEY` 为组织 API Key、`PRAXIS_AGENT` 为公开 `agent_…` ID。Key 只留在服务器。把下面保存为 `end-users.mjs`。

    ```js
    import assert from 'node:assert/strict';

    const { PRAXIS_API_BASE, PRAXIS_API_KEY, PRAXIS_AGENT } = process.env;
    assert(PRAXIS_API_BASE && PRAXIS_API_KEY && PRAXIS_AGENT, 'Set the three environment variables');
    assert(PRAXIS_AGENT.startsWith('agent_'), 'Use the public agent ID');
    const [action = 'list', user = ''] = process.argv.slice(2);
    assert(['list', 'get', 'revoke', 'block', 'unblock'].includes(action), 'Unknown action');
    const root = `${PRAXIS_API_BASE.replace(/\/$/, '')}/agent/v1/agents/${encodeURIComponent(PRAXIS_AGENT)}/end_users`;
    async function request(url, method = 'GET') {
      const response = await fetch(url, { method, headers: { Authorization: `Bearer ${PRAXIS_API_KEY}` } });
      if (!response.ok) throw new Error(`End-user request failed: HTTP ${response.status}`);
      return response.json();
    }
    if (action === 'list') {
      let page;
      do {
        const query = new URLSearchParams({ q: user, limit: '20' });
        if (page) query.set('page', page);
        const result = await request(`${root}?${query}`);
        console.log(result.data);
        page = result.next_page;
      } while (page);
    } else {
      assert(/^[A-Za-z0-9_.:@-]{1,128}$/.test(user), 'Provide the exact external_user_id');
      const url = `${root}/${encodeURIComponent(user)}`;
      const suffix = { revoke: 'revoke_tokens', block: 'block', unblock: 'unblock' }[action];
      const result = await request(suffix ? `${url}/${suffix}` : url, suffix ? 'POST' : 'GET');
      if (action === 'block') assert.equal(result.blocked, true);
      if (action === 'unblock') assert.equal(result.blocked, false);
      if (action === 'revoke') assert(Number.isInteger(result.revoked));
      console.log(result);
    }
    ```

    先执行 `node end-users.mjs list customer_` 按区分大小写的 ID 前缀查找，再执行 `node end-users.mjs get customer_42` 读取详情；替换为实际测试用户。需要停止访问时，选择运行 `node end-users.mjs revoke customer_42` 或 `node end-users.mjs block customer_42`。恢复时运行 `node end-users.mjs unblock customer_42`。

    这些写操作分别调用 `POST /agent/v1/agents/{agent_id}/end_users/{external_user_id}/revoke_tokens`、`POST /agent/v1/agents/{agent_id}/end_users/{external_user_id}/block`、`POST /agent/v1/agents/{agent_id}/end_users/{external_user_id}/unblock`，均不需要请求体。

    **预期结果：** 成功返回 HTTP 200；撤销返回 `revoked` 计数，封禁和解封分别返回 `blocked: true` 与 `blocked: false`。详情的 `recent_sessions` 可用于核对用户。`cost_micro_usd` 单位是百万分之一美元，不是美分。
  </Tab>
</Tabs>

## 验证访问边界 [#verify]

撤销后，用旧令牌再次访问，应被拒绝；宿主重新确认登录身份后可以签发新令牌。封禁后，宿主的签发请求应收到 `EMBED_USER_BLOCKED`（HTTP 403）。解封后重新签发并发送消息，确认恢复；不要以旧令牌重试成功作为解封标准。

同时用另一个用户访问同一智能体，确认其访问未被本次操作影响。用户身份不能取自浏览器任意提交的 ID，应由宿主后端的登录态确定。

## 处理失败 [#failures]

查不到用户时，核对组织、公开智能体 ID 和签发时的完整用户 ID；控制台试用调试的数据不会进入客户用户列表。仅有前缀相似不能证明是同一个用户。

若封禁返回失败，重新读取状态后再决定是否重试：封禁状态可能已经保存，而撤销步骤尚未完成。不要在这种情况下显示“封禁完成”。记录 HTTP 状态和请求 ID，按[限制与错误](/docs/developers/agent/embed/limits-and-errors)处理。

## 下一步 [#next]

[查看会话与用量](/docs/developers/agent/monitor) · [测试嵌入接入](/docs/developers/agent/embed/testing) · [智能体 API](/docs/api/native/agent) · [Cookbook](/docs/cookbook)
