Introduction

The Shuttle OAuth 2.0 API is designed for multi-tenant platforms and service providers who need to manage multiple merchant accounts. This OAuth 2.0 implementation allows your platform to enable individual merchants (tenants) to authorize access to their Shuttle accounts, giving your application the ability to perform operations on their behalf.

Use Cases

This authorization method is ideal for:

  • Payment service providers managing multiple merchant accounts
  • E-commerce platforms integrating Shuttle payments for their merchants
  • Accounting software that needs to access payment data across multiple businesses
  • Any multi-tenant SaaS platform requiring access to merchants' Shuttle accounts

How It Works

When a merchant uses your platform, they can authorize your application to access their Shuttle account through OAuth 2.0. Once authorized, your platform receives access tokens that are scoped to that specific merchant's instance, allowing you to make API calls on their behalf.

OAuth Flow Overview

The Shuttle OAuth API supports the Authorization Code flow:

  1. Authorization Request: Redirect merchants to /authorize to grant permissions
  2. Authorization Code: Receive an authorization code via redirect
  3. Token Exchange: Exchange the code for access and refresh tokens using /token
  4. API Access: Use access tokens to make authenticated API calls on behalf of the merchant
  5. Token Management: Refresh expired tokens or revoke unused tokens

Detailed OAuth Walkthrough

This section provides a step-by-step guide for implementing the complete OAuth flow for multi-tenant applications.

Step 1: Initiate Authorization

When a merchant wants to connect their Shuttle account to your platform, redirect them to Shuttle's authorization endpoint.

Request Format:

GET https://merchant.shuttleglobal.com/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_url=https://yourplatform.com/callback
  &scope=payments%20orders    // Optional: space-separated scopes
  &state=merchant_123_random  // Include merchant identifier for tracking

What happens next:

  1. Merchant is redirected to Shuttle's login page
  2. Merchant authenticates with their Shuttle credentials
  3. Merchant reviews and approves the permissions your platform is requesting
  4. Merchant is redirected back to your redirect_url

Step 2: Handle the Authorization Response

After the merchant approves access, Shuttle redirects back to your platform with an authorization code.

Success Response:

https://yourplatform.com/callback
  ?code=AUTH_CODE_HERE
  &state=merchant_123_random  // Use this to identify which merchant authorized

Error Response:

https://yourplatform.com/callback
  ?error=access_denied
  &error_description=User%20denied%20access
  &state=merchant_123_random

Important: The authorization code expires in 15 minutes and can only be used once.

Step 3: Exchange Code for Tokens

Immediately exchange the authorization code for access and refresh tokens. Store these tokens securely associated with the merchant's account in your system.

Request:

POST https://merchant.shuttleglobal.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE_HERE
&redirect_uri=https://yourplatform.com/callback  // Must match Step 1
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

Alternative using Basic Auth (recommended):

POST https://merchant.shuttleglobal.com/token
Authorization: Basic BASE64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE_HERE
&redirect_uri=https://yourplatform.com/callback

Success Response:

{
  "access_token": "at_1234_h480djs93hd8",
  "refresh_token": "rt_1234_8xLOxBtZp8",
  "token_type": "Bearer",
  "expires_in": 3600
}

Step 4: Using Access Tokens for Multi-Tenant API Calls

Use the access token to make API requests on behalf of the specific merchant. Each token is scoped to a particular merchant instance.

Example API Request:

GET https://merchant.shuttleglobal.com/c/api/payments
Authorization: Bearer at_1234_h480djs93hd8

Multi-Tenant Token Usage Notes:

  • Tokens are instance-specific - each merchant's token only accesses their data
  • Works with /c/api/* endpoints, automatically scoped to the merchant's instance
  • Token is valid for 1 hour (3600 seconds)
  • Store tokens securely mapped to each merchant in your database
  • Never mix tokens between different merchants

Step 5: Refreshing Tokens for Multiple Merchants

Manage token refresh for all your connected merchants. Implement automatic refresh before tokens expire.

Request:

POST https://merchant.shuttleglobal.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=rt_1234_8xLOxBtZp8
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

Response:

{
  "access_token": "new_access_token_here",
  "token_type": "Bearer",
  "expires_in": 3600
}

Multi-Tenant Refresh Strategy:

  • Implement a background job to refresh tokens before expiry
  • Track token expiration times for each merchant
  • Handle refresh failures gracefully (merchant may have revoked access)

Step 6: Revoking Tokens

Revoke tokens when a merchant disconnects from your platform or upon their request.

Revoke Access Token:

POST https://merchant.shuttleglobal.com/revoke
Content-Type: application/x-www-form-urlencoded

token=at_1234_h480djs93hd8
&token_type_hint=access_token
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

Revoke Refresh Token:

POST https://merchant.shuttleglobal.com/revoke
Content-Type: application/x-www-form-urlencoded

token=rt_1234_8xLOxBtZp8
&token_type_hint=refresh_token
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET

Complete Multi-Tenant Example

Here's a complete example for a multi-tenant platform:

// Multi-tenant OAuth implementation
class ShuttleOAuth {
  constructor(clientId, clientSecret) {
    this.clientId = clientId;
    this.clientSecret = clientSecret;
  }

  // 1. Generate authorization URL for a merchant
  getAuthorizationUrl(merchantId, redirectUrl) {
    const state = `${merchantId}_${generateRandomString()}`;
    
    // Store state in database for verification
    await db.saveOAuthState(merchantId, state);
    
    return `https://merchant.shuttleglobal.com/authorize?` +
      `response_type=code&` +
      `client_id=${this.clientId}&` +
      `redirect_url=${encodeURIComponent(redirectUrl)}&` +
      `state=${state}`;
  }

  // 2. Handle OAuth callback
  async handleCallback(code, state) {
    // Verify state and get merchant ID
    const merchantId = await db.verifyOAuthState(state);
    if (!merchantId) throw new Error('Invalid state');

    // 3. Exchange code for tokens
    const tokens = await this.exchangeCodeForTokens(code, redirectUrl);
    
    // 4. Store tokens for this merchant
    await db.saveMerchantTokens(merchantId, {
      accessToken: tokens.access_token,
      refreshToken: tokens.refresh_token,
      expiresAt: Date.now() + (tokens.expires_in * 1000)
    });
    
    return merchantId;
  }

  // 5. Make API call for a specific merchant
  async makeApiCall(merchantId, endpoint) {
    const tokens = await db.getMerchantTokens(merchantId);
    
    // Check if token needs refresh
    if (Date.now() >= tokens.expiresAt - 300000) { // 5 min buffer
      await this.refreshMerchantToken(merchantId);
      tokens = await db.getMerchantTokens(merchantId);
    }
    
    const response = await fetch(`https://merchant.shuttleglobal.com${endpoint}`, {
      headers: {
        'Authorization': `Bearer ${tokens.accessToken}`
      }
    });
    
    if (response.status === 401) {
      // Token might be revoked, handle accordingly
      await this.handleRevokedToken(merchantId);
    }
    
    return response;
  }

  // 6. Refresh tokens for all merchants (run as cron job)
  async refreshExpiringTokens() {
    const expiringTokens = await db.getTokensExpiringIn(3600000); // 1 hour
    
    for (const merchant of expiringTokens) {
      try {
        await this.refreshMerchantToken(merchant.id);
      } catch (error) {
        console.error(`Failed to refresh token for merchant ${merchant.id}`, error);
        // Notify merchant to re-authorize
      }
    }
  }
}

Common Multi-Tenant Scenarios

Merchant Onboarding:

  1. Merchant signs up for your platform
  2. Merchant initiates Shuttle connection
  3. Your platform redirects to Shuttle OAuth
  4. Merchant authorizes access
  5. Your platform stores tokens for future use

Handling Disconnections:

  • Provide UI for merchants to disconnect
  • Revoke tokens when merchant disconnects
  • Clean up stored tokens from your database
  • Handle gracefully if merchant revokes access from Shuttle's side

Error Handling:

  • Token expiration: Automatic refresh using refresh tokens
  • Invalid tokens: Prompt merchant to re-authorize
  • API errors: Differentiate between auth errors and other API errors
  • Rate limiting: Implement proper backoff strategies

Security Best Practices for Multi-Tenant Platforms

  • Isolate merchant data: Never allow tokens from one merchant to access another's data
  • Encrypt stored tokens: Use encryption at rest for all stored OAuth tokens
  • Implement token rotation: Regularly refresh tokens even if not expired
  • Audit token usage: Log all token usage for security and compliance
  • Handle merchant offboarding: Ensure tokens are revoked when merchants leave
  • Use HTTPS everywhere: All token handling must be over secure connections
  • Validate state parameter: Always verify state to prevent CSRF attacks
  • Secure your client secret: Never expose client credentials in client-side code