OAuth Account API Integration Guide
This documentation covers OAuth integration for both Google and Microsoft accounts.
Overview
This API allows third-party developers to programmatically connect Google OAuth accounts to Instantly workspaces without requiring browser automation through the frontend.
Flow Summary
- 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.
- 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 get the session ID from step #1
Authentication
All API requests require authentication using your Instantly API key:
Authorization: Bearer <your-api-key>The API key must have the accounts:create scope for initializing OAuth and 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] |
|---|---|---|
| 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 |
- 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, simply replacemicrosoftin all URLs.
API Endpoints
1. Initialize OAuth Session
Creates a new OAuth session and returns the Google authorization URL.
Endpoint: POST /api/v2/oauth/google/init
Request:
curl -X POST "https://api.instantly.ai/api/v2/oauth/google/init" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"Response:
{
"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 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:
curl "https://api.instantly.ai/api/v2/oauth/session/status/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer YOUR_API_KEY"Response (Pending):
{
"status": "pending"
}Response (Success):
{
"status": "success",
"email": "user@example.com",
"name": "John Doe",
"account_id": "user@example.com"
}Response (Error):
{
"status": "error",
"error": "account_exists",
"error_description": "Account already exists in another workspace"
}Response (Expired):
{
"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.
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. This can be done 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)
// 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.
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 Example
Node.js Example
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('\n✓ Account 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(`\n✗ OAuth failed: ${result.error}`);
console.error(` ${result.error_description}`);
throw new Error(result.error_description);
case 'expired':
console.error('\n✗ Session 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 Example
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\n✓ Account 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\n✗ OAuth failed: {result['error']}")
print(f" {result.get('error_description', '')}")
raise Exception(result.get('error_description', result['error']))
elif result['status'] == 'expired':
print('\n\n✗ Session 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 | Description |
|---|---|
200 | Success |
401 | Unauthorized - Invalid or missing API key |
404 | Session not found or belongs to different workspace |
429 | Rate limit exceeded |
Security Considerations
- Session Binding: Sessions are tied to the workspace that created them. Only the same API key/workspace can poll for status.
- One-Time Use: Successful or error sessions are deleted after the first status retrieval.
- Short TTL: Sessions expire after 10 minutes if not completed.
- No Token Exposure: OAuth refresh tokens are never returned via the API - they're stored directly in the database.
- GSuite Only: Only Google Workspace (GSuite) accounts are supported. Personal Gmail accounts will be rejected.
Rate Limits
- Init endpoint: Standard API rate limits apply
- Status endpoint: Standard API rate limits 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 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