OAuth 2.0 Integration

JMPY.me provides OAuth 2.0 authentication following industry standards, allowing third-party applications to securely access user accounts.
Standards ComplianceOur OAuth implementation follows:
  • RFC 6749 - OAuth 2.0 Authorization Framework
  • RFC 7636 - PKCE (Proof Key for Code Exchange)
  • RFC 7591 - Dynamic Client Registration
  • RFC 8414 - Authorization Server Metadata

Quick Start

1

Register Your Application

Register your app to get OAuth credentials (client_id and client_secret).
curl -X POST https://jmpy.me/mcp/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My Awesome App",
    "redirect_uris": ["https://myapp.com/callback"],
    "grant_types": ["authorization_code"],
    "response_types": ["code"],
    "scope": "shorturl:read shorturl:create qrcode:read qrcode:create"
  }'
Response:
{
  "client_id": "jmpy_client_abc123...",
  "client_secret": "jmpy_secret_xyz789...",
  "client_name": "My Awesome App",
  "redirect_uris": ["https://myapp.com/callback"]
}
2

Redirect User to Authorization

When a user wants to connect, redirect them to our authorization endpoint:
const authUrl = new URL('https://jmpy.me/mcp/oauth/authorize');
authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'shorturl:read shorturl:create');
authUrl.searchParams.set('state', 'random_csrf_token');

// For PKCE (recommended)
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

window.location.href = authUrl.toString();
3

Handle the Callback

After user approval, they’re redirected back with an authorization code:
https://myapp.com/callback?code=AUTH_CODE&state=random_csrf_token
4

Exchange Code for Tokens

Exchange the authorization code for access and refresh tokens:
curl -X POST https://jmpy.me/mcp/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=https://myapp.com/callback" \
  -d "code_verifier=ORIGINAL_CODE_VERIFIER"
Response:
{
  "access_token": "eyJhbGciOiJIUzI1...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "jmpy_rt_abc123...",
  "scope": "shorturl:read shorturl:create"
}
5

Make API Calls

Use the access token to make API requests:
curl https://jmpy.me/api/v1/urls \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1..."

OAuth Endpoints

EndpointURLDescription
Discovery/.well-known/oauth-authorization-serverOAuth server metadata
RegisterPOST /mcp/oauth/registerDynamic client registration
AuthorizeGET /mcp/oauth/authorizeUser authorization page
TokenPOST /mcp/oauth/tokenToken exchange
RevokePOST /mcp/oauth/revokeToken revocation

Pre-Registered Clients

For common AI assistants, JMPY provides pre-registered OAuth clients:
AppClient IDDocumentation
OpenAI/ChatGPTopenaiGPT Actions Guide
Claude (MCP)claudeMCP Setup
ZapierzapierComing soon
Pre-registered clients have verified credentials and streamlined setup. Users can connect these apps directly from their JMPY dashboard.

Token Lifecycle

Access Tokens

  • Type: JWT (JSON Web Token)
  • Expiration: 1 hour
  • Usage: Include in Authorization: Bearer <token> header

Refresh Tokens

  • Expiration: 30 days
  • Usage: Exchange for new access token when expired
  • Rotation: Each refresh returns a new refresh token (old token is invalidated)
# Refresh an expired access token
curl -X POST https://jmpy.me/mcp/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=jmpy_rt_abc123..." \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. We strongly recommend using PKCE for all OAuth flows.

Generate Code Verifier and Challenge

// Generate a random code verifier
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

// Create the code challenge (SHA-256 hash)
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const hash = await crypto.subtle.digest('SHA-256', data);
  return base64UrlEncode(new Uint8Array(hash));
}

function base64UrlEncode(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

Use in Authorization Request

const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);

// Store codeVerifier securely for later use in token exchange
sessionStorage.setItem('pkce_verifier', codeVerifier);

// Add to authorization URL
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

Error Handling

OAuth endpoints return standard error codes per RFC 6749:
ErrorDescription
invalid_requestMissing or invalid parameters
invalid_clientClient authentication failed
invalid_grantAuthorization code invalid or expired
unauthorized_clientClient not authorized for this grant type
access_deniedUser denied the authorization request
invalid_scopeRequested scope is invalid or exceeds plan limits
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}

Next Steps