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

# Register an MCP OAuth client

> Register a public client with exact callback URLs and a bounded set of product permissions.

Register a public client with exact callback URLs and a bounded set of product permissions.

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

## Authentication

None. Registration does not sign in a user or authorize an account connection.

## Request body

<ParamField body="redirect_uris" type="array" required>
  Between 1 and 10 callbacks before duplicates are removed.

  Minimum items: 1. Maximum items: 10.
</ParamField>

<ParamField body="client_name" type="string">
  Optional client label; trimmed before saving and must remain nonempty.

  Minimum length: 1. Maximum length: 120. Pattern: `^(?=.*\S)[^\u0000-\u001F\u007F]+$`. Default: "MCP client".
</ParamField>

<ParamField body="token_endpoint_auth_method" type="field">
  Public clients only.

  Required value: "none". Default: "none".
</ParamField>

<ParamField body="grant_types" type="union">
  Must include authorization\_code; refresh\_token is optional. Omitted or null selects both. Duplicate values are removed.

  Default: \["authorization\_code","refresh\_token"].
</ParamField>

<ParamField body="response_types" type="array">
  List of values. See the complete input schema for its item structure.

  Minimum items: 1. Maximum items: 1. Default: \["code"].
</ParamField>

<ParamField body="scope" type="string">
  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, registers all supported product scopes. email requires openid; email alone is rejected. When OpenID Connect is available, registration includes openid and email as client capabilities even when scope is omitted. Registration grants no access; identity permissions require an explicit authorization request and user consent.

  Minimum length: 1. Maximum length: 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))* *$`.
</ParamField>

<Accordion title="Technical schema">
  ```json theme={null}
  {
    "type": "object",
    "properties": {
      "redirect_uris": {
        "type": "array",
        "minItems": 1,
        "maxItems": 10,
        "items": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2048,
          "pattern": "^[^\\s\\u0000-\\u001F\\u007F-\\u009F#\\\\]+$",
          "format": "uri",
          "anyOf": [
            {
              "pattern": "^[Hh][Tt][Tt][Pp][Ss]://"
            },
            {
              "pattern": "^http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]{1,5})?(?:/|\\?|$)"
            },
            {
              "const": "cursor://anysphere.cursor-mcp/oauth/callback"
            }
          ],
          "description": "An absolute HTTPS callback, an HTTP callback using the exact lowercase loopback spelling localhost, 127.0.0.1, or [::1], or exactly cursor://anysphere.cursor-mcp/oauth/callback. Must parse as a URL with no username/password, whitespace, control characters, backslashes, or fragment. A loopback port must be a valid URL port. The exact spelling is retained."
        },
        "description": "Between 1 and 10 callbacks before duplicates are removed."
      },
      "client_name": {
        "type": "string",
        "minLength": 1,
        "maxLength": 120,
        "pattern": "^(?=.*\\S)[^\\u0000-\\u001F\\u007F]+$",
        "default": "MCP client",
        "description": "Optional client label; trimmed before saving and must remain nonempty."
      },
      "token_endpoint_auth_method": {
        "const": "none",
        "default": "none",
        "description": "Public clients only."
      },
      "grant_types": {
        "anyOf": [
          {
            "type": "array",
            "minItems": 1,
            "items": {
              "enum": [
                "authorization_code",
                "refresh_token"
              ]
            },
            "contains": {
              "const": "authorization_code"
            }
          },
          {
            "type": "null"
          }
        ],
        "default": [
          "authorization_code",
          "refresh_token"
        ],
        "description": "Must include authorization_code; refresh_token is optional. Omitted or null selects both. Duplicate values are removed."
      },
      "response_types": {
        "type": "array",
        "minItems": 1,
        "maxItems": 1,
        "items": {
          "const": "code"
        },
        "default": [
          "code"
        ]
      },
      "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, registers all supported product scopes. email requires openid; email alone is rejected. When OpenID Connect is available, registration includes openid and email as client capabilities even when scope is omitted. Registration grants no access; identity permissions require an explicit authorization request and user consent.",
        "examples": [
          "account:read projects:read offline_access"
        ]
      }
    },
    "required": [
      "redirect_uris"
    ],
    "additionalProperties": true
  }
  ```
</Accordion>

## Request examples

Follow the documented encoding and authentication for this route. The action API's envelope and idempotency rules apply only to action invocations.

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST 'https://api.voicecheap.ai/oauth/register' \
    --header 'Content-Type: application/json' \
    --data '{"client_name":"Acme assistant","redirect_uris":["https://assistant.example.com/oauth/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"scope":"account:read projects:read offline_access"}'
  ```

  ```javascript JavaScript theme={null}
  const input = {
    client_name: 'Acme assistant',
    redirect_uris: ['https://assistant.example.com/oauth/callback'],
    token_endpoint_auth_method: 'none',
    grant_types: ['authorization_code', 'refresh_token'],
    response_types: ['code'],
    scope: 'account:read projects:read offline_access',
  };
  const response = await fetch('https://api.voicecheap.ai/oauth/register', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(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/register",
      headers={"Content-Type":"application/json"},
      json={"client_name": "Acme assistant", "redirect_uris": ["https://assistant.example.com/oauth/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "account:read projects:read offline_access"},
      timeout=120,
      allow_redirects=False,
  )
  print(response.status_code, response.text)
  ```
</RequestExample>

## Response

Registered public-client metadata. Save client\_id and use it with the exact registered redirect\_uri. No client\_secret is issued.

```json HTTP 201 theme={null}
{
  "client_id": "mcp_client_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  "client_id_issued_at": 1792152000,
  "client_name": "Acme assistant",
  "redirect_uris": ["https://assistant.example.com/oauth/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "account:read projects:read offline_access"
}
```

## Response headers

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

## Operation notes

* Recognized scalar parameters must be supplied once as nonempty strings without ASCII control characters or DEL. Repeated query/form fields decode to arrays and are rejected when read as scalars.
* Use Content-Type application/json. A URL-encoded registration body is rejected with the OAuth invalid\_request response; unsupported media types may be rejected earlier by the integration parser.
* Redirect URIs are retained and compared exactly. Changes to case, port spelling, path, query, or trailing slash require registering that exact URI.
* HTTP callbacks accept only the raw loopback names localhost, 127.0.0.1, and \[::1]. Private-network hosts, alternate IP spellings, \*.localhost, and uppercase LOCALHOST do not qualify.
* The only accepted custom URI scheme callback is exactly cursor://anysphere.cursor-mcp/oauth/callback.
* Every callback must pass validation; one invalid callback rejects the entire registration. URL credentials, whitespace, controls, fragments, and backslashes are rejected.
* The client may register any supported product scopes, but the user must separately approve the requested subset during authorization. Registration alone grants no access.
* Only authorization\_code and optional refresh\_token grants are supported. A client registered without refresh\_token receives no refresh token.
* Unknown top-level metadata fields are ignored by the current implementation. Known fields keep the constraints shown in the schema.
* The public registration limit is 10 requests per 10 minutes per client IP, shared across workers. JSON bodies are limited to 1 MiB.
* Successful registration returns HTTP 201.
* openid and email are identity permissions, separate from product permissions, and are accepted only when advertised. email requires openid. During initial authorization, a request containing only identity scopes uses the default product permissions shown on the consent screen.

## Errors

| HTTP status | Code | Meaning or recovery |
| - | - | - |
| 400 | `invalid_request` | The body or a recognized parameter is invalid, missing, duplicated, or has the wrong encoding. |
| 400 | `invalid_client_metadata` | Invalid callback count, blank client label, unsupported client authentication, grant types, or response types. |
| 400 | `invalid_redirect_uri` | A callback URL uses an unsupported or unsafe form. |
| 400 | `invalid_scope` | The scope string contains an unsupported permission or no requested scopes. |
| 413 | `REQUEST_TOO_LARGE` | The request exceeds the allowed body size. JSON is limited to 1 MiB and URL-encoded forms to 32 KiB. |
| 415 | `UNSUPPORTED_CONTENT_TYPE` | The integration body parser accepts JSON or URL-encoded forms only. The route may require one of those encodings specifically. |
| 429 | `temporarily_unavailable` | The public OAuth request rate limit was reached. Wait for the current window before retrying. |
| 500 | `server_error` | Authorization is temporarily unavailable. Retry later. |


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