> ## 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.

# 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.

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.

```http theme={null}
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

<Expandable title="grant_type: authorization_code">
  <ParamField body="client_id" type="string" required>
    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]+$`.
  </ParamField>

  <ParamField body="resource" type="string" required>
    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](https://api.voicecheap.ai/mcp). Se rechaza un origen, una ruta o una barra final diferente.

    Formato: "uri".
  </ParamField>

  <ParamField body="grant_type" type="field" required>
    Valor definido por el esquema completo de entrada.

    Valor obligatorio: "authorization\_code".
  </ParamField>

  <ParamField body="code" type="string" required>
    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}$`.
  </ParamField>

  <ParamField body="redirect_uri" type="string" required>
    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]+$`.
  </ParamField>

  <ParamField body="code_verifier" type="string" required>
    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}$`.
  </ParamField>
</Expandable>

<Expandable title="grant_type: refresh_token">
  <ParamField body="client_id" type="string" required>
    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]+$`.
  </ParamField>

  <ParamField body="resource" type="string" required>
    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](https://api.voicecheap.ai/mcp). Se rechaza un origen, una ruta o una barra final diferente.

    Formato: "uri".
  </ParamField>

  <ParamField body="grant_type" type="field" required>
    Valor definido por el esquema completo de entrada.

    Valor obligatorio: "refresh\_token".
  </ParamField>

  <ParamField body="refresh_token" type="string" required>
    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}$`.
  </ParamField>

  <ParamField body="scope" type="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))* *$`.
  </ParamField>
</Expandable>

<Accordion title="Esquema técnico">
  ```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>

## 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.

<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>

## 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.

```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"
}
```

## Cabeceras de respuesta

```json theme={null}
{
  "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. |


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