Skip to content

Reference

Google

Set up Google OAuth for Gmail, Calendar, Drive, and more

Connect your users to Google Workspace services including Gmail, Google Calendar, Google Drive, and Google Sheets.

PropertyValue
Provider IDgoogle
CategoryProductivity
PKCESupported
Token refreshAutomatic (refresh token reused)
Access token lifetime~1 hour
Redirect URIShown in Developer Portal

Go to Google Cloud Console

Navigate to the Google Auth Platform Clients page and sign in with your Google account.

Create or select a project

Click the project dropdown at the top and select New Project. Give it a name (e.g., “My App - Alter Integration”) and click Create.

Enable APIs

Go to APIs & Services > Library and enable the APIs you need:

  • Gmail API - for email access
  • Google Calendar API - for calendar access
  • Google Drive API - for file access
  • Google Sheets API - for spreadsheet access

Open Clients page

Go to the Google Auth Platform > Clients page and click Create Client.

Configure consent screen (first time only)

If prompted, configure the OAuth consent screen:

  • User Type: External (unless you only need Google Workspace users)
  • App name: The application name
  • User support email: The operator’s support email
  • Authorized domains: The application’s domain
  • Developer contact: The operator’s contact email

Click through the remaining steps and Save.

Create OAuth client ID

  • Application type: Web application
  • Name: e.g., “Alter Vault Integration”
  • Authorized redirect URIs: Copy the Redirect URI from the Developer Portal
  • Click Create

Download credentials immediately

Download and securely store the Client ID and Client Secret from the dialog. The secret is only shown once at creation time.

The Client ID looks like: 123456789-abcdefg.apps.googleusercontent.com

Open the Developer Portal

Go to portal.alterauth.com and navigate to the application.

Add Google provider

Go to OAuth Providers > Add Provider > Google.

Enter credentials

  • Client ID: Paste your Google Client ID
  • Client Secret: Paste your Google Client Secret

Select scopes

Choose the scopes the application needs. See the Available Scopes section below.

Save

Click Save. The provider is now active and ready to use.

After a user connects via Alter Connect, use the returned grant_id to make API calls:

from alter_sdk import App, HttpMethod
async with App(
api_key="alter_key_...",
caller="my-agent",
) as alter_app:
# Use the grant_id returned by Alter Connect
response = await alter_app.request(
HttpMethod.GET,
"https://www.googleapis.com/calendar/v3/calendars/primary/events",
grant_id=grant_id,
query_params={"maxResults": "5"},
)
events = response.json()
print(f"Found {len(events.get('items', []))} events")
ScopeDescription
openidAuthenticate your identity with OpenID Connect
profileView your basic profile information
emailView your email address
ScopeDescription
https://www.googleapis.com/auth/gmail.readonlyView your email messages and settings
https://www.googleapis.com/auth/gmail.sendSend email on your behalf
https://www.googleapis.com/auth/gmail.composeManage drafts and send emails
https://www.googleapis.com/auth/gmail.modifyRead, compose, send, and permanently delete all your email from Gmail
ScopeDescription
https://www.googleapis.com/auth/calendar.readonlyView your calendars
https://www.googleapis.com/auth/calendar.eventsView and edit events on all your calendars
ScopeDescription
https://www.googleapis.com/auth/drive.readonlyView your Google Drive files
https://www.googleapis.com/auth/drive.fileView and manage Google Drive files that you have opened with this app
https://www.googleapis.com/auth/driveSee, edit, create, and delete all of your Google Drive files
Use CaseMethodURL
List calendar eventsGEThttps://www.googleapis.com/calendar/v3/calendars/primary/events
List Gmail messagesGEThttps://www.googleapis.com/gmail/v1/users/me/messages
Send emailPOSThttps://www.googleapis.com/gmail/v1/users/me/messages/send
List Drive filesGEThttps://www.googleapis.com/drive/v3/files
  • Google requires access_type=offline and prompt=consent to issue refresh tokens. Alter Vault handles this automatically.
  • If requesting profile or email scopes, the openid scope is automatically included.
  • Refresh tokens never expire unless the user revokes access or the token is inactive for 6 months.
  • Google enforces a limit of ~50 refresh tokens per user per app. Alter Vault’s connection deduplication prevents hitting this limit.
  • See the Google OAuth 2.0 overview and web-server flow guide for details.

Content rules and conditional approvals can judge these parameters of Google requests:

ParameterOperationsMeaning
recipientsGmail send operationsEvery To, Cc, Bcc, and Resent address in the message
recipientscalendar.events.insert, update, patch, importWho the request may invite or email: every attendee address the event lists. An update or patch is judged only when sent with sendUpdates=none and no sendNotifications other than false (see below)
recipientscalendar.events.delete, move, quickAddWho the request may email. Empty when a delete or move is sent with sendUpdates=none and no sendNotifications other than false; otherwise it cannot be judged (see below)
recipientscalendar.acl.insert, update, patchThe person or group the calendar is shared with: the body scope.value on insert, the rule id on update and patch
calendarIdevery Calendar operation whose path names a calendar (calendar.events, calendar.acl, calendar.calendars, calendar.calendarList)The calendar the request acts on, as its path names it (primary, an address or a calendar id)
eventId, ruleId, settingevery Calendar operation whose path names oneThe event, sharing rule or setting the request acts on, as its path names it
destinationcalendar.events.moveThe calendar a move hands the event to
senderGmail send operationsThe From address
subjectGmail send operationsThe Subject header (redactable)
event_timecalendar.events.insert, update, patch, importThe event’s start-to-end span, judged with outside_windows

recipients means who the request may email or invite, so one rule on it can limit both who an agent can email through Gmail and who it can invite, notify or share with through Calendar. Every Google operation in the send family carries it. Which calendar a request writes to is a separate question, governed by calendarId and, for a move, destination.

  • calendarId rule. Name the operations whose calendarId is the calendar written to: calendar.events.insert, import and quickAdd, which create an event there, and update, patch and delete, which change an event that lives there.
  • destination rule. A move’s calendarId is the calendar it moves the event out of, so where a move goes needs its own destination rule.

The user’s address names their primary calendar just as primary does, and both compare case-insensitively.

These two rules limit which calendars an agent writes events into or moves them to. They do not stop an event from reaching another calendar through its guest list. Google lets a calendar be invited as an attendee: a group calendar (whose id ends in group.calendar.google.com), or a resource calendar that accepts invitations automatically. Such an attendee is a recipient, so it is stopped only by a recipients rule like the third one below. Use all three to keep an agent’s events within the user’s own calendars and the organization’s addresses:

{
"match": {"operations": ["calendar.events.insert", "calendar.events.import", "calendar.events.quickAdd",
"calendar.events.update", "calendar.events.patch", "calendar.events.delete"]},
"params": [{"name": "calendarId", "op": "not_subset_of", "value": ["primary", "alice@example.com"]}],
"effect": "deny"
}
{
"match": {"operations": ["calendar.events.move"]},
"params": [{"name": "destination", "op": "not_subset_of", "value": ["primary", "alice@example.com"]}],
"effect": "deny"
}
{
"match": {"operations": ["calendar.events.insert", "calendar.events.import",
"calendar.events.quickAdd", "calendar.events.update", "calendar.events.patch"]},
"params": [{"name": "recipients", "op": "not_subset_of", "value": ["*@example.com"]}],
"effect": "deny"
}

The third rule names calendar.events.quickAdd because a quick add can invite anyone its text names, including a calendar. Its recipients can never be judged (see below), so this rule denies every quick add.

A query parameter named like a path parameter (?calendarId=primary on a request whose path names another calendar) does not stand in for it: the two disagree, so a condition on that parameter cannot be judged and denies. A Calendar request whose path is percent-encoded twice (%25) cannot be judged and is denied by any rule with conditions. Google decodes a path once, so judging the twice-decoded calendar or rule id would judge a value Google never reads.

Requests whose recipients or span cannot be judged:

  • Updates and patches that email guests. An update or patch emails every guest it removes (a cancellation) and every guest it keeps. The request lists only the guests the event will have afterwards. Unless it is sent with sendUpdates=none and no sendNotifications other than false (Google sends no email then), its recipients cannot be judged. Such a silent patch that does not list attendees emails and invites nobody, so its recipients are empty.
  • Deletes and moves that email guests. A delete or move emails the event’s current guests, none of whom the request names. With sendUpdates=none and no sendNotifications other than false, its recipients are empty.
  • Auto-declining out-of-office and focus time. An event whose outOfOfficeProperties or focusTimeProperties sets an autoDeclineMode other than declineNone, or a declineMessage, declines the meetings it overlaps, and each decline goes to that meeting’s organizer, none of whom the request names.
  • Setting a response. An update or patch where any attendee carries responseStatus or comment can notify the event’s organizer, whom the request need not name. The attendee’s self and organizer flags do not change this: Google sets them itself, so a request’s values for them are not trusted.
  • Quick add. calendar.events.quickAdd lets Google read guests and the time from free text, so its recipients can never be judged and it has no event_time.
  • Partial attendee lists. An event with attendeesOmitted: true lists only some guests.
  • Sharing with a domain or the public. A domain or default scope (on insert) or rule id (on update or patch) is not a set of people.
  • Sharing-rule updates and patches whose rule id cannot be read. An update or patch changes the rule its id names (user:a@example.com, group:…, domain:…, or default), so the grantee is taken from the id. Its recipients cannot be judged if the id is in any other form: a patch, or an update that carries a scope.
  • Patches that change one end. A patch that names only start or only end keeps the other end, which the request does not show.
  • Recurring events. An event with recurrence rules repeats beyond the span the request shows.
  • Updates and patches that do not state recurrence. A patch keeps the edited event’s recurrence, and Google does not document what an update without recurrence does to a recurring event, so event_time cannot be judged for either. Including "recurrence": [] lets it be judged: Google replaces an array a patch names, and an update replaces the whole event, so the written event does not repeat. A single occurrence of a recurring event has no recurrence field, and Google does not document whether it accepts one. A change to a single occurrence that does not state recurrence is therefore treated like any other undetermined span.
  • Updates and patches that name neither end. An update or patch without start or end keeps the stored time, which the request does not show.

Some sharing-rule requests are refused outright as unclassifiable, whatever the rule: an insert without a scope, an update without a scope (Google requires one), and an update or patch whose body scope names someone other than its rule id.

A deny, redact or step-up rule with a condition that cannot be judged denies the request. A conditional approval sends it to an approver instead. The approval page says which condition was not evaluated and why. It shows the values the request carries as it sent them, with percent-encoding decoded:

  • For an event write: the calendar, the listed guests, attendeesOmitted, start and end with their time zones, recurrence, sendUpdates and sendNotifications, and, when the request sets them, the auto-decline settings and each attendee response it sets (the attendee’s address with its responseStatus and comment).
  • For a sharing rule: the rule id, role and scope.
  • For a quick add: the text.
  • For a move: the source calendar and destination.

Every Calendar request also lists its query and body as sent, field by field (query.sendUpdates, body.status, body.attendees[0].responseStatus, body.address on a watch, …), whether or not a condition was undetermined: the policy reads only part of the request, and an approval covers everything that executes.

Values outside the request are not shown, and the page says so: the event’s current guests, the organizers an auto-decline or a response notifies, the stored time of a patch that names one end or neither, or the stored scope of a sharing-rule patch. The page never shows the rule’s own address patterns: only whether each listed address matches them.

Each listed address is checked against the rule’s addresses, and the result is shown next to it. An address containing any non-ASCII character is marked as such. A body grantee whose rule id cannot be read is shown as not judged, never as inside the list. Against an allowlist (not_subset_of), a look-alike address, such as one using a Cyrillic letter, is shown as outside the list. Against a blocklist (any_in), it is shown as not matching, so a blocklist does not catch look-alikes; the non-ASCII mark is the only signal.

If the values in the request cannot be shown, the request cannot be approved. A condition on a parameter the operation does not carry at all, such as recipients on calendar.events.list, still denies. Query parameters such as sendUpdates are also available to conditions. See Calendar events only during business hours for business-hours and outside-invitee examples.

Alter policy rules can target these attested operations and families for operation-level and parameter-aware controls.

Operation IDFamiliesMethodProvider path
gmail.users.messages.sendsendPOST/gmail/v1/users/{userId}/messages/send
gmail.users.drafts.sendsendPOST/gmail/v1/users/{userId}/drafts/send
gmail.users.messages.listreadGET/gmail/v1/users/{userId}/messages
gmail.users.messages.getreadGET/gmail/v1/users/{userId}/messages/{id}
gmail.users.messages.deletedeleteDELETE/gmail/v1/users/{userId}/messages/{id}
gmail.users.messages.trashwritePOST/gmail/v1/users/{userId}/messages/{id}/trash
gmail.users.drafts.createwritePOST/gmail/v1/users/{userId}/drafts
gmail.users.drafts.listreadGET/gmail/v1/users/{userId}/drafts
gmail.users.labels.listreadGET/gmail/v1/users/{userId}/labels
gmail.users.getProfilereadGET/gmail/v1/users/{userId}/profile
calendar.events.insertsend, writePOST/calendar/v3/calendars/{calendarId}/events
calendar.events.updatesend, writePUT/calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.patchsend, writePATCH/calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.importwritePOST/calendar/v3/calendars/{calendarId}/events/import
calendar.events.quickAddsend, writePOST/calendar/v3/calendars/{calendarId}/events/quickAdd
calendar.events.movesend, writePOST/calendar/v3/calendars/{calendarId}/events/{eventId}/move
calendar.events.deletedelete, sendDELETE/calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.listreadGET/calendar/v3/calendars/{calendarId}/events
calendar.events.getreadGET/calendar/v3/calendars/{calendarId}/events/{eventId}
calendar.events.instancesreadGET/calendar/v3/calendars/{calendarId}/events/{eventId}/instances
calendar.events.watchread, writePOST/calendar/v3/calendars/{calendarId}/events/watch
calendar.calendars.insertwritePOST/calendar/v3/calendars
calendar.calendars.getreadGET/calendar/v3/calendars/{calendarId}
calendar.calendars.updatewritePUT/calendar/v3/calendars/{calendarId}
calendar.calendars.patchwritePATCH/calendar/v3/calendars/{calendarId}
calendar.calendars.deletedeleteDELETE/calendar/v3/calendars/{calendarId}
calendar.calendars.cleardeletePOST/calendar/v3/calendars/{calendarId}/clear
calendar.calendars.transferOwnershipadminPOST/calendar/v3/calendars/{calendarId}/transferOwnership
calendar.acl.insertadmin, sendPOST/calendar/v3/calendars/{calendarId}/acl
calendar.acl.updateadmin, sendPUT/calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.patchadmin, sendPATCH/calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.deleteadmin, deleteDELETE/calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.listreadGET/calendar/v3/calendars/{calendarId}/acl
calendar.acl.getreadGET/calendar/v3/calendars/{calendarId}/acl/{ruleId}
calendar.acl.watchread, writePOST/calendar/v3/calendars/{calendarId}/acl/watch
calendar.calendarList.insertwritePOST/calendar/v3/users/me/calendarList
calendar.calendarList.updatewritePUT/calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.patchwritePATCH/calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.deletedeleteDELETE/calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.listreadGET/calendar/v3/users/me/calendarList
calendar.calendarList.getreadGET/calendar/v3/users/me/calendarList/{calendarId}
calendar.calendarList.watchread, writePOST/calendar/v3/users/me/calendarList/watch
calendar.settings.listreadGET/calendar/v3/users/me/settings
calendar.settings.getreadGET/calendar/v3/users/me/settings/{setting}
calendar.settings.watchread, writePOST/calendar/v3/users/me/settings/watch
calendar.colors.getreadGET/calendar/v3/colors
calendar.freebusy.queryreadPOST/calendar/v3/freeBusy
calendar.channels.stopwritePOST/calendar/v3/channels/stop

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.