curl --request GET 'https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state'
const input = {
client_id: 'mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
redirect_uri: 'https://assistant.example.com/oauth/callback',
response_type: 'code',
response_mode: 'query',
code_challenge: 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM',
code_challenge_method: 'S256',
resource: 'https://api.voicecheap.ai/mcp',
scope: 'account:read projects:read offline_access',
state: 'example-client-csrf-state',
};
const response = await fetch(
'https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state',
{
method: 'GET',
headers: {},
redirect: 'manual',
},
);
console.log(response.status, await response.text());
import os
import requests
response = requests.request(
"GET",
"https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state",
headers={},
timeout=120,
allow_redirects=False,
)
print(response.status_code, response.text)
OAuth 和 MCP 协议
开始 MCP 账户授权
创建短期有效的授权请求,并将用户重定向到 VoiceCheap 登录及权限同意页面。
GET
/
oauth
/
authorize
curl --request GET 'https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state'
const input = {
client_id: 'mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
redirect_uri: 'https://assistant.example.com/oauth/callback',
response_type: 'code',
response_mode: 'query',
code_challenge: 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM',
code_challenge_method: 'S256',
resource: 'https://api.voicecheap.ai/mcp',
scope: 'account:read projects:read offline_access',
state: 'example-client-csrf-state',
};
const response = await fetch(
'https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state',
{
method: 'GET',
headers: {},
redirect: 'manual',
},
);
console.log(response.status, await response.text());
import os
import requests
response = requests.request(
"GET",
"https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state",
headers={},
timeout=120,
allow_redirects=False,
)
print(response.status_code, response.text)
创建短期有效的授权请求,并将用户重定向到 VoiceCheap 登录及权限同意页面。
GET https://api.voicecheap.ai/oauth/authorize
身份验证
此路由无需身份验证。浏览器后续的授权同意流程要求用户登录,批准连接还要求具备符合条件的付费账户访问权限。查询参数
string
必填
动态注册返回的 client_id,或客户端元数据文档的精确公开 HTTPS URL。最小长度: 1. 最大长度: 2048. 匹配模式:
^[^\u0000-\u001F\u007F]+$.string
必填
必须与此客户端已注册的某个 redirect_uris 完全匹配。最小长度: 1. 最大长度: 2048. 匹配模式:
^[^\u0000-\u001F\u007F]+$.field
必填
由完整输入架构定义的值。指定值: “code”.
field
由完整输入架构定义的值。指定值: “query”. 默认值: “query”.
string
必填
原始 code_verifier 的 Base64url SHA-256 摘要,不含填充。最小长度: 43. 最大长度: 43. 匹配模式:
^[A-Za-z0-9_-]{43}$.field
必填
由完整输入架构定义的值。指定值: “S256”.
string
必填
必须使用完全匹配的资源受众:请使用此环境受保护资源元数据中的 resource 值。生产环境中为 https://api.voicecheap.ai/mcp。不同的源地址、路径或末尾斜杠均会被拒绝。格式: “uri”.
string
以空格分隔、选自 scopes_supported 的权限,可包含 offline_access。不支持的名称和仅含空白字符的值会被拒绝;重复项会被移除。省略 scope 或不提供任何产品权限(例如仅提供 openid 或 offline_access)时,会选择默认读取范围与此客户端已注册产品范围的交集。email 必须与 openid 一起使用;仅提供 email 会被拒绝。请求的产品权限必须已为此客户端注册。身份权限必须显式请求并由用户批准;省略 scope 不会请求任何身份权限。最小长度: 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))* *$.string
可选的不透明值,会在最终回调中原样返回。请使用新的不可预测值,并在客户端验证。最小长度: 1. 最大长度: 2048. 匹配模式:
^[^\u0000-\u001F\u007F]+$.string
可选的不透明 nonce 值,授予 openid 后会原样包含在 ID 令牌中。请在客户端验证。最小长度: 1. 最大长度: 2048. 匹配模式:
^[^\u0000-\u001F\u007F]+$.string
文本值。指定值: “consent”.
技术架构
技术架构
{
"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."
},
"redirect_uri": {
"type": "string",
"minLength": 1,
"maxLength": 2048,
"pattern": "^[^\\u0000-\\u001F\\u007F]+$",
"description": "Must exactly match one of this client's registered redirect_uris."
},
"response_type": {
"const": "code"
},
"response_mode": {
"const": "query",
"default": "query"
},
"code_challenge": {
"type": "string",
"minLength": 43,
"maxLength": 43,
"pattern": "^[A-Za-z0-9_-]{43}$",
"description": "Base64url SHA-256 digest of the original code_verifier, without padding."
},
"code_challenge_method": {
"const": "S256"
},
"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"
]
},
"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 permissions from scopes_supported, optionally including offline_access. Unsupported names and whitespace-only values are rejected; duplicates are removed. Omitting scope or supplying no product scopes, such as openid alone or offline_access alone, selects the default read scopes intersected with this client's registered product scopes. email requires openid; email alone is rejected. Requested product permissions must be registered for this client. Identity permissions must be explicitly requested and approved by the user; omitting scope requests no identity permissions.",
"examples": [
"account:read projects:read offline_access"
]
},
"state": {
"type": "string",
"minLength": 1,
"maxLength": 2048,
"pattern": "^[^\\u0000-\\u001F\\u007F]+$",
"description": "Optional opaque value echoed on the final callback. Use a fresh unpredictable value and verify it in the client."
},
"nonce": {
"type": "string",
"minLength": 1,
"maxLength": 2048,
"pattern": "^[^\\u0000-\\u001F\\u007F]+$",
"description": "Optional opaque nonce echoed in the ID token when openid is granted. Verify it in the client."
},
"prompt": {
"type": "string",
"const": "consent"
}
},
"required": [
"client_id",
"redirect_uri",
"response_type",
"code_challenge",
"code_challenge_method",
"resource"
],
"additionalProperties": true
}
请求示例
请遵循此路由文档规定的编码和身份验证方式。操作 API 的封装格式和幂等性规则仅适用于操作调用。curl --request GET 'https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state'
const input = {
client_id: 'mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
redirect_uri: 'https://assistant.example.com/oauth/callback',
response_type: 'code',
response_mode: 'query',
code_challenge: 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM',
code_challenge_method: 'S256',
resource: 'https://api.voicecheap.ai/mcp',
scope: 'account:read projects:read offline_access',
state: 'example-client-csrf-state',
};
const response = await fetch(
'https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state',
{
method: 'GET',
headers: {},
redirect: 'manual',
},
);
console.log(response.status, await response.text());
import os
import requests
response = requests.request(
"GET",
"https://api.voicecheap.ai/oauth/authorize?client_id=mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&redirect_uri=https%3A%2F%2Fassistant.example.com%2Foauth%2Fcallback&response_type=code&response_mode=query&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&resource=https%3A%2F%2Fapi.voicecheap.ai%2Fmcp&scope=account%3Aread+projects%3Aread+offline_access&state=example-client-csrf-state",
headers={},
timeout=120,
allow_redirects=False,
)
print(response.status_code, response.text)
响应
HTTP 重定向至 VoiceCheap 授权同意页面。不返回 JSON 结果体。Location 包含一个不透明的 request 值;随后用户登录并选择权限。HTTP/1.1 302 Found
Cache-Control: no-store
Pragma: no-cache
Referrer-Policy: no-referrer
Location: https://app.voicecheap.ai/mcp/authorize?request=mcp_req_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
响应头
{
"Cache-Control": "no-store",
"Pragma": "no-cache",
"Referrer-Policy": "no-referrer",
"Location": "https://app.voicecheap.ai/mcp/authorize?request=mcp_req_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
}
操作说明
- 受支持的标量参数必须仅提供一次,值须为不含 ASCII 控制字符或 DEL 的非空字符串。重复的查询或表单字段会解码为数组,在按标量读取时会被拒绝。
- 请对每个查询值进行 URL 编码。redirect_uri 必须与此客户端注册时的拼写完全一致。
- 仅使用 S256;不支持 plain PKCE 和 implicit/token 响应。请保留原始的 43–128 个字符的 code_verifier,用于令牌兑换。
- resource 参数为必填,且必须与所选环境的 MCP 资源 URL 完全一致。
- 默认产品权限范围集合为 account:read usage:read projects:read transcription:read subtitles:read tts:read voices:read images:read thumbnails:read。使用默认值的请求会收窄至客户端已注册的权限范围;若未剩余任何产品权限范围,授权将失败。
- 仅包含 offline_access 的 scope 会使用默认产品权限范围。显示或授予产品权限前,会移除 offline_access。
- 授权同意请求在 10 分钟后过期,且只能答复一次。用户只能批准所请求产品权限的非空子集。
- 批准后,客户端回调会收到 code、iss,以及最初提供的 state(如有)。授权码在 2 分钟后过期,且只能兑换一次。
- 拒绝授权后,客户端回调会收到 error=access_denied、error_description、iss,以及提供时的原始 state;不会签发 code。
- 无效的客户端元数据、重定向不匹配、PKCE、scopes 或 resource 问题会直接返回 OAuth JSON 错误;不会重定向到未经验证的回调地址。
- 公共授权接口的限流为每个客户端 IP 每分钟 30 次请求,由所有工作进程共享。
- openid 和 email 是独立于产品权限的身份权限,仅在公布支持时接受。email 需要 openid。在首次授权时,仅包含身份权限的请求会使用同意页面显示的默认产品权限。
- 动态注册返回的 client_id,或客户端元数据文档的精确公开 HTTPS URL。
- OpenID Connect 支持 prompt=consent。其他 prompt 值和 max_age 会被拒绝;不支持静默授权或强制重新登录。
错误
| HTTP 状态 | 代码 | 含义或恢复方式 |
|---|---|---|
| 400 | invalid_request | 请求体或已识别的参数无效、缺失、重复或编码错误。 |
| 400 | invalid_client | client_id 未知,或无法获取或验证客户端元数据文档。 |
| 400 | unsupported_response_type | response_type 不是 code。 |
| 400 | invalid_target | 缺少 resource,或其与该环境的 MCP URL 不完全一致。 |
| 400 | invalid_scope | 请求的某个产品权限范围不受支持、未注册,或未留下任何可用权限。 |
| 429 | temporarily_unavailable | 已达到公开 OAuth 请求速率上限。请等待当前时间窗口结束后重试。 |
| 500 | server_error | 授权暂时不可用。请稍后重试。 |
| 503 | temporarily_unavailable | 客户端元数据暂时不可用。请稍后重试。 |
此页面对您有帮助吗?

