Skip to content
Twexapi
English
Esc
↑↓navigate↵open⌘Jpreview
On this page

OAuth for MCP clients

Reference for building an MCP client that signs users in to TwexAPI: discovery, dynamic client registration, PKCE, scopes, tokens, refresh, and revocation.

TwexAPI runs an OAuth 2.1 authorization server for its MCP server. MCP clients such as Claude, ChatGPT, and Cursor use it automatically. Read this page if you are building your own client or debugging one.

  • Authorization code flow with PKCE (S256 only).
  • Public clients only: no client secret. Register with dynamic client registration (RFC 7591).
  • Users sign in and approve on https://twexapi.io/oauth/consent with their TwexAPI account.
  • Tokens are opaque and work only on https://api.twexapi.io/mcp. They are not TwexAPI API keys and cannot call the REST API.
  • Calls are billed to the user’s credits through their API key, so the user needs an active key with credits to approve.

If you only need a script or backend to call TwexAPI, use an API key instead.

Discovery

A request to https://api.twexapi.io/mcp without credentials returns 401 with:

WWW-Authenticate: Bearer realm="twexapi", resource_metadata="https://api.twexapi.io/.well-known/oauth-protected-resource/mcp", scope="mcp:read mcp:write"

Protected resource metadata

GET https://api.twexapi.io/.well-known/oauth-protected-resource/mcp (also served at /.well-known/oauth-protected-resource):

{
  "resource": "https://api.twexapi.io/mcp",
  "authorization_servers": ["https://api.twexapi.io"],
  "bearer_methods_supported": ["header"],
  "scopes_supported": ["mcp:read", "mcp:write"],
  "resource_name": "TwexAPI"
}

Authorization server metadata

GET https://api.twexapi.io/.well-known/oauth-authorization-server:

{
  "issuer": "https://api.twexapi.io",
  "authorization_endpoint": "https://api.twexapi.io/oauth/authorize",
  "token_endpoint": "https://api.twexapi.io/oauth/token",
  "registration_endpoint": "https://api.twexapi.io/oauth/register",
  "revocation_endpoint": "https://api.twexapi.io/oauth/revoke",
  "response_types_supported": ["code"],
  "response_modes_supported": ["query"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "revocation_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["mcp:read", "mcp:write"],
  "authorization_response_iss_parameter_supported": true
}

Scopes

Scope Grants
mcp:read explore and twexapi_request (read-only endpoints). Always granted.
mcp:write twexapi_write (posting tweets and replies). The user can decline it on the consent page.

Request mcp:read mcp:write to offer both. Unknown scopes are ignored, and a request with no recognized scope asks for both. The token response’s scope field tells you what the user granted.

1. Register the client

POST https://api.twexapi.io/oauth/register
Content-Type: application/json

{
  "client_name": "My Agent",
  "redirect_uris": ["http://127.0.0.1:53682/callback"]
}
{
  "client_id": "twx_client_...",
  "client_id_issued_at": 1792600000,
  "client_name": "My Agent",
  "redirect_uris": ["http://127.0.0.1:53682/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
Field Rules
redirect_uris Required. 1 to 10 URIs, each one of: https://; http:// on localhost, 127.0.0.1, or [::1]; or a private-use scheme such as cursor://. No fragments or user info.
client_name Optional, up to 100 characters. Shown on the consent page and in the user’s Authorized apps list.
token_endpoint_auth_method Optional. Only none is accepted.
grant_types Optional. A subset of authorization_code and refresh_token.
response_types Optional. Only code.

Registration is limited to 30 clients per IP address per hour. A client registration lasts 180 days and is extended every time a user starts an authorization with it, so register once and reuse the client_id.

2. Create a PKCE pair

Node.js:

import crypto from "node:crypto";

const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");

Python:

import base64, hashlib, secrets

verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()

The verifier must be 43 to 128 characters from A-Z a-z 0-9 - . _ ~.

3. Send the user to authorize

Open this URL in the user’s browser:

https://api.twexapi.io/oauth/authorize
  ?response_type=code
  &client_id=twx_client_...
  &redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2Fcallback
  &code_challenge=CHALLENGE
  &code_challenge_method=S256
  &state=RANDOM_STATE
  &scope=mcp%3Aread%20mcp%3Awrite
  &resource=https%3A%2F%2Fapi.twexapi.io%2Fmcp
Parameter Required Notes
response_type Yes code.
client_id Yes From registration.
redirect_uri Yes Must match a registered URI exactly. Loopback http URIs may use any port.
code_challenge, code_challenge_method Yes S256 only.
state Recommended Returned unchanged; check it to prevent CSRF.
scope No See Scopes.
resource No If sent, must be https://api.twexapi.io/mcp.

TwexAPI redirects the user to the consent page at twexapi.io. The user signs in, reviews the app name and the host they will return to, chooses whether to grant write access, and approves or denies. The request expires after 10 minutes.

4. Receive the code

On approval, the browser returns to your redirect URI:

http://127.0.0.1:53682/callback?code=CODE&state=RANDOM_STATE&iss=https%3A%2F%2Fapi.twexapi.io

Check that state matches and that iss is https://api.twexapi.io. On denial, the redirect carries error=access_denied instead of code. The code is single use and expires after 2 minutes.

If client_id is unknown or redirect_uri is not registered, TwexAPI shows a 400 error instead of redirecting, so a misconfigured client never sends users to an unverified address.

5. Exchange the code

POST https://api.twexapi.io/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=CODE
&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2Fcallback
&client_id=twx_client_...
&code_verifier=VERIFIER
{
  "access_token": "twx_at_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "twx_rt_...",
  "scope": "mcp:read mcp:write"
}

redirect_uri and client_id must match the authorization request. You may also send resource=https://api.twexapi.io/mcp. A JSON body is accepted as well.

6. Call the MCP server

POST https://api.twexapi.io/mcp
Authorization: Bearer twx_at_...
Content-Type: application/json
Accept: application/json, text/event-stream

The access token works only on /mcp. An expired, revoked, or unknown token returns 401 with error="invalid_token" in the WWW-Authenticate header; refresh it or ask the user to sign in again.

Refresh tokens

POST https://api.twexapi.io/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=twx_rt_...
&client_id=twx_client_...

Each refresh returns a new access token and a new refresh token, and the old refresh token stops working. Store the new pair every time. If an old refresh token is used again, TwexAPI treats it as leaked and revokes the whole authorization, so every token from it stops working and the user has to sign in again.

Revocation

POST https://api.twexapi.io/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=twx_rt_...

Revoking a refresh token ends the whole authorization. Revoking an access token ends only that token. The endpoint always returns 200, even for unknown tokens. Users can also revoke any app under Authorized apps in the dashboard, which takes effect immediately.

Token lifetimes

Item Lifetime
Authorization request (consent page) 10 minutes
Authorization code 2 minutes, single use
Access token 1 hour
Refresh token 30 days, rotated on every use
Authorization not used for 30 days Expires
Client registration 180 days, extended on each authorization

Errors

Errors use the standard OAuth shape:

{
  "error": "invalid_grant",
  "error_description": "The authorization code is invalid, expired or already used"
}
Endpoint error Cause
Register invalid_redirect_uri A redirect URI is missing, malformed, or uses a disallowed scheme or host.
Register invalid_client_metadata Unsupported token_endpoint_auth_method, grant_types, or response_types.
Register temporarily_unavailable (429) Too many registrations from your IP address. Reuse your client_id.
Authorize invalid_client (400, no redirect) Unknown client_id. Register first.
Authorize invalid_request (400, no redirect) redirect_uri is not registered for this client.
Authorize unsupported_response_type response_type is not code.
Authorize invalid_request Missing or invalid PKCE parameters.
Authorize invalid_target resource is not https://api.twexapi.io/mcp.
Authorize access_denied The user denied access.
Token invalid_grant Code or refresh token is invalid, expired, already used, or issued to another client; PKCE verification failed; or the authorization was revoked.
Token invalid_target resource does not match.
Token unsupported_grant_type Use authorization_code or refresh_token.

The consent page also stops the user before approval when their account has no usable API key or no credits left, so your client never receives a token that cannot make calls.

Was this page helpful?