Gmail Scopes
Read-only access:
https://www.googleapis.com/auth/gmail.readonlySend emails:
https://www.googleapis.com/auth/gmail.sendFull access:
https://mail.google.com/Modify (read/write, no delete):
https://www.googleapis.com/auth/gmail.modifyLearn how to manage OAuth scopes and permissions for Agent Auth connections to control what your application can access.
OAuth scopes and permissions determine what data and actions your application can access on behalf of users. Understanding how to properly configure and manage scopes is essential for building secure and functional Agent Auth integrations.
OAuth scopes are permission grants that define the level of access your application has to a user’s data with third-party providers.
Scopes are strings that represent specific permissions:
# Example OAuth scopeshttps://www.googleapis.com/auth/gmail.readonly # Read Gmail messageshttps://www.googleapis.com/auth/gmail.send # Send Gmail messageshttps://www.googleapis.com/auth/calendar.events # Manage calendar eventschannels:read # Read Slack channelschat:write # Send Slack messages###How scopes work
Scopes typically follow a hierarchy from broad to specific:
Gmail example:
https://mail.google.com/ - Full Gmail access (read, send, delete)https://www.googleapis.com/auth/gmail.modify - Read and modify (but not delete)https://www.googleapis.com/auth/gmail.readonly - Read-only accesshttps://www.googleapis.com/auth/gmail.send - Send emails onlyDifferent providers use different scope formats and naming conventions:
Google uses URL-based scopes with hierarchical permissions:
Gmail Scopes
Read-only access:
https://www.googleapis.com/auth/gmail.readonlySend emails:
https://www.googleapis.com/auth/gmail.sendFull access:
https://mail.google.com/Modify (read/write, no delete):
https://www.googleapis.com/auth/gmail.modifyGoogle Calendar Scopes
Read-only calendar access:
https://www.googleapis.com/auth/calendar.readonlyManage calendar events:
https://www.googleapis.com/auth/calendar.eventsFull calendar access:
https://www.googleapis.com/auth/calendarGoogle Drive Scopes
Read-only access:
https://www.googleapis.com/auth/drive.readonlyPer-file access:
https://www.googleapis.com/auth/drive.fileFull drive access:
https://www.googleapis.com/auth/driveGoogle Sheets Scopes
Read-only sheets:
https://www.googleapis.com/auth/spreadsheets.readonlyEdit sheets:
https://www.googleapis.com/auth/spreadsheetsMicrosoft uses dotted notation with resource.permission format:
Outlook/Mail Scopes
Read mail:
Mail.ReadSend mail:
Mail.SendRead/write mail:
Mail.ReadWriteCalendar Scopes
Read calendar:
Calendars.ReadManage calendar:
Calendars.ReadWriteOneDrive Scopes
Read files:
Files.Read.AllRead/write files:
Files.ReadWrite.AllTeams Scopes
Read teams:
Team.ReadBasic.AllSend messages:
ChannelMessage.SendSlack uses simple string-based scopes:
Channel Scopes
Read channels:
channels:readManage channels:
channels:manageJoin channels:
channels:joinChat Scopes
Send messages:
chat:writeSend as user:
chat:write.customizeUser Scopes
Read user info:
users:readRead user email:
users:read.emailFile Scopes
Read files:
files:readWrite files:
files:writeAtlassian uses colon-separated scopes:
read:jira-work # Read issues and projectswrite:jira-work # Create and update issuesread:jira-user # Read user informationmanage:jira-project # Manage projectsScopes are configured at the connection level in Scalekit:
Gmail connection with multiple scopes:
// Dashboard configuration (for reference){ "connection_name": "gmail", "provider": "GMAIL", "scopes": [ "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.send", "https://www.googleapis.com/auth/gmail.modify" ]}Slack connection with workspace scopes:
// Dashboard configuration (for reference){ "connection_name": "slack", "provider": "SLACK", "scopes": [ "channels:read", "chat:write", "users:read", "files:read" ]}Verify which scopes a user has granted:
# Get connected account and check granted scopesaccount = actions.get_connected_account( identifier="user_123", connection_name="gmail")
print(f"Granted scopes: {account.scopes}")
# Check if specific scope is grantedrequired_scope = "https://www.googleapis.com/auth/gmail.send"if required_scope in account.scopes: print("✓ User granted email sending permission")else: print("✗ Email sending permission not granted") # Request re-authentication with required scope// Get connected account and check granted scopesconst account = await scalekit.actions.getConnectedAccount({ identifier: 'user_123', connectionName: 'gmail'});
console.log(`Granted scopes: ${account.scopes}`);
// Check if specific scope is grantedconst requiredScope = 'https://www.googleapis.com/auth/gmail.send';if (account.scopes.includes(requiredScope)) { console.log('✓ User granted email sending permission');} else { console.log('✗ Email sending permission not granted'); // Request re-authentication with required scope}// Get connected account and check granted scopesaccount, err := scalekitClient.Actions.GetConnectedAccount( context.Background(), "user_123", "gmail",)if err != nil { log.Fatal(err)}
fmt.Printf("Granted scopes: %v\n", account.Scopes)
// Check if specific scope is grantedrequiredScope := "https://www.googleapis.com/auth/gmail.send"hasScope := falsefor _, scope := range account.Scopes { if scope == requiredScope { hasScope = true break }}
if hasScope { fmt.Println("✓ User granted email sending permission")} else { fmt.Println("✗ Email sending permission not granted")}// Get connected account and check granted scopesConnectedAccount account = scalekitClient.actions().getConnectedAccount( "user_123", "gmail");
System.out.println("Granted scopes: " + account.getScopes());
// Check if specific scope is grantedString requiredScope = "https://www.googleapis.com/auth/gmail.send";if (account.getScopes().contains(requiredScope)) { System.out.println("✓ User granted email sending permission");} else { System.out.println("✗ Email sending permission not granted"); // Request re-authentication with required scope}When you need additional permissions, users must re-authenticate:
def ensure_required_scopes(identifier: str, connection_name: str, required_scopes: list): """ Ensure user has granted all required scopes. Returns True if all scopes granted, False if re-authentication needed. """ # Get current account and scopes account = actions.get_connected_account( identifier=identifier, connection_name=connection_name )
# Check if all required scopes are granted granted_scopes = set(account.scopes) missing_scopes = [s for s in required_scopes if s not in granted_scopes]
if not missing_scopes: print("✓ All required scopes granted") return True
print(f"⚠ Missing scopes: {missing_scopes}")
# Generate authorization link for re-authentication link_response = actions.get_authorization_link( connection_name=connection_name, identifier=identifier )
print(f"🔗 User must re-authorize with additional permissions:") print(f" {link_response.link}") print(f"\nMissing permissions:") for scope in missing_scopes: print(f" - {scope}")
return False
# Usagerequired_scopes = [ "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.send", "https://www.googleapis.com/auth/gmail.modify"]
if ensure_required_scopes("user_123", "gmail", required_scopes): # All scopes granted, proceed with operation result = actions.execute_tool(...)else: # Waiting for user to re-authenticate print("Please authorize additional permissions")async function ensureRequiredScopes(identifier, connectionName, requiredScopes) { /** * Ensure user has granted all required scopes. * Returns true if all scopes granted, false if re-authentication needed. */ // Get current account and scopes const account = await scalekit.actions.getConnectedAccount({ identifier, connectionName });
// Check if all required scopes are granted const grantedScopes = new Set(account.scopes); const missingScopes = requiredScopes.filter(s => !grantedScopes.has(s));
if (missingScopes.length === 0) { console.log('✓ All required scopes granted'); return true; }
console.log(`⚠ Missing scopes: ${missingScopes.join(', ')}`);
// Generate authorization link for re-authentication const linkResponse = await scalekit.actions.getAuthorizationLink({ connectionName, identifier });
console.log('🔗 User must re-authorize with additional permissions:'); console.log(` ${linkResponse.link}`); console.log('\nMissing permissions:'); missingScopes.forEach(scope => console.log(` - ${scope}`));
return false;}
// Usageconst requiredScopes = [ 'https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.modify'];
if (await ensureRequiredScopes('user_123', 'gmail', requiredScopes)) { // All scopes granted, proceed with operation const result = await scalekit.actions.executeTool(...);} else { // Waiting for user to re-authenticate console.log('Please authorize additional permissions');}func ensureRequiredScopes(identifier, connectionName string, requiredScopes []string) (bool, error) { // Get current account and scopes account, err := scalekitClient.Actions.GetConnectedAccount( context.Background(), identifier, connectionName, ) if err != nil { return false, err }
// Check if all required scopes are granted grantedScopes := make(map[string]bool) for _, scope := range account.Scopes { grantedScopes[scope] = true }
var missingScopes []string for _, scope := range requiredScopes { if !grantedScopes[scope] { missingScopes = append(missingScopes, scope) } }
if len(missingScopes) == 0 { fmt.Println("✓ All required scopes granted") return true, nil }
fmt.Printf("⚠ Missing scopes: %v\n", missingScopes)
// Generate authorization link linkResponse, err := scalekitClient.Actions.GetAuthorizationLink( context.Background(), connectionName, identifier, ) if err != nil { return false, err }
fmt.Printf("🔗 User must re-authorize: %s\n", linkResponse.Link)
return false, nil}public boolean ensureRequiredScopes(String identifier, String connectionName, List<String> requiredScopes) { try { // Get current account and scopes ConnectedAccount account = scalekitClient.actions().getConnectedAccount( identifier, connectionName );
// Check if all required scopes are granted Set<String> grantedScopes = new HashSet<>(account.getScopes()); List<String> missingScopes = requiredScopes.stream() .filter(s -> !grantedScopes.contains(s)) .collect(Collectors.toList());
if (missingScopes.isEmpty()) { System.out.println("✓ All required scopes granted"); return true; }
System.out.println("⚠ Missing scopes: " + String.join(", ", missingScopes));
// Generate authorization link AuthorizationLink linkResponse = scalekitClient.actions().getAuthorizationLink( connectionName, identifier );
System.out.println("🔗 User must re-authorize: " + linkResponse.getLink()); System.out.println("\nMissing permissions:"); missingScopes.forEach(scope -> System.out.println(" - " + scope));
return false; } catch (Exception e) { System.err.println("Error checking scopes: " + e.getMessage()); return false; }}Always validate scopes before executing tools to provide better error messages:
# Map tools to required scopesTOOL_SCOPE_REQUIREMENTS = { 'gmail_send_email': ['https://www.googleapis.com/auth/gmail.send'], 'gmail_fetch_mails': ['https://www.googleapis.com/auth/gmail.readonly'], 'gmail_delete_email': ['https://mail.google.com/'], 'calendar_create_event': ['https://www.googleapis.com/auth/calendar.events'], 'slack_send_message': ['chat:write'],}
def execute_tool_with_scope_check(identifier, connection_name, tool_name, tool_input): """Execute tool after validating required scopes""" # Get required scopes for this tool required_scopes = TOOL_SCOPE_REQUIREMENTS.get(tool_name, [])
if required_scopes: # Verify user has granted required scopes account = actions.get_connected_account( identifier=identifier, connection_name=connection_name )
granted_scopes = set(account.scopes) missing_scopes = [s for s in required_scopes if s not in granted_scopes]
if missing_scopes: raise PermissionError( f"Missing required permissions for {tool_name}: {missing_scopes}. " f"Please re-authorize to grant these permissions." )
# Scopes verified, execute tool return actions.execute_tool( identifier=identifier, tool_name=tool_name, tool_input=tool_input )
# Usagetry: result = execute_tool_with_scope_check( identifier="user_123", connection_name="gmail", tool_name="gmail_send_email", tool_input={"to": "user@example.com", "subject": "Test", "body": "Hello"} ) print("✓ Email sent successfully")except PermissionError as e: print(f"✗ Permission error: {e}") # Prompt user to re-authorizeGood:
# Only request scopes you needscopes = [ "https://www.googleapis.com/auth/gmail.readonly", # For reading emails "https://www.googleapis.com/auth/gmail.send" # For sending emails]Avoid:
# Don't request overly broad accessscopes = [ "https://mail.google.com/" # Full Gmail access including delete]Provide clear explanations of why you need specific permissions:
SCOPE_EXPLANATIONS = { "https://www.googleapis.com/auth/gmail.readonly": "Read your emails to analyze and summarize them", "https://www.googleapis.com/auth/gmail.send": "Send emails on your behalf", "https://www.googleapis.com/auth/calendar.events": "Create and manage calendar events for you", "chat:write": "Send messages in Slack channels",}
# Show explanations in your UI before redirecting to OAuthdef get_scope_explanation(scope): return SCOPE_EXPLANATIONS.get(scope, "Access your account data")# After OAuth callbackif user_denied_scopes: # Don't show error - explain what features won't work message = """ Some features will be limited because certain permissions weren't granted: - Email sending: Requires 'Send email' permission - Email reading: Requires 'Read email' permission
You can grant these permissions later in Settings. """ # Provide link to re-authorize in settingsRequest additional scopes only when needed:
# Start with minimal scopesinitial_scopes = ["https://www.googleapis.com/auth/gmail.readonly"]
# Later, when user wants to send emailif user_wants_to_send_email: # Request additional scope additional_scopes = ["https://www.googleapis.com/auth/gmail.send"] # Prompt user to grant additional permissionError: “Insufficient permissions” or 403 Forbidden
Solution:
Error: Invalid scope or scope not recognized
Solution:
Issue: OAuth consent shows different or additional permissions
Causes:
Solution: