OAuth Flow
Authenticate with the ScamVerify™ MCP server and API using OAuth 2.1 with PKCE.
ScamVerify™ implements OAuth 2.1 with PKCE (Proof Key for Code Exchange) for MCP clients and third-party applications. This allows users to authorize applications without sharing API keys.
Most MCP clients (Claude Desktop, Cursor) handle OAuth automatically. You only need this guide if you are building a custom integration or want to understand the flow.
Discovery Endpoints
ScamVerify™ publishes OAuth metadata at standard well-known URLs:
| Endpoint | Purpose |
|---|---|
GET /.well-known/oauth-authorization-server | RFC 8414 authorization server metadata |
GET /.well-known/oauth-protected-resource | RFC 9728 protected resource metadata |
Authorization Server Metadata
curl https://scamverify.ai/.well-known/oauth-authorization-server{
"issuer": "https://scamverify.ai",
"authorization_endpoint": "https://scamverify.ai/api/oauth/authorize",
"token_endpoint": "https://scamverify.ai/api/oauth/token",
"registration_endpoint": "https://scamverify.ai/api/oauth/register",
"scopes_supported": [
"phone:lookup",
"url:lookup",
"text:analyze",
"email:analyze",
"usage:read"
],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"]
}Protected Resource Metadata
curl https://scamverify.ai/.well-known/oauth-protected-resource{
"resource": "https://scamverify.ai/api/mcp",
"authorization_servers": ["https://scamverify.ai"],
"scopes_supported": [
"phone:lookup",
"url:lookup",
"text:analyze",
"email:analyze",
"usage:read"
]
}OAuth Flow Step by Step
Register your client
Register a new OAuth client with the registration endpoint. You need at least one redirect URI from the allowlist.
curl -X POST https://scamverify.ai/api/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My MCP App",
"redirect_uris": ["http://localhost:3000/callback"]
}'Response:
{
"client_id": "client_abc123...",
"client_secret": "secret_xyz789...",
"client_name": "My MCP App",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}Store the client_secret securely. It is only returned once during registration.
Generate PKCE challenge
Generate a code verifier (random string) and its S256 challenge:
import crypto from 'crypto';
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');import secrets
import hashlib
import base64
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()Redirect user to authorize
Build the authorization URL and redirect the user:
https://scamverify.ai/api/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=http://localhost:3000/callback&
code_challenge=CODE_CHALLENGE&
code_challenge_method=S256&
state=RANDOM_STATE&
scope=phone:lookup url:lookup text:analyze email:analyze usage:readThe user will see a ScamVerify™-branded consent screen showing the requested permissions. If the user is not signed in, they will be redirected to the login page first.
Exchange code for tokens
After the user approves, they are redirected to your redirect_uri with an authorization code:
http://localhost:3000/callback?code=AUTH_CODE&state=RANDOM_STATEExchange the code for tokens:
curl -X POST https://scamverify.ai/api/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "http://localhost:3000/callback",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"code_verifier": "CODE_VERIFIER"
}'const response = await fetch("https://scamverify.ai/api/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code: authCode,
redirect_uri: "http://localhost:3000/callback",
client_id: clientId,
client_secret: clientSecret,
code_verifier: codeVerifier,
}),
});
const tokens = await response.json();import requests
response = requests.post(
"https://scamverify.ai/api/oauth/token",
json={
"grant_type": "authorization_code",
"code": auth_code,
"redirect_uri": "http://localhost:3000/callback",
"client_id": client_id,
"client_secret": client_secret,
"code_verifier": code_verifier,
},
)
tokens = response.json()Response:
{
"access_token": "svat_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "svrt_...",
"scope": "phone:lookup url:lookup text:analyze email:analyze usage:read"
}Use the access token
Include the access token in MCP requests or direct API calls:
curl -X POST https://scamverify.ai/api/mcp \
-H "Authorization: Bearer svat_YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}'Refresh expired tokens
Access tokens expire after 1 hour. Use the refresh token to get a new pair:
curl -X POST https://scamverify.ai/api/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"refresh_token": "svrt_YOUR_REFRESH_TOKEN",
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET"
}'Refresh tokens are rotated on each use. The old refresh token is invalidated and a new one is returned. Refresh tokens expire after 30 days.
Scopes
Request only the scopes your application needs:
| Scope | Permission |
|---|---|
phone:lookup | Look up phone number risk assessments |
url:lookup | Check website and URL safety |
text:analyze | Analyze text messages for scams |
email:analyze | Analyze emails for phishing |
usage:read | View API usage and quota |
If no scope is specified, all scopes are granted.
Allowed Redirect URIs
For security, redirect URIs must match one of these patterns:
| Pattern | Use Case |
|---|---|
https://*.claude.ai/* | Claude Desktop / claude.ai |
https://*.anthropic.com/* | Anthropic services |
https://*.chatgpt.com/* | ChatGPT |
https://*.openai.com/* | OpenAI platform |
https://*.smithery.ai/* | Smithery MCP registry |
https://*.run.tools/* | run.tools MCP platform |
http://localhost:*/* | Local development (any port) |
http://127.0.0.1:*/* | Local development (any port) |
Exact matches for common OAuth redirect URLs are also supported (e.g., https://chatgpt.com/connector_platform_oauth_redirect).
Token Details
| Property | Access Token | Refresh Token |
|---|---|---|
| Prefix | svat_ | svrt_ |
| TTL | 1 hour | 30 days |
| Storage | SHA-256 hashed (never stored in plaintext) | SHA-256 hashed |
| Rotation | New token on each refresh | Rotated on use |
Error Responses
Invalid Client
{
"error": "invalid_client",
"error_description": "Client authentication failed"
}Invalid Grant
Returned when the authorization code is expired, already used, or the PKCE verifier does not match:
{
"error": "invalid_grant",
"error_description": "Authorization code is invalid or expired"
}Unsupported Grant Type
{
"error": "unsupported_grant_type",
"error_description": "Only authorization_code and refresh_token grants are supported"
}Security Notes
- PKCE is mandatory. All authorization requests must include
code_challengewith methodS256. Plain challenge method is not supported. - Authorization codes are one-time use. A code can only be exchanged for tokens once. Subsequent attempts will fail.
- Authorization codes expire in 5 minutes. Exchange them promptly after the user approves.
- Tokens are stored as SHA-256 hashes. ScamVerify™ never stores tokens in plaintext.
- CSRF protection. The authorization endpoint uses cookie-based nonce validation.