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
- 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 receive 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 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.
All code examples below use Google endpoints. To use Microsoft instead, replace google with microsoft in 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 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:
curl "https://api.instantly.ai/api/v2/oauth/session/status/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer YOUR_API_KEY"
Response (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 | 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. 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)
// 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 examples
Node.js
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
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 | Description |
|---|
200 | Success |
401 | Unauthorized — invalid or missing API key |
404 | Session not found or belongs to a 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 are 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 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.