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 y protocolo MCP
Intercambiar un código de autorización o rotar un token de actualización
Obtén tokens de acceso MCP de corta duración usando el código de autorización S256 de un cliente público o su token de actualización actual.
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)
Obtén tokens de acceso MCP de corta duración usando el código de autorización S256 de un cliente público o su token de actualización actual.
POST https://api.voicecheap.ai/oauth/token
Autenticación
Sin autenticación HTTP del cliente. Envía client_id en el cuerpo codificado como URL. Se rechaza cualquier cabecera Authorization, client_secret o client_assertion.Cuerpo de la solicitud
Mostrar grant_type: authorization_code
Mostrar grant_type: authorization_code
string
requerido
El client_id devuelto por el registro dinámico o la URL HTTPS pública exacta del documento de metadatos del cliente.Longitud mínima: 1. Longitud máxima: 2048. Patrón:
^[^\u0000-\u001F\u007F]+$.string
requerido
Audiencia exacta del recurso requerida: usa el valor resource de los metadatos del recurso protegido de este entorno. En producción es https://api.voicecheap.ai/mcp. Se rechaza un origen, una ruta o una barra final diferente.Formato: “uri”.
field
requerido
Valor definido por el esquema completo de entrada.Valor obligatorio: “authorization_code”.
string
requerido
Código de un solo uso devuelto a la URL de retorno registrada exacta.Longitud mínima: 1. Longitud máxima: 256. Patrón:
^mcp_code_[A-Za-z0-9_-]{43}$.string
requerido
Debe ser exactamente igual al callback vinculado a este código y registro.Longitud mínima: 1. Longitud máxima: 2048. Patrón:
^[^\u0000-\u001F\u007F]+$.string
requerido
Verificador PKCE original utilizado para calcular el code_challenge S256 de la solicitud de autorización.Longitud mínima: 43. Longitud máxima: 128. Patrón:
^[A-Za-z0-9._~-]{43,128}$.Mostrar grant_type: refresh_token
Mostrar grant_type: refresh_token
string
requerido
El client_id devuelto por el registro dinámico o la URL HTTPS pública exacta del documento de metadatos del cliente.Longitud mínima: 1. Longitud máxima: 2048. Patrón:
^[^\u0000-\u001F\u007F]+$.string
requerido
Audiencia exacta del recurso requerida: usa el valor resource de los metadatos del recurso protegido de este entorno. En producción es https://api.voicecheap.ai/mcp. Se rechaza un origen, una ruta o una barra final diferente.Formato: “uri”.
field
requerido
Valor definido por el esquema completo de entrada.Valor obligatorio: “refresh_token”.
string
requerido
El token de actualización actual sin consumir de un cliente registrado para refresh_token.Longitud mínima: 1. Longitud máxima: 256. Patrón:
^mcp_refresh_[A-Za-z0-9_-]{43}$.string
Subconjunto de los permisos originales separado por espacios, que puede incluir offline_access. Se rechazan nombres no admitidos, valores vacíos y permisos añadidos; se eliminan duplicados. Un scope explícito conserva solo los permisos de producto e identidad indicados y puede eliminar todos los permisos del producto. Omite scope para conservar la autorización actual, salvo los permisos de identidad, que se eliminan si OpenID Connect no está disponible.Longitud mínima: 1. Longitud máxima: 2048. Patrón:
^ *(?: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
}
]
}
Ejemplos de solicitud
Sigue la codificación y la autenticación documentadas para esta ruta. El formato envolvente y las reglas de idempotencia de la API de acciones solo se aplican a las invocaciones de acciones.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)
Respuesta
Un token de acceso Bearer opaco, su tiempo de validez restante en segundos, los ámbitos concedidos y un token de actualización cuando el cliente haya registrado refresh_token. expires_in es como máximo 900 y puede ser menor cerca de la caducidad de la concesión.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"
}
Cabeceras de respuesta
{
"Cache-Control": "no-store",
"Pragma": "no-cache"
}
Notas de la operación
- Los parámetros escalares reconocidos deben proporcionarse una sola vez como cadenas no vacías, sin caracteres de control ASCII ni DEL. Los campos repetidos de consulta o formulario se decodifican como arrays y se rechazan al leerse como escalares.
- Usa datos de formulario codificados como URL, por ejemplo curl —data-urlencode por campo. Un cuerpo de token JSON se rechaza con invalid_request.
- El destinatario resource es obligatorio para ambos tipos de concesión y debe coincidir exactamente con la URL MCP del entorno que emitió el código o el token de actualización.
- El canje del código de autorización requiere el client_id original, el redirect_uri exacto y un code_verifier S256 coincidente. Los códigos caducan tras 2 minutos y son de un solo uso.
- El endpoint de tokens rechaza todas las cabeceras Authorization proporcionadas, incluidas Basic y Bearer, así como cualquier campo client_secret o client_assertion en el cuerpo.
- Los tokens de acceso duran como máximo 15 minutos. Si refresh_token está registrado, la conexión y la familia de tokens de actualización caducan 30 días después de la aprobación del usuario; la rotación no amplía ese plazo.
- La devolución de refresh_token depende de los tipos de concesión registrados. No depende de incluir offline_access en la solicitud de autorización. El scope devuelto añade offline_access siempre que se devuelve un token de actualización.
- Para renovar, envía grant_type=refresh_token con client_id, el refresh_token actual, resource y, opcionalmente, un scope más restringido. Sustituye de forma atómica el token de actualización almacenado por el nuevo valor.
- Reutilizar un código de autorización consumido o un token de actualización rotado revoca toda la conexión, incluidos todos sus tokens de acceso. No canjees simultáneamente el mismo código o token de actualización.
- Las solicitudes de renovación nunca añaden permisos. Con un scope explícito solo se conservan los permisos de producto e identidad solicitados; offline_access no conserva los permisos de producto omitidos.
- Los tokens están vinculados al usuario que concede el acceso, al cliente, a la audiencia del recurso, a la propiedad actual del equipo y al acceso de pago activo. Una concesión revocada o un cambio de propiedad requiere volver a conectar.
- El límite público de tokens es de 60 solicitudes por minuto y por IP de cliente, compartido entre los workers. Los cuerpos codificados como URL están limitados a 32 KiB.
- Los errores de OAuth devuelven error y error_description. Los errores del analizador del cuerpo pueden devolverse antes con el formato de error del analizador de integración.
- openid y email son permisos de identidad, distintos de los permisos del producto, y solo se aceptan cuando se anuncian. email requiere openid. Durante la autorización inicial, una solicitud que solo contiene permisos de identidad usa los permisos predeterminados del producto mostrados en la pantalla de consentimiento.
- El client_id devuelto por el registro dinámico o la URL HTTPS pública exacta del documento de metadatos del cliente.
- Cuando se concede openid, el intercambio del código de autorización también devuelve un id_token firmado con RS256. Valida su firma, emisor, audiencia, caducidad y nonce si se proporcionó. Los permisos de identidad concedidos aparecen en scope de la respuesta; la renovación no emite un id_token nuevo.
- Si OpenID Connect deja de estar disponible, renovar sin scope conserva los permisos del producto y elimina los de identidad. Solicitar explícitamente permisos de identidad no disponibles devuelve invalid_scope.
- Si la cuenta fue eliminada o desactivada, el endpoint de tokens devuelve invalid_grant con HTTP 400.
Errores
| Estado HTTP | Código | Significado o solución |
|---|---|---|
| 400 | invalid_request | El cuerpo o un parámetro reconocido no es válido, falta, está duplicado o tiene una codificación incorrecta. |
| 400 | invalid_client | El client_id es desconocido, se ha proporcionado autenticación HTTP de cliente o se ha proporcionado client_secret/client_assertion. |
| 400 | invalid_target | Falta la audiencia del recurso o no coincide exactamente con el recurso MCP. |
| 400 | unsupported_grant_type | La concesión no es compatible o refresh_token no se registró para este cliente. |
| 400 | invalid_grant | El código o token no es válido, ha caducado, ha sido revocado, ya se ha usado, no coincide o ya no está autorizado para la cuenta o el equipo actuales. |
| 400 | invalid_scope | Una solicitud de actualización intenta añadir permisos o contiene ámbitos no compatibles. |
| 413 | REQUEST_TOO_LARGE | La solicitud supera el tamaño de cuerpo permitido. JSON está limitado a 1 MiB y los formularios codificados como URL a 32 KiB. |
| 415 | UNSUPPORTED_CONTENT_TYPE | El analizador del cuerpo de la integración solo acepta JSON o formularios codificados como URL. La ruta puede exigir específicamente una de esas codificaciones. |
| 429 | temporarily_unavailable | Se ha alcanzado el límite público de frecuencia de solicitudes OAuth. Espera a que termine la ventana actual antes de reintentarlo. |
| 500 | server_error | La autorización no está disponible temporalmente. Vuelve a intentarlo más tarde. |
¿Esta página le ayudó?

