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

# 交换授权码或轮换刷新令牌

> 使用公共客户端的 S256 授权码或其当前刷新令牌获取短期 MCP 访问令牌。

使用公共客户端的 S256 授权码或其当前刷新令牌获取短期 MCP 访问令牌。

```http theme={null}
POST https://api.voicecheap.ai/oauth/token
```

## 身份验证

不使用 HTTP 客户端身份验证。在 URL 编码的请求体中发送 client\_id。任何 Authorization 请求头、client\_secret 或 client\_assertion 都会被拒绝。

## 请求体

<Expandable title="grant_type: authorization_code">
  <ParamField body="client_id" type="string" required>
    动态注册返回的 client\_id，或客户端元数据文档的精确公开 HTTPS URL。

    最小长度: 1. 最大长度: 2048. 匹配模式: `^[^\u0000-\u001F\u007F]+$`.
  </ParamField>

  <ParamField body="resource" type="string" required>
    必须使用完全匹配的资源受众：请使用此环境受保护资源元数据中的 resource 值。生产环境中为 [https://api.voicecheap.ai/mcp。不同的源地址、路径或末尾斜杠均会被拒绝。](https://api.voicecheap.ai/mcp。不同的源地址、路径或末尾斜杠均会被拒绝。)

    格式: "uri".
  </ParamField>

  <ParamField body="grant_type" type="field" required>
    由完整输入架构定义的值。

    指定值: "authorization\_code".
  </ParamField>

  <ParamField body="code" type="string" required>
    返回至完全匹配的已注册回调地址的一次性代码。

    最小长度: 1. 最大长度: 256. 匹配模式: `^mcp_code_[A-Za-z0-9_-]{43}$`.
  </ParamField>

  <ParamField body="redirect_uri" type="string" required>
    必须与绑定到此授权码和注册信息的回调完全一致。

    最小长度: 1. 最大长度: 2048. 匹配模式: `^[^\u0000-\u001F\u007F]+$`.
  </ParamField>

  <ParamField body="code_verifier" type="string" required>
    用于计算授权请求 S256 code\_challenge 的原始 PKCE 验证值。

    最小长度: 43. 最大长度: 128. 匹配模式: `^[A-Za-z0-9._~-]{43,128}$`.
  </ParamField>
</Expandable>

<Expandable title="grant_type: refresh_token">
  <ParamField body="client_id" type="string" required>
    动态注册返回的 client\_id，或客户端元数据文档的精确公开 HTTPS URL。

    最小长度: 1. 最大长度: 2048. 匹配模式: `^[^\u0000-\u001F\u007F]+$`.
  </ParamField>

  <ParamField body="resource" type="string" required>
    必须使用完全匹配的资源受众：请使用此环境受保护资源元数据中的 resource 值。生产环境中为 [https://api.voicecheap.ai/mcp。不同的源地址、路径或末尾斜杠均会被拒绝。](https://api.voicecheap.ai/mcp。不同的源地址、路径或末尾斜杠均会被拒绝。)

    格式: "uri".
  </ParamField>

  <ParamField body="grant_type" type="field" required>
    由完整输入架构定义的值。

    指定值: "refresh\_token".
  </ParamField>

  <ParamField body="refresh_token" type="string" required>
    已注册 refresh\_token 的客户端当前尚未使用的刷新令牌。

    最小长度: 1. 最大长度: 256. 匹配模式: `^mcp_refresh_[A-Za-z0-9_-]{43}$`.
  </ParamField>

  <ParamField body="scope" type="string">
    以空格分隔的原始权限子集，可包含 offline\_access。不支持的名称、空值和新增权限会被拒绝；重复项会被移除。显式 scope 仅保留列出的产品和身份权限，也可以移除所有产品权限。省略 scope 会保留当前授权，但 OpenID Connect 不可用时会移除身份权限。

    最小长度: 1. 最大长度: 2048. 匹配模式: `^ *(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email)(?: +(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email))* *$`.
  </ParamField>
</Expandable>

<Accordion title="技术架构">
  ```json theme={null}
  {
    "oneOf": [
      {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "The client_id returned by dynamic registration, or the exact public HTTPS URL of the client metadata document."
          },
          "resource": {
            "type": "string",
            "format": "uri",
            "description": "Required exact resource audience: use the resource value from this environment's protected-resource metadata. In production this is https://api.voicecheap.ai/mcp. A different origin, path, or trailing slash is rejected.",
            "examples": [
              "https://api.voicecheap.ai/mcp"
            ]
          },
          "grant_type": {
            "const": "authorization_code"
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "pattern": "^mcp_code_[A-Za-z0-9_-]{43}$",
            "description": "Single-use code returned to the exact registered callback."
          },
          "redirect_uri": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "Must exactly equal the callback bound to this code and registration."
          },
          "code_verifier": {
            "type": "string",
            "minLength": 43,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._~-]{43,128}$",
            "description": "Original PKCE verifier used to calculate the authorization request's S256 code_challenge."
          }
        },
        "required": [
          "grant_type",
          "client_id",
          "resource",
          "code",
          "redirect_uri",
          "code_verifier"
        ],
        "not": {
          "anyOf": [
            {
              "required": [
                "client_secret"
              ]
            },
            {
              "required": [
                "client_assertion"
              ]
            }
          ]
        },
        "additionalProperties": true
      },
      {
        "type": "object",
        "properties": {
          "client_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "The client_id returned by dynamic registration, or the exact public HTTPS URL of the client metadata document."
          },
          "resource": {
            "type": "string",
            "format": "uri",
            "description": "Required exact resource audience: use the resource value from this environment's protected-resource metadata. In production this is https://api.voicecheap.ai/mcp. A different origin, path, or trailing slash is rejected.",
            "examples": [
              "https://api.voicecheap.ai/mcp"
            ]
          },
          "grant_type": {
            "const": "refresh_token"
          },
          "refresh_token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256,
            "pattern": "^mcp_refresh_[A-Za-z0-9_-]{43}$",
            "description": "The current unconsumed refresh token from a client registered for refresh_token."
          },
          "scope": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "pattern": "^ *(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email)(?: +(?:account:read|usage:read|settings:read|settings:write|team:read|team:write|projects:read|projects:write|projects:delete|translations:write|transcription:read|transcription:write|subtitles:read|subtitles:write|tts:read|tts:write|voices:read|voices:write|images:read|images:write|thumbnails:read|thumbnails:write|folders:read|folders:write|glossary:read|glossary:write|webhooks:read|webhooks:write|publishing:read|publishing:write|offline_access|openid|email))* *$",
            "description": "Space-delimited subset of the original permissions, optionally including offline_access. Unsupported names, empty values, and added permissions are rejected; duplicates are removed. An explicit scope keeps only the listed product and identity permissions and may remove every product permission. Omit scope to retain the current grant, except identity permissions are dropped if OpenID Connect is unavailable.",
            "examples": [
              "account:read projects:read offline_access"
            ]
          }
        },
        "required": [
          "grant_type",
          "client_id",
          "resource",
          "refresh_token"
        ],
        "not": {
          "anyOf": [
            {
              "required": [
                "client_secret"
              ]
            },
            {
              "required": [
                "client_assertion"
              ]
            }
          ]
        },
        "additionalProperties": true
      }
    ]
  }
  ```
</Accordion>

## 请求示例

请遵循此路由文档规定的编码和身份验证方式。操作 API 的封装格式和幂等性规则仅适用于操作调用。

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST 'https://api.voicecheap.ai/oauth/token' \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'grant_type=authorization_code' \
    --data-urlencode 'client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    --data-urlencode 'code=mcp_code_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC' \
    --data-urlencode 'redirect_uri=https://assistant.example.com/oauth/callback' \
    --data-urlencode 'code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk' \
    --data-urlencode 'resource=https://api.voicecheap.ai/mcp'
  ```

  ```javascript JavaScript theme={null}
  const input = {
    grant_type: 'authorization_code',
    client_id: 'mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
    code: 'mcp_code_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC',
    redirect_uri: 'https://assistant.example.com/oauth/callback',
    code_verifier: 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk',
    resource: 'https://api.voicecheap.ai/mcp',
  };
  const response = await fetch('https://api.voicecheap.ai/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams(input),
    redirect: 'manual',
  });
  console.log(response.status, await response.text());
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.request(
      "POST",
      "https://api.voicecheap.ai/oauth/token",
      headers={"Content-Type":"application/x-www-form-urlencoded"},
      data={"grant_type": "authorization_code", "client_id": "mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "code": "mcp_code_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", "redirect_uri": "https://assistant.example.com/oauth/callback", "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", "resource": "https://api.voicecheap.ai/mcp"},
      timeout=120,
      allow_redirects=False,
  )
  print(response.status_code, response.text)
  ```
</RequestExample>

## 响应

不透明的 Bearer 访问令牌、剩余有效期（秒）、已授予的范围，以及客户端注册了 refresh\_token 时返回的刷新令牌。expires\_in 最大为 900，接近授权到期时可能更小。

```json HTTP 200 theme={null}
{
  "access_token": "mcp_access_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "mcp_refresh_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
  "scope": "account:read projects:read offline_access"
}
```

## 响应头

```json theme={null}
{
  "Cache-Control": "no-store",
  "Pragma": "no-cache"
}
```

## 操作说明

* 受支持的标量参数必须仅提供一次，值须为不含 ASCII 控制字符或 DEL 的非空字符串。重复的查询或表单字段会解码为数组，在按标量读取时会被拒绝。
* 使用 URL 编码的表单数据，例如为每个字段使用 curl --data-urlencode。JSON 格式的令牌请求体会被拒绝，并返回 invalid\_request。
* 两种授权类型都必须提供 resource 受众值，且必须与签发授权码或刷新令牌的环境的 MCP URL 完全匹配。
* 兑换授权码需要原始 client\_id、完全一致的 redirect\_uri，以及匹配的 S256 code\_verifier。授权码在 2 分钟后过期，且仅能使用一次。
* 令牌端点拒绝所有传入的 Authorization 请求头，包括 Basic 和 Bearer，以及请求体中的任何 client\_secret 或 client\_assertion 字段。
* 访问令牌最长有效 15 分钟。注册了 refresh\_token 时，连接及其刷新令牌族会在用户批准后 30 天到期；轮换不会延长该期限。
* 是否返回 refresh\_token 取决于已注册的授权类型，与授权请求中是否包含 offline\_access 无关。只要返回刷新令牌，返回的 scope 就会追加 offline\_access。
* 续期时，发送 grant\_type=refresh\_token，并提供 client\_id、当前 refresh\_token、resource 及可选的缩小范围后的 scope。以原子方式用新值替换存储的刷新令牌。
* 重复使用已消耗的授权码或已轮换的刷新令牌，会撤销整个连接，包括其所有访问令牌。不要并发兑换同一个授权码或刷新令牌。
* 刷新请求不会添加权限。显式指定 scope 时，仅保留请求的产品和身份权限；offline\_access 不会保留被省略的产品权限。
* 令牌绑定到授予权限的用户、客户端、资源受众、当前团队所有权和有效付费访问权限。授权被撤销或所有权变更后，需要重新连接。
* 公开令牌端点的限制为每个客户端 IP 每分钟 60 次请求，由所有工作进程共用。URL 编码的请求体上限为 32 KiB。
* OAuth 失败时会返回 error 和 error\_description。请求体解析失败可能更早以集成解析器的错误格式返回。
* openid 和 email 是独立于产品权限的身份权限，仅在公布支持时接受。email 需要 openid。在首次授权时，仅包含身份权限的请求会使用同意页面显示的默认产品权限。
* 动态注册返回的 client\_id，或客户端元数据文档的精确公开 HTTPS URL。
* 授予 openid 后，授权码交换还会返回 RS256 签名的 id\_token。请验证签名、颁发者、受众、过期时间，以及已提供的 nonce。已授予的身份权限会出现在令牌响应的 scope 中；刷新响应不会签发新的 id\_token。
* 如果 OpenID Connect 变为不可用，未指定 scope 的刷新会保留产品权限并移除身份权限。显式请求不可用的身份权限会返回 invalid\_scope。
* 如果账户已被删除或停用，令牌端点会返回 HTTP 400 和 invalid\_grant。

## 错误

| HTTP 状态 | 代码 | 含义或恢复方式 |
| - | - | - |
| 400 | `invalid_request` | 请求体或已识别的参数无效、缺失、重复或编码错误。 |
| 400 | `invalid_client` | client\_id 未知、提供了客户端 HTTP 身份验证，或提供了 client\_secret/client\_assertion。 |
| 400 | `invalid_target` | 缺少资源 audience，或其与 MCP 资源不完全匹配。 |
| 400 | `unsupported_grant_type` | 授权类型不受支持，或此客户端未注册 refresh\_token。 |
| 400 | `invalid_grant` | 授权码或令牌无效、已过期、已撤销、被重复使用、不匹配，或已不再获准用于当前账户/团队。 |
| 400 | `invalid_scope` | 刷新请求试图增加权限，或包含不受支持的权限范围。 |
| 413 | `REQUEST_TOO_LARGE` | 请求超出允许的请求体大小。JSON 上限为 1 MiB，URL 编码表单上限为 32 KiB。 |
| 415 | `UNSUPPORTED_CONTENT_TYPE` | 集成请求体解析器仅接受 JSON 或 URL 编码表单。具体路由可能要求使用其中一种编码。 |
| 429 | `temporarily_unavailable` | 已达到公开 OAuth 请求速率上限。请等待当前时间窗口结束后重试。 |
| 500 | `server_error` | 授权暂时不可用。请稍后重试。 |


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