Skip to main content

Overview

RunTools provides built-in OAuth integration for 10 providers. Connect your accounts once, and tools automatically resolve fresh access tokens — no manual token management. Key features:
  • One-click connect — authorize via the Connected Apps tab
  • Auto-refresh — background daemon refreshes tokens before they expire
  • On-demand refresh — if a token is expired when needed, it’s refreshed instantly
  • BYOA — bring your own OAuth app credentials for white-label consent screens
  • AES-256-GCM encryption — tokens encrypted at rest, decrypted only when needed

Supported Providers

ProviderIDToken TypeUsed By
GooglegoogleOAuth 2.0Gmail, Calendar, Sheets
GitHubgithubOAuth 2.0GitHub tool
SlackslackOAuth 2.0Slack tool
DiscorddiscordOAuth 2.0Discord tool
MicrosoftmicrosoftOAuth 2.0Outlook tool
X (Twitter)xOAuth 2.0 (PKCE)X/Twitter tool
LinkedInlinkedinOAuth 2.0LinkedIn tool
TelegramtelegramLogin Widget (HMAC)Telegram tool
WhatsAppwhatsappMeta OAuth 2.0WhatsApp tool

Connecting an Account

Via Dashboard

  1. Go to Dashboard → Credentials → Connected Apps
  2. Find the provider you want to connect
  3. Click Connect — you’ll be redirected to the provider’s authorization page
  4. Authorize RunTools (or your BYOA app) to access your account
  5. You’ll be redirected back with a Connected status

Via CLI

# Start OAuth flow (opens browser)
runtools oauth connect google

# List connected accounts
runtools oauth list

# Disconnect
runtools oauth disconnect google

How Auto-Resolution Works

When a tool executes, the tools service resolves credentials automatically:
Tool needs credentials

Check: per-request credentials passed?
    → YES: use them
    → NO: continue

Check: stored credentials in DB?
    → YES: decrypt and use them
    → NO: continue

Check: tool declares credentials.oauth.provider?
    → YES: call auth service GET /v1/oauth/token/:provider

           Token expired?
              → YES: refresh via provider's token endpoint, update DB
              → NO: return current token

           Map token via credentialMapping

           Tool executes with fresh credentials
    → NO: run with empty credentials

Token Refresh

OAuth tokens are kept fresh through two mechanisms:

Background Daemon (Proactive)

A background task in the auth service runs every 5 minutes:
  1. Queries for connections with tokens expiring within 10 minutes
  2. Decrypts refresh tokens
  3. Calls each provider’s token refresh endpoint
  4. Updates the DB with new access tokens and expiry times
  5. Tracks failures to avoid retrying revoked tokens

On-Demand (Reactive)

When GET /v1/oauth/token/:provider is called and the token is expired:
  1. The auth service refreshes the token immediately
  2. Stores the new token
  3. Returns the fresh token to the caller
This dual approach ensures tokens are almost always fresh when tools need them.

BYOA (Bring Your Own App)

Organizations can register their own OAuth app credentials for any provider. When configured:
  • The consent screen shows your app name instead of “RunTools”
  • You control the scopes requested
  • Your customers see your brand throughout the OAuth flow

Dashboard

Go to Dashboard → Credentials → Connected Apps → BYOA Settings to configure per-provider OAuth apps.

API

# Register BYOA credentials
curl -X POST https://auth.runtools.ai/v1/oauth/byoa/google \
  -H "Authorization: Bearer rt_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "your-google-client-id.apps.googleusercontent.com",
    "client_secret": "your-google-client-secret",
    "scopes": ["https://www.googleapis.com/auth/gmail.modify"]
  }'
BYOA credentials are AES-256-GCM encrypted at rest, just like regular tokens.

Declaring OAuth in Custom Tools

When building custom tools with defineTool(), declare the OAuth provider in the credentials spec:
tools/my-google-tool.ts
import { defineTool } from '@runtools/sdk';

export default defineTool({
  name: 'my-google-tool',
  description: 'Custom tool using Google OAuth',
  credentials: {
    required: ['accessToken'],
    schema: {
      accessToken: {
        type: 'string',
        description: 'Google OAuth2 access token',
      },
    },
    oauth: {
      provider: 'google',                                         // which provider to use
      scopes: ['https://www.googleapis.com/auth/drive.readonly'],  // required scopes
      credentialMapping: { accessToken: 'access_token' },          // map to token field
    },
  },
  actions: {
    list_files: {
      description: 'List Google Drive files',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'Search query' },
        },
        required: [],
      },
      execute: async (params, credentials) => {
        // credentials.accessToken is automatically a fresh Google token
        const res = await fetch('https://www.googleapis.com/drive/v3/files', {
          headers: { Authorization: `Bearer ${credentials.accessToken}` },
        });
        return await res.json();
      },
    },
  },
});
The credentialMapping maps your tool’s credential key names to OAuth token fields:
  • { accessToken: 'access_token' } — maps credentials.accessToken to the OAuth access token
  • { token: 'access_token' } — maps credentials.token to the OAuth access token
If no mapping is specified, the default is { token: 'access_token' }.

Security

PropertyDetail
EncryptionAES-256-GCM at rest for all tokens and BYOA client secrets
TransportHTTPS only, internal calls use X-Internal-Secret service auth
Sandbox isolationOAuth tokens never enter sandbox VMs — resolved server-side
Refresh trackingFailed refreshes are tracked to prevent retry storms
ScopingConnections are scoped to org_id + user_id

API Reference

MethodPathDescription
GET/v1/oauth/providersList available OAuth providers
POST/v1/oauth/start/:providerStart OAuth authorization flow
GET/v1/oauth/callback/:providerOAuth callback (handled automatically)
GET/v1/oauth/connectionsList user’s OAuth connections
DELETE/v1/oauth/connections/:providerDisconnect an OAuth provider
GET/v1/oauth/token/:providerGet valid access token (internal only, auto-refreshes)