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

# OAuth connection flow

> Programmatically connect Google and Microsoft OAuth accounts to your Instantly workspace.

# OAuth account API integration guide

This API allows third-party developers to programmatically connect Google and Microsoft OAuth accounts to Instantly workspaces without requiring browser automation through the frontend.

## Flow summary

1. Send an API request to get the OAuth URL.
   * Send this request with the API key of the workspace to which you want to connect the account.
2. Open that URL in a new browser tab and connect the account.
   * The account will automatically connect behind the scenes.
   * There is no need for any Instantly login cookie, or to open the Instantly dashboard at all.
   * Optionally, you can check status using the session ID you receive from step 1.

## Authentication

All API requests require authentication using your Instantly API key:

```bash theme={null}
Authorization: Bearer <your-api-key>
```

The API key must have the `accounts:create` scope for initializing OAuth and the `accounts:read` scope for checking status.

## Provider-specific notes

The API endpoints and flow are identical -- simply replace the provider name in the init endpoint:

| Provider      | Init endpoint                       | Status endpoint (same for both)               |
| ------------- | ----------------------------------- | --------------------------------------------- |
| **Google**    | `POST /api/v2/oauth/google/init`    | `GET /api/v2/oauth/session/status/:sessionId` |
| **Microsoft** | `POST /api/v2/oauth/microsoft/init` | `GET /api/v2/oauth/session/status/:sessionId` |

**Google**

* Only Google Workspace (GSuite) accounts are supported.
* Personal Gmail accounts (`@gmail.com`) will be rejected.

**Microsoft**

* Supports both Microsoft 365 (business) and personal Microsoft accounts.
* Uses Outlook / Office 365 for email access.

<Note>All code examples below use Google endpoints. To use Microsoft instead, replace `google` with `microsoft` in all URLs.</Note>

## API endpoints

### 1. Initialize OAuth session

Creates a new OAuth session and returns the Google authorization URL.

**Endpoint:** `POST /api/v2/oauth/google/init`

<Note>This endpoint has special rate limits to comply with upstream provider limits. See [Rate limits](#rate-limits) for details.</Note>

**Request:**

```bash theme={null}
curl -X POST "https://api.instantly.ai/api/v2/oauth/google/init" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

**Response:**

```json theme={null}
{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&state=api_session:550e8400-e29b-41d4-a716-446655440000&...",
  "expires_at": "2024-01-15T10:30:00.000Z"
}
```

| Field        | Type              | Description                                     |
| ------------ | ----------------- | ----------------------------------------------- |
| `session_id` | string            | Unique session ID for polling status            |
| `auth_url`   | string            | Google authorization URL to open in the browser |
| `expires_at` | string (ISO 8601) | Session expiry time (10 minutes from creation)  |

### 2. Check OAuth status

Poll this endpoint to check the result of the OAuth flow.

**Endpoint:** `GET /api/v2/oauth/session/status/:sessionId`

**Request:**

```bash theme={null}
curl "https://api.instantly.ai/api/v2/oauth/session/status/550e8400-e29b-41d4-a716-446655440000" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response (pending):**

```json theme={null}
{
  "status": "pending"
}
```

**Response (success):**

```json theme={null}
{
  "status": "success",
  "email": "user@example.com",
  "name": "John Doe",
  "account_id": "user@example.com"
}
```

**Response (error):**

```json theme={null}
{
  "status": "error",
  "error": "account_exists",
  "error_description": "Account already exists in another workspace"
}
```

**Response (expired):**

```json theme={null}
{
  "status": "expired"
}
```

| Status    | Description                         |
| --------- | ----------------------------------- |
| `pending` | User has not completed OAuth yet    |
| `success` | Account successfully connected      |
| `error`   | OAuth failed (see error fields)     |
| `expired` | Session expired (10-minute timeout) |

## Integration guide

### Step 1: Initialize the OAuth session

Call the init endpoint to get a session ID and authorization URL.

```javascript theme={null}
const response = await fetch('https://api.instantly.ai/api/v2/oauth/google/init', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  }
});

const { session_id, auth_url, expires_at } = await response.json();
```

### Step 2: Open the authorization URL

Direct the user to the `auth_url` to complete Google authentication. You can do this by:

* **Option A:** Opening in a new browser tab/window
* **Option B:** Opening in a popup window
* **Option C:** Redirecting the current page (requires handling the return)

```javascript theme={null}
// Option A: New tab
window.open(auth_url, '_blank');

// Option B: Popup window
const popup = window.open(
  auth_url,
  'google-oauth',
  'width=500,height=600,scrollbars=yes'
);
```

### Step 3: Poll for completion

Poll the status endpoint until the session completes or expires.

```javascript theme={null}
async function pollOAuthStatus(sessionId, apiKey, maxAttempts = 60) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const response = await fetch(
      `https://api.instantly.ai/api/v2/oauth/session/status/${sessionId}`,
      {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      }
    );

    const result = await response.json();

    if (result.status === 'success') {
      return { success: true, account: result };
    }

    if (result.status === 'error') {
      return { success: false, error: result.error, description: result.error_description };
    }

    if (result.status === 'expired') {
      return { success: false, error: 'expired', description: 'Session expired' };
    }

    // Still pending, wait before next poll
    await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second interval
  }

  return { success: false, error: 'timeout', description: 'Polling timeout exceeded' };
}

// Usage
const result = await pollOAuthStatus(session_id, API_KEY);
if (result.success) {
  console.log(`Account connected: ${result.account.email}`);
} else {
  console.error(`OAuth failed: ${result.description}`);
}
```

## Complete examples

### Node.js

```javascript theme={null}
const fetch = require('node-fetch');

const API_KEY = 'your-api-key';
const API_BASE = 'https://api.instantly.ai/api/v2';

async function connectGoogleAccount() {
  // Step 1: Initialize OAuth session
  console.log('Initializing OAuth session...');

  const initResponse = await fetch(`${API_BASE}/oauth/google/init`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    }
  });

  const { session_id, auth_url, expires_at } = await initResponse.json();

  console.log(`Session created: ${session_id}`);
  console.log(`Expires at: ${expires_at}`);
  console.log(`\nPlease open this URL in your browser to authenticate:\n${auth_url}\n`);

  // Step 2: Poll for completion
  console.log('Waiting for authentication...');

  const pollInterval = 2000; // 2 seconds
  const maxWaitTime = 10 * 60 * 1000; // 10 minutes
  const startTime = Date.now();

  while (Date.now() - startTime < maxWaitTime) {
    const statusResponse = await fetch(
      `${API_BASE}/oauth/session/status/${session_id}`,
      {
        headers: { 'Authorization': `Bearer ${API_KEY}` }
      }
    );

    const result = await statusResponse.json();

    switch (result.status) {
      case 'success':
        console.log('\nAccount connected successfully!');
        console.log(`  Email: ${result.email}`);
        console.log(`  Name: ${result.name}`);
        console.log(`  Account ID: ${result.account_id}`);
        return result;

      case 'error':
        console.error(`\nOAuth failed: ${result.error}`);
        console.error(`  ${result.error_description}`);
        throw new Error(result.error_description);

      case 'expired':
        console.error('\nSession expired');
        throw new Error('OAuth session expired');

      case 'pending':
        process.stdout.write('.');
        break;
    }

    await new Promise(resolve => setTimeout(resolve, pollInterval));
  }

  throw new Error('Polling timeout exceeded');
}

// Run
connectGoogleAccount()
  .then(() => process.exit(0))
  .catch(err => {
    console.error(err.message);
    process.exit(1);
  });
```

### Python

```python theme={null}
import requests
import time

API_KEY = 'your-api-key'
API_BASE = 'https://api.instantly.ai/api/v2'

def connect_google_account():
    headers = {
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json'
    }

    # Step 1: Initialize OAuth session
    print('Initializing OAuth session...')

    init_response = requests.post(
        f'{API_BASE}/oauth/google/init',
        headers=headers
    )
    init_data = init_response.json()

    session_id = init_data['session_id']
    auth_url = init_data['auth_url']
    expires_at = init_data['expires_at']

    print(f'Session created: {session_id}')
    print(f'Expires at: {expires_at}')
    print(f'\nPlease open this URL in your browser to authenticate:\n{auth_url}\n')

    # Step 2: Poll for completion
    print('Waiting for authentication...', end='', flush=True)

    poll_interval = 2  # seconds
    max_attempts = 300  # 10 minutes at 2 second intervals

    for _ in range(max_attempts):
        status_response = requests.get(
            f'{API_BASE}/oauth/session/status/{session_id}',
            headers=headers
        )
        result = status_response.json()

        if result['status'] == 'success':
            print('\n\nAccount connected successfully!')
            print(f"  Email: {result['email']}")
            print(f"  Name: {result['name']}")
            print(f"  Account ID: {result['account_id']}")
            return result

        elif result['status'] == 'error':
            print(f"\n\nOAuth failed: {result['error']}")
            print(f"  {result.get('error_description', '')}")
            raise Exception(result.get('error_description', result['error']))

        elif result['status'] == 'expired':
            print('\n\nSession expired')
            raise Exception('OAuth session expired')

        print('.', end='', flush=True)
        time.sleep(poll_interval)

    raise Exception('Polling timeout exceeded')

if __name__ == '__main__':
    connect_google_account()
```

## Error handling

### Common errors

| Error code       | Description                                    | Resolution                                                                 |
| ---------------- | ---------------------------------------------- | -------------------------------------------------------------------------- |
| `account_exists` | Account already connected to another workspace | The Google account is already connected to a different Instantly workspace |
| `server_error`   | Internal server error                          | Retry the OAuth flow                                                       |
| `expired`        | Session expired                                | Start a new OAuth flow (sessions expire after 10 minutes)                  |

### HTTP status codes

| Code  | Endpoint  | Description                                                                                                                                                          |
| ----- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | All       | Success                                                                                                                                                              |
| `401` | All       | Unauthorized -- invalid or missing API key                                                                                                                           |
| `404` | Status    | Session not found or belongs to a different workspace                                                                                                                |
| `429` | All       | Rate limit exceeded — init endpoints have [additional stricter limits](#rate-limits); all endpoints share the [standard API rate limit](/getting-started/rate-limit) |
| `503` | Init only | Upstream provider temporarily unavailable — retry after a short delay                                                                                                |

## Security considerations

1. **Session binding:** Sessions are tied to the workspace that created them. Only the same API key/workspace can poll for status.
2. **One-time use:** Successful or error sessions are deleted after the first status retrieval.
3. **Short TTL:** Sessions expire after 10 minutes if not completed.
4. **No token exposure:** OAuth refresh tokens are never returned via the API -- they are stored directly in the database.
5. **GSuite only:** Only Google Workspace (GSuite) accounts are supported. Personal Gmail accounts will be rejected.

## Rate limits

<Warning>
  The OAuth init endpoints (`/oauth/google/init` and `/oauth/microsoft/init`) have **stricter rate limits** than the [standard API rate limit](/getting-started/rate-limit). These exist to stay compliant with upstream provider (Google, Microsoft) rate limits on OAuth authorization requests.
</Warning>

* **Init endpoint (per workspace):** 75 requests per minute. Exceeding returns `429`.
* **Init endpoint (per IP):** 150 requests per minute. Exceeding returns `429`.
* **Escalating IP blocks:** Persistent abuse from the same IP triggers progressively longer blocks (30 minutes → 2 hours → 24 hours).

<Note>When an IP is blocked, the `429` response includes a `Retry-After` header with the remaining block duration in seconds. Use this value to schedule your next retry.</Note>

* **Circuit breaker:** If the upstream provider (Google/Microsoft) is temporarily rate-limiting Instantly, init requests return `503 Service Unavailable`. This resolves automatically — retry after a short delay.
* **Provider isolation:** Rate limits, IP blocks, and circuit breakers are tracked per provider. A Microsoft outage does not affect Google init requests, and vice versa.
* **Status endpoint:** [Standard API rate limits](/getting-started/rate-limit) apply.
* **Recommended polling interval:** 5 seconds.

## Troubleshooting

### User sees "Non GSuite accounts are not allowed"

The OAuth flow only supports Google Workspace (GSuite) accounts. Personal Gmail accounts (`@gmail.com`) are not supported.

### Session expires before the user completes OAuth

Sessions have a 10-minute TTL. Ensure users complete the OAuth flow promptly. If needed, initialize a new session.

### Status returns 404 "Session not found"

This can happen if:

* The session ID is incorrect.
* The session was created by a different workspace/API key.
* The session has already been consumed (success/error status already retrieved).

### Account shows as connected but status returns "error"

Check the `error` and `error_description` fields. Common issues:

* Account already exists in another workspace.
* Account was blocked by admin.
* Account contains prohibited content.
