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

# Portal token

> 使用平台 client token 换取只读的 Portal 用户 token。

Portal token 接口返回一个短时用户 token，平台后端用它调用只读的 [只读接口](https://docs.clinkbill.com/cn/api-reference/agent-portal/read_apis.md)。如果已有 Clink 用户还没有授权过当前平台，该接口会返回浏览器授权 URL，而不是 token。

## 请求

```http theme={null}
POST {API_BASE_URL}/agent/cwallet/platform/portal/tokens
Authorization: Bearer {PLATFORM_CLIENT_TOKEN}
Content-Type: application/json
```

| 字段 | 必填 | 含义 |
| - | - | - |
| `email` | 是 | 平台已验证的用户邮箱。 |
| `external_user_id` | 否 | 平台侧稳定用户 ID，用于审计和回调绑定。 |
| `device_id` | 否 | 稳定设备标识；默认由平台 client 和客户生成。 |
| `redirect_uri` | 首次授权时 | 平台 client 注册的精确回调 URI。 |
| `state` | 首次授权时 | 不透明 CSRF 值，会在 OAuth 回调返回。 |
| `code_challenge` | 首次授权时 | PKCE S256 challenge。 |
| `code_challenge_method` | 首次授权时 | 传 `code_challenge` 时必须为 `S256`。 |

新客户，或已经有有效 LoginSession 的客户，只需要 `email`：

```json theme={null}
{
  "email": "user@example.com"
}
```

已有 Clink 用户第一次授权时，需要完整的重定向和 PKCE 字段：

```json theme={null}
{
  "email": "user@example.com",
  "external_user_id": "platform-user-123",
  "redirect_uri": "https://agent.example.com/oauth/clink/callback",
  "state": "{RANDOM_STATE}",
  "code_challenge": "{S256_CODE_CHALLENGE}",
  "code_challenge_method": "S256"
}
```

## 已授权

响应使用统一 `{ code, msg, data }` envelope；以下示例展示完整响应。

```json theme={null}
{
  "code": 200,
  "msg": "Success",
  "data": {
    "status": "authorized",
    "customer_id": "ccus_...",
    "token_type": "Bearer",
    "access_token": "<Portal 用户 token>",
    "expires_in": 3600,
    "scope": "portal:read"
  }
}
```

Token claims：

| Claim | 值 |
| - | - |
| `aud` | `clink-agent-platform-portal` |
| `scope` | `portal:read` |
| `sub` | `customer_id` |
| `client_id` | 平台 client ID |
| `sid` | LoginSession ID |
| `token_use` | `access` |

调用 `/agent/platform/**` 时使用：

```http theme={null}
Authorization: Bearer {PORTAL_TOKEN}
```

## 需要授权

```json theme={null}
{
  "code": 200,
  "msg": "Success",
  "data": {
    "status": "authorization_required",
    "customer_id": "ccus_...",
    "authorization_url": "https://api.clinkbill.com/agent/cwallet/oauth/authorize?..."
  }
}
```

表示 Clink 找到了已有客户，但当前平台 client 没有该客户的有效 LoginSession。让用户打开 URL，完成标准 OAuth + PKCE 回调后再次调用本接口。

授权 URL 可用时会携带 `email` 预填 Portal 登录页；用户仍然需要完成邮箱 OTP 验证，预填只是省去重新输入邮箱。

如果用户取消或拒绝授权，不要绕过浏览器同意流程；重新发起一次授权请求。

## 新用户与已有用户

| 用户状态 | 返回 |
| - | - |
| 邮箱在 Clink 不存在 | Clink 创建客户和 LoginSession，返回 `authorized`，不打开浏览器。 |
| 客户存在，当前平台有有效 LoginSession | 返回新的 Portal access token。 |
| 客户存在，当前平台没有有效 LoginSession | 返回 `authorization_required` 和授权 URL，不返回 token。 |

Portal token 只有 access token。平台不会拿到 Portal refresh token；需要重新获取时，用平台 client token 再换一次。平台 token 始终是获取用户 token 的权限来源，只能保存在服务端。

## 错误

| HTTP status | 含义 |
| - | - |
| `400` | 请求无效、缺少邮箱，或 LoginSession 数据无效/过期。 |
| `401` | 平台 client token 缺失、过期或无效。 |
| `403` | 平台 client 被禁用、未注册或缺少 scope。 |
| `429` | 超过限流。 |

不要把平台 token 或 Portal token 放进浏览器 URL、日志或 Agent 可见输出。

## 下一步

使用 Portal token 调用 [只读接口](https://docs.clinkbill.com/cn/api-reference/agent-portal/read_apis.md)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.