> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runtools.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth & Connected Apps

> Connect third-party accounts for Tool Hub credential resolution

Connected Apps let a user link a third-party account once — GitHub, Google, Slack, Microsoft, and the rest of the org's enabled providers — and then every tool that needs that provider just works. Runtools mints and refreshes the token server-side at call time; the access token never passes through your code or an agent's prompt.

If you are building a product on Runtools and your product's users need to connect their own accounts, use [Hosted Connect](/sdk/hosted-connect) instead. Connected Apps are for the authenticated Runtools user or org; Hosted Connect is for app-scoped customer connections such as `atlas + customer_123 + gmail`.

The provider catalog comes from `GET /v1/oauth/providers`. Most providers use a browser redirect flow. `openai-codex` uses a device-code flow when it is enabled.

<Warning>
  Raw OAuth token retrieval is internal-only service plumbing. Use connection management helpers and hosted tool execution instead of building your own token exchange or token storage.
</Warning>

## Connect With The CLI

```bash theme={null}
runtools login
runtools oauth connect github
runtools oauth status
runtools oauth set-default <connection-id>
runtools oauth disconnect <connection-id>
```

The CLI supports browser redirect providers such as `github`, `google`, `slack`, `discord`, `microsoft`, `linkedin`, and `x`.

Use custom scopes when needed:

```bash theme={null}
runtools oauth connect google \
  --scopes "https://www.googleapis.com/auth/gmail.modify"
```

## Connect With The SDK

```typescript theme={null}
const { authUrl } = await rt.auth.connect('github', {
  scopes: ['repo', 'read:user'],
});

// Redirect the user to authUrl.
```

```typescript theme={null}
const connected = await rt.auth.isConnected('github');
const connections = await rt.auth.listConnections();

if (connections[0]) {
  await rt.auth.setDefault(connections[0].id);   // pick the default account
  await rt.auth.disconnect(connections[0].id);   // or remove a connection
}
```

## How Tools Use Connections

When you run a Tool Hub action, RunTools resolves credentials in this order and only fills missing required credential fields:

1. Per-request `credentials`
2. `credentialOverrides` that point at stored secrets
3. Stored credentials for the installed tool
4. Matching user or org secrets
5. Connected OAuth account for the tool's provider

Secret fallbacks use normalized names. For example, a required field named `apiKey` maps to the secret name `APIKEY`; use `credentialOverrides` when you want a different secret name.

```typescript theme={null}
const result = await rt.tools.execute('github', {
  action: 'list_repos',
  params: { owner: 'runtools-ai' },
});
```

If multiple accounts are connected for the same provider, execution uses the user's default connection unless you pass an account selector. The selector can be a human-readable email, username, display name, provider account ID, or exact connection ID from Connected Apps:

```typescript theme={null}
await rt.tools.execute('gmail', {
  action: 'list_emails',
  params: { query: 'is:unread' },
  oauth: {
    account: 'artkara2002@gmail.com',
  },
});
```

Agents receive optional `connected_account` and `connected_connection_id` inputs on OAuth-backed tools. Prefer `connected_account` so the model can use labels users naturally mention, such as an email address or GitHub username.

## BYOA Provider Configs

Bring-your-own-app configs let an organization use its own OAuth client for a provider. Creating, listing, updating, or deleting provider configs requires credential-admin access.

```typescript theme={null}
await rt.auth.setProviderConfig('github', {
  clientId: process.env.GITHUB_CLIENT_ID!,
  clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  scopes: 'repo,read:user',
});

const configs = await rt.auth.listProviderConfigs();
await rt.auth.deleteProviderConfig('github');
```

When a BYOA config is enabled, OAuth start uses the organization's client ID, client secret, and custom scopes. If it is absent or disabled, RunTools falls back to the platform provider configuration when that provider is registered.

```bash cURL theme={null}
curl -X POST https://auth.runtools.ai/v1/oauth/configs \
  -H "X-API-Key: $RUNTOOLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "github",
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "scopes": "repo,read:user"
  }'
```

## Custom Tools

Custom tools declare OAuth needs in their credential spec. RunTools maps the connected provider token into the credential field before running the tool.

```typescript tools/my-google-tool.ts theme={null}
import { defineTool } from '@runtools-ai/sdk';

export default defineTool({
  name: 'my-google-tool',
  description: 'Read Google Drive metadata',
  credentials: {
    required: ['accessToken'],
    schema: {
      accessToken: {
        type: 'string',
        description: 'Google OAuth access token',
      },
    },
    oauth: {
      provider: 'google',
      scopes: ['https://www.googleapis.com/auth/drive.readonly'],
      credentialMapping: { accessToken: 'access_token' },
    },
  },
  actions: {
    list_files: {
      description: 'List Drive files',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string' },
        },
      },
      execute: async (params, credentials) => {
        const response = await fetch('https://www.googleapis.com/drive/v3/files', {
          headers: {
            Authorization: `Bearer ${credentials.accessToken}`,
          },
        });
        return response.json();
      },
    },
  },
});
```

If a tool has an OAuth credential spec and no matching connection exists, hosted execution reports the missing credential through the normal tool execution error path. The caller never receives the provider access token directly.

## API Reference

| Method   | Path                                     | Description                                               |
| -------- | ---------------------------------------- | --------------------------------------------------------- |
| `GET`    | `/v1/oauth/providers`                    | List providers                                            |
| `GET`    | `/v1/oauth/start/{provider}`             | Start an OAuth connection flow                            |
| `POST`   | `/v1/oauth/device/start/{provider}`      | Start a device-code OAuth flow for providers that use one |
| `POST`   | `/v1/oauth/device/status/{provider}`     | Poll a device-code OAuth attempt                          |
| `GET`    | `/v1/oauth/connections`                  | List connections                                          |
| `GET`    | `/v1/oauth/connections/{id}`             | Get connection metadata                                   |
| `DELETE` | `/v1/oauth/connections/{id}`             | Disconnect by connection ID                               |
| `POST`   | `/v1/oauth/connections/{id}/set-default` | Mark a connection as default                              |
| `GET`    | `/v1/oauth/status/{provider}`            | Check provider connection status                          |
| `GET`    | `/v1/oauth/configs`                      | List BYOA provider configs                                |
| `POST`   | `/v1/oauth/configs`                      | Create or update a BYOA provider config                   |
| `PATCH`  | `/v1/oauth/configs/{provider}`           | Enable or disable a BYOA provider config                  |
| `DELETE` | `/v1/oauth/configs/{provider}`           | Delete a BYOA provider config                             |

Internal services also call `/v1/oauth/token/{provider}` and `/v1/oauth/export/{provider}` with internal scopes. Those endpoints are not public customer APIs.
