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'
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());
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)
OAuth e protocolo MCP
Trocar um código de autorização ou rotacionar um refresh token
Obtenha tokens de acesso MCP de curta duração usando o código de autorização S256 de um cliente público ou seu token de atualização atual.
POST
/
oauth
/
token
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'
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());
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)
Obtenha tokens de acesso MCP de curta duração usando o código de autorização S256 de um cliente público ou seu token de atualização atual.
POST https://api.voicecheap.ai/oauth/token
Autenticação
Sem autenticação HTTP do cliente. Envie client_id no corpo codificado em URL. Qualquer cabeçalho Authorization, client_secret ou client_assertion é rejeitado.Corpo da requisição
Mostrar grant_type: authorization_code
Mostrar grant_type: authorization_code
string
obrigatório
O client_id retornado pelo registro dinâmico ou a URL HTTPS pública exata do documento de metadados do cliente.Comprimento mínimo: 1. Comprimento máximo: 2048. Padrão:
^[^\u0000-\u001F\u007F]+$.string
obrigatório
Público-alvo exato exigido para o recurso: use o valor resource dos metadados de recurso protegido deste ambiente. Em produção, é https://api.voicecheap.ai/mcp. Uma origem, caminho ou barra final diferente é rejeitada.Formato: “uri”.
field
obrigatório
Valor definido pelo esquema de entrada completo.Valor obrigatório: “authorization_code”.
string
obrigatório
Código de uso único retornado ao callback exato registrado.Comprimento mínimo: 1. Comprimento máximo: 256. Padrão:
^mcp_code_[A-Za-z0-9_-]{43}$.string
obrigatório
Deve corresponder exatamente ao callback vinculado a este código e registro.Comprimento mínimo: 1. Comprimento máximo: 2048. Padrão:
^[^\u0000-\u001F\u007F]+$.string
obrigatório
Verificador PKCE original usado para calcular o code_challenge S256 da requisição de autorização.Comprimento mínimo: 43. Comprimento máximo: 128. Padrão:
^[A-Za-z0-9._~-]{43,128}$.Mostrar grant_type: refresh_token
Mostrar grant_type: refresh_token
string
obrigatório
O client_id retornado pelo registro dinâmico ou a URL HTTPS pública exata do documento de metadados do cliente.Comprimento mínimo: 1. Comprimento máximo: 2048. Padrão:
^[^\u0000-\u001F\u007F]+$.string
obrigatório
Público-alvo exato exigido para o recurso: use o valor resource dos metadados de recurso protegido deste ambiente. Em produção, é https://api.voicecheap.ai/mcp. Uma origem, caminho ou barra final diferente é rejeitada.Formato: “uri”.
field
obrigatório
Valor definido pelo esquema de entrada completo.Valor obrigatório: “refresh_token”.
string
obrigatório
O refresh token atual ainda não consumido de um cliente registrado para refresh_token.Comprimento mínimo: 1. Comprimento máximo: 256. Padrão:
^mcp_refresh_[A-Za-z0-9_-]{43}$.string
Subconjunto das permissões originais separado por espaços, que pode incluir offline_access. Nomes não suportados, valores vazios e permissões adicionadas são rejeitados; duplicatas são removidas. Um scope explícito mantém apenas as permissões de produto e identidade listadas e pode remover todas as permissões de produto. Omita scope para manter a autorização atual, exceto as permissões de identidade, que são removidas se OpenID Connect estiver indisponível.Comprimento mínimo: 1. Comprimento máximo: 2048. Padrão:
^ *(?: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))* *$.Esquema técnico
Esquema técnico
{
"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
}
]
}
Exemplos de requisição
Siga a codificação e a autenticação documentadas para esta rota. O formato de resposta e as regras de idempotência da API de ações se aplicam apenas às chamadas de ações.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'
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());
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)
Resposta
Um token de acesso Bearer opaco, seu tempo de vida restante em segundos, os escopos concedidos e um refresh token quando o cliente registrou refresh_token. expires_in é de no máximo 900 e pode ser menor perto da expiração da concessão.HTTP 200
{
"access_token": "mcp_access_DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "mcp_refresh_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
"scope": "account:read projects:read offline_access"
}
Cabeçalhos da resposta
{
"Cache-Control": "no-store",
"Pragma": "no-cache"
}
Notas da operação
- Parâmetros escalares reconhecidos devem ser fornecidos uma única vez como strings não vazias, sem caracteres de controle ASCII ou DEL. Campos de consulta/formulário repetidos são decodificados como arrays e rejeitados quando lidos como escalares.
- Use dados de formulário codificados em URL, como curl —data-urlencode por campo. Um corpo de token JSON é rejeitado com invalid_request.
- O público-alvo resource é obrigatório para ambos os tipos de concessão e deve corresponder exatamente à URL MCP do ambiente que emitiu o código ou token de atualização.
- A troca do código de autorização exige o client_id original, o redirect_uri exato e um code_verifier S256 correspondente. Os códigos expiram após 2 minutos e são de uso único.
- O endpoint de token rejeita qualquer cabeçalho Authorization fornecido, incluindo Basic e Bearer, além de qualquer campo client_secret ou client_assertion no corpo.
- Tokens de acesso duram no máximo 15 minutos. Com refresh_token registrado, a conexão e a família de refresh tokens expiram 30 dias após a aprovação do usuário; a rotação não estende esse prazo.
- O retorno de refresh_token depende dos tipos de concessão registrados. Não depende da inclusão de offline_access na requisição de autorização. O scope retornado acrescenta offline_access sempre que um token de atualização é retornado.
- Para renovar, envie grant_type=refresh_token com client_id, o refresh_token atual, resource e, opcionalmente, um scope mais restrito. Substitua atomicamente o refresh token armazenado pelo novo valor.
- Reutilizar um código de autorização consumido ou um token de atualização rotacionado revoga toda a conexão, incluindo todos os seus tokens de acesso. Não troque o mesmo código ou token de atualização simultaneamente.
- Solicitações de renovação nunca adicionam permissões. Com um scope explícito, apenas as permissões de produto e identidade solicitadas são mantidas; offline_access não preserva permissões de produto omitidas.
- Os tokens são vinculados ao usuário que concedeu acesso, ao cliente, ao público-alvo do recurso, à propriedade atual da equipe e ao acesso pago ativo. Uma concessão revogada ou uma mudança de propriedade requer reconexão.
- O limite público de tokens é de 60 requisições por minuto por IP de cliente, compartilhado entre workers. Corpos codificados em URL são limitados a 32 KiB.
- Falhas OAuth retornam error e error_description. Falhas no Body-parser podem ser retornadas antes no formato de erro do parser de integração.
- openid e email são permissões de identidade, separadas das permissões do produto, aceitas apenas quando anunciadas. email exige openid. Na autorização inicial, uma solicitação apenas com escopos de identidade usa as permissões padrão do produto mostradas na tela de consentimento.
- O client_id retornado pelo registro dinâmico ou a URL HTTPS pública exata do documento de metadados do cliente.
- Quando openid é concedido, a troca do código de autorização também retorna um id_token assinado com RS256. Valide assinatura, emissor, público, expiração e nonce, se fornecido. Os escopos de identidade concedidos aparecem em scope da resposta; a renovação não emite um novo id_token.
- Se OpenID Connect ficar indisponível, renovar sem scope mantém as permissões de produto e remove as de identidade. Solicitar explicitamente escopos de identidade indisponíveis retorna invalid_scope.
- Se a conta tiver sido excluída ou desativada, o endpoint de tokens retorna invalid_grant com HTTP 400.
Erros
| Status HTTP | Código | Significado ou recuperação |
|---|---|---|
| 400 | invalid_request | O corpo ou um parâmetro reconhecido é inválido, está ausente, está duplicado ou tem a codificação errada. |
| 400 | invalid_client | O client_id é desconhecido, a autenticação HTTP do cliente foi fornecida ou um client_secret/client_assertion foi fornecido. |
| 400 | invalid_target | O público-alvo do recurso está ausente ou não corresponde exatamente ao recurso MCP. |
| 400 | unsupported_grant_type | A concessão não é compatível, ou refresh_token não foi registrado para este cliente. |
| 400 | invalid_grant | O código ou token é inválido, expirou, foi revogado, repetido, não corresponde ou não está mais autorizado para a conta/equipe atual. |
| 400 | invalid_scope | Uma requisição de atualização tenta adicionar permissões ou contém escopos não compatíveis. |
| 413 | REQUEST_TOO_LARGE | A requisição excede o tamanho permitido do corpo. JSON é limitado a 1 MiB e formulários codificados em URL a 32 KiB. |
| 415 | UNSUPPORTED_CONTENT_TYPE | O parser de corpo da integração aceita apenas JSON ou formulários codificados em URL. A rota pode exigir especificamente uma dessas codificações. |
| 429 | temporarily_unavailable | O limite de requisições OAuth públicas foi atingido. Aguarde a janela atual antes de tentar novamente. |
| 500 | server_error | A autorização está temporariamente indisponível. Tente novamente mais tarde. |
Esta página foi útil?

