DocsAPI

OAuth for connectors

How Claude.ai and other MCP clients that cannot hold an API key connect to VortexDeployer through OAuth, and how a grant is tied to one of your keys.

Some MCP clients, like Claude.ai custom connectors, cannot send a fixed API key. They sign in with OAuth instead. OAuth is a standard way for an app to get access to your account without ever seeing your password.

VortexDeployer runs an OAuth 2.1 server for these clients. It uses the authorization code flow with PKCE. At the end, the client holds an access token that starts with vdo_. It works anywhere an API key works: the MCP server, every REST route and the live streams.

WhatAddress
Issuerhttps://api.vortexdeployer.com/api/v1
Server info (RFC 8414)https://api.vortexdeployer.com/.well-known/oauth-authorization-server
Resource info (RFC 9728)https://api.vortexdeployer.com/.well-known/oauth-protected-resource
Register a client (RFC 7591)POST https://api.vortexdeployer.com/api/v1/oauth/register
Ask for accessGET https://api.vortexdeployer.com/api/v1/oauth/authorize
Get tokensPOST https://api.vortexdeployer.com/api/v1/oauth/token
Revoke a token (RFC 7009)POST https://api.vortexdeployer.com/api/v1/oauth/revoke

The two .well-known files live at the root of the API host, not under /api/v1. They also answer with the resource path added at the end, like https://api.vortexdeployer.com/.well-known/oauth-protected-resource/api/v1/mcp. Copies answer under https://api.vortexdeployer.com/api/v1/oauth/.well-known/… too.

One grant, one key#

When you approve a client, you pick one of your API keys. The grant is tied to that key. The client's token then acts as that key in every way:

  • It has the key's scopes, its max per buy, its daily cap, its wallet list and its IP list.
  • It shares the key's rate limits.
  • Its calls show under that key on the API page, Usage tab.
  • Revoke the key and every token made from it stops working on its next call.

OAuth has no scopes of its own here. A scope value in the request is ignored.

Give the agent a small key

The agent spends real money as the key allows. Make a new key for it first. Use a read-only key to look around, or a trade key with a low max per buy and a daily cap. See API overview & keys.

Connect Claude.ai#

  1. In the app, open API and make a key for the agent.
  2. In Claude.ai, add a custom connector with the address https://api.vortexdeployer.com/api/v1/mcp.
  3. Claude registers itself and sends you to the Connect an AI agent page. Sign in if asked.
  4. The page names the app that asks and the site your browser returns to. Under Act as this API key, pick the key.
  5. Click Approve as key name. To refuse, click Deny.

Claude's tools now act as that key. To end access, remove the connector in Claude.ai or revoke the key in the app.

If you have no active key, the page says No active API keys. Make one in the app, then connect again.

Build an OAuth client#

This part is for developers of MCP clients. The flow is plain OAuth 2.1, so the OAuth helpers in the MCP SDKs work as they are.

1. Find the server#

Call POST https://api.vortexdeployer.com/api/v1/mcp with no token. The 401 answer has a header that points to the resource info:

WWW-Authenticate: Bearer resource_metadata="https://api.vortexdeployer.com/.well-known/oauth-protected-resource"

That file names the server in authorization_servers. Then read https://api.vortexdeployer.com/.well-known/oauth-authorization-server for the addresses above. It lists S256 as the only PKCE method and none as the only client sign-in method.

2. Register once#

POST https://api.vortexdeployer.com/api/v1/oauth/register
Content-Type: application/json

{ "client_name": "My agent", "redirect_uris": ["https://myapp.example/oauth/callback"] }
  • client_name — up to 128 characters. Default MCP client.
  • redirect_uris — 1 to 16 addresses your user comes back to. Each must be https, or http on localhost, 127.0.0.1 or [::1]. No # part.

The answer is 201 with your client_id. No client secret is given. Every client is public, and PKCE proves the code is yours.

3. Send the user to approve#

Make a random code_verifier. Its code_challenge is base64url(sha256(verifier)), 43 characters. Then send the browser to:

https://api.vortexdeployer.com/api/v1/oauth/authorize?response_type=code
  &client_id=…&redirect_uri=…&state=…
  &code_challenge=…&code_challenge_method=S256
  • redirect_uri must match a registered one exactly.
  • state is optional, up to 1,024 characters. It comes back to you unchanged.
  • A bad request answers 400 on the spot. It never sends the browser to your address.

The user then sees the Connect an AI agent page in the app. It stays open for 10 minutes. On approve, the browser comes back with code and state. On deny, it comes back with error=access_denied.

4. Trade the code for tokens#

The body is form-encoded, up to 8 KB:

POST https://api.vortexdeployer.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=…&redirect_uri=…&client_id=…&code_verifier=…
{ "access_token": "vdo_…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "vdr_…" }

A code works once and lasts 2 minutes. The client, the redirect address and the verifier must all match.

5. Use and refresh#

Send Authorization: Bearer vdo_… to https://api.vortexdeployer.com/api/v1/mcp or any REST route. On a stream, put it in token on each subscribe.

The access token lasts 1 hour. Before then, get a new pair:

POST https://api.vortexdeployer.com/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=vdr_…&client_id=…

A refresh token lasts 30 days and works once. Each refresh gives a new one. If an old refresh token is sent again, the server takes that as a leak. It revokes every token of that key and client, and the user must approve again.

6. Revoke#

POST https://api.vortexdeployer.com/api/v1/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=vdo_…

It always answers 200, even for a token it does not know.

Errors#

These addresses answer in the OAuth style, not the API's usual shape:

{ "error": "invalid_grant", "error_description": "the grant is invalid, expired or already used" }
errorStatusWhen
invalid_client_metadata400The register body is wrong.
invalid_redirect_uri400A redirect address breaks the rules in step 2.
invalid_request400A field is missing or wrong, or the client or redirect address is unknown.
invalid_grant400The code or refresh token is wrong, expired, used, or sent with the wrong verifier.
temporarily_unavailable429Too many tries from your IP address. Wait a minute.

The limits per IP address and minute:

  • Register: 10 clients.
  • Ask for access: 30 requests.
  • Get tokens: failed tries share the API's budget for bad keys, 30 a minute. Over it, every call to /oauth/token gets 429 with Retry-After: 60.

See also#