Skip to main content
Connector ID: google · Type: oauth_pkce · Catalog entry: src/gaia/connectors/catalog/google.py

What you’ll need

Google requires every desktop app — including GAIA running locally — to identify itself with an OAuth client that you create in your own Google Cloud project. This sounds heavy, but for a single-developer machine it takes about five minutes and is free. You will do three things in the Google Cloud Console — enable the APIs you want GAIA to call, register yourself as a test user, and create an OAuth client — then paste two values into the GAIA Agent UI: a Client ID and a Client Secret. After that, GAIA stores them encrypted in your OS keyring and you never need to think about them again.
Bringing your own OAuth client is a temporary step. A future GAIA release (#2104) ships a verified AMD-managed client so end users skip the Cloud Console entirely and just click Connect. Until then, the steps below are required for personal accounts.
Google restructured this console in 2024–2025. The OAuth settings that used to live under APIs & Services → OAuth consent screen and → Credentials now live under Google Auth Platform, split across three tabs: Branding, Audience, and Clients. This guide uses the new layout with direct links; the old menu path is noted only as a fallback in case Google shifts the UI again.

Step 1 — Create a Google Cloud project

If you already have a project you use for hobby projects, skip ahead. Otherwise:
  1. Go to the Google Cloud Console.
  2. Click the project dropdown at the top → New project.
  3. Name it something like gaia-personal and click Create.
  4. Make sure the new project is selected in the dropdown before continuing — every step below applies to the selected project.

Step 2 — Enable the APIs you want to use

This step is mandatory, not optional. GAIA can only call the Google APIs you have explicitly enabled on the project. If you skip it, the OAuth flow still succeeds — but the first mailbox call fails with a raw Google 403 (Gmail API has not been used in project … or it is disabled), which is confusing because the connection looks healthy (#2116).
Enable each API you plan to use: For each one, open the link (with your project selected), then click Enable. You can also reach these from APIs & Services → Library and search by name.
Enabling an API takes 1–2 minutes to propagate. If your first request 403s right after enabling, wait a minute and retry before assuming something else is wrong.
Google requires you to fill in a consent-screen form before it will issue OAuth credentials, even for personal-use apps. In the new console this is the Google Auth Platform.
  1. Go to Google Auth Platform (old path: APIs & Services → OAuth consent screen). On a fresh project you’ll see a Get started button — click it to enter the short setup wizard.
  2. Branding — fill in the required fields:
    • App name: GAIA Personal (or whatever)
    • User support email: your email
    • Developer contact email: your email
  3. Audience — pick External.
A personal @gmail.com account has no “Internal” option — Internal is only available to Google Workspace organizations. External is the correct (and only) choice for a personal account. An External app starts in Testing publishing status, which is exactly what you want for personal use.

Add yourself as a test user

While the app is in Testing publishing status (the default), only listed test users can authenticate. Add your own Google account:
  1. On the Google Auth Platform → Audience tab, scroll to Test users → Add users.
  2. Enter your Google email and Save.
You can stay in Testing mode indefinitely if you only use this for yourself — there’s no need to verify the app for personal use.
Test-mode refresh tokens expire after ~7 days. While the app stays in Testing status, Google expires the refresh token roughly a week after consent, so GAIA will eventually prompt you to reconnect. This is a Google policy for unverified test apps, not a GAIA bug — just reconnect when it happens (see Common issues). Google waives this limit only when the connection’s only scopes are a subset of openid / email / profile — any Gmail or Calendar scope (which GAIA’s agents always request) means the 7-day limit applies to you.

Declare scopes under Data access

Every scope GAIA can request must also be declared on the consent screen — a separate step from anything you type into a gaia command, and easy to miss because nothing in the flow above mentions it.
Exact click path not verified for this guide — flagged for a human pass, tracked in #2602. Open Google Auth Platform → Data access and use its scope picker to add each entry from the table below. If the tab’s exact label or control has moved since this was written, look for wherever the console lets you register OAuth scopes for the consent screen — the goal is that every scope your agents will request ends up listed there.
You only need to declare the scopes the agents you actually use will request — the Email Triage agent needs gmail.modify, gmail.send, calendar.events, and calendar.readonly; openid / email / profile are requested on every connection regardless of agent. Tiers, sourced from Google’s own tables: Gmail scopes and Drive scopes. Two corrections worth flagging if you’ve seen older guidance: gmail.readonly and drive.readonly are Restricted — the same strictest tier as gmail.modify — not “Sensitive”; read-only buys no verification relief. drive.file is Non-sensitive, which is why Google recommends it as the default per-file Drive scope. Google publishes no tier table for Calendar; calendar.events / calendar.readonly are listed as Sensitive based on the Console UI’s own grouping, not an authoritative Google document. A Testing-status app shows the “Google hasn’t verified this app” warning for any Sensitive or Restricted scope regardless of whether it’s declared here — so seeing that warning doesn’t by itself tell you a scope is missing from Data access. Unverified: whether Google actually refuses OAuth consent in Testing mode for a scope that’s requested in the authorization URL but not declared under Data access has not been checked end-to-end for this guide — tracked in #2602. Declaring every scope you’ll use removes the question either way, so do that regardless of the answer.

Step 4 — Create the OAuth client

This is the credential GAIA uses.
  1. Go to Google Auth Platform → Clients (old path: APIs & Services → Credentials) → Create client.
  2. Application type: Desktop app.
  3. Name: GAIA (or whatever you want).
  4. Click Create. A dialog shows the Client ID and Client Secret. Copy both — you’ll paste them into GAIA in a moment. You can also download the JSON.
Even though “Desktop app” clients are technically public (the secret isn’t a real secret in the cryptographic sense), Google’s token endpoint requires client_secret to be present in every exchange. Don’t omit it.

Step 5 — Paste credentials into GAIA

  1. Launch the Agent UI: gaia chat --ui.
  2. Click Settings (gear) → Connections.
  3. Click the Google tile to expand it.
  4. Paste the Client ID and Client Secret from Step 4.
  5. Click Save & Connect.
Already past first-time setup (client came from env vars, or you need to rotate it)? Expand the tile’s collapsible OAuth client section — it shows which client GAIA currently resolves (saved / environment / none) and accepts a replacement Client ID + Secret at any time. Save client persists to the OS keyring without starting a sign-in; saved credentials take precedence over GAIA_GOOGLE_CLIENT_ID / GAIA_GOOGLE_CLIENT_SECRET, exactly like gaia connectors configure google. GAIA will:
  1. Store the credentials in your OS keyring (macOS Keychain on Mac, gnome-keyring/kwallet on Linux, Credential Locker on Windows).
  2. Open Google’s consent screen in your default browser.
  3. Receive the callback on a temporary loopback server.
  4. Exchange the auth code for a refresh token (also stored in the keyring).
During authorization you’ll hit a “Google hasn’t verified this app” interstitial. This is expected for a personal test app — click Advanced → Continue (or Go to GAIA Personal (unsafe)) to proceed. The warning only appears because your app is unverified, not because anything is wrong.
The tile flips to Connected as [email protected] once the flow completes.
Prefer the CLI? The whole flow works headless without opening the Agent UI — gaia connectors configure google --client-id … --client-secret … then gaia connectors connect google. See the Email Triage guide’s CLI tab for the exact commands.

Step 6 — Grant scopes to specific agents

Each agent must be granted the specific scopes it needs — a connection alone is not enough. In the Agent UI this happens during connect: the Google tile’s connect panel shows “Grant access to these agents when you connect” with a default-on checkbox per installed agent that declares Google access, and checked agents receive their declared scopes the moment the OAuth flow completes. Uncheck an agent to withhold its grant, and adjust or revoke any grant later under the tile’s Per-agent grants section. On the CLI, lead with --grant-agent — GAIA reads the scopes the agent already declares in its own code, so there’s no list to type out or keep in sync: Email Triage agent — the full mailbox + calendar scope set it needs to read, organize, draft/send, and manage events:
One command both authorizes those scopes with Google and grants them to the agent — the same thing the Agent UI’s Connect button does. Naming the agent is enough; GAIA rejects --grant-agent outright for an agent that declares no Google scopes, rather than silently granting nothing. Chat agent — a narrower, read-only Gmail grant: the chat agent declares no fixed Google requirement, so grant it explicit scopes instead:
gmail.readonly is a Restricted scope — the same tier as gmail.modify (see Scopes to declare in your Console). Read-only narrows what the agent can do, not which verification tier Google applies.
--scopes is a two-command path, and it does not union with the connection’s default scopes. Authorize with gaia connectors connect google --scopes … first, then grant the same list with gaia connectors grants grant google <agent> --scopes … (the grants grant example above assumes the scope was already authorized on a prior connect). A hand-typed list that omits openid can still connect but resolve to Connected as default instead of your real address — one reason --grant-agent is the recommended path for any agent that supports it. The Email Triage guide’s CLI tab has the full two-command sequence; a missing grant either way surfaces as AGENT_NOT_GRANTED.
The default scopes a connection is established with, and every scope an agent may request, are listed in the spec at src/gaia/connectors/catalog/google.py.

Common issues

Gmail API has not been used in project … or it is disabled (403)

You skipped Step 2, or the enablement hasn’t propagated yet. Enable the Gmail API (and Calendar API if you use calendar) on the selected project, wait 1–2 minutes, and retry. The OAuth connection itself is fine — only the API call is blocked (#2116).

AGENT_NOT_GRANTED — an agent can’t use your connection

Connecting Google is not the same as granting a specific agent access to it (Step 6). Until the per-agent grant lands, the agent surfaces:
Fix it in the UI (Google tile → Per-agent grants) or run the gaia connectors grants grant google installed:email … command from Step 6.

redirect_uri_mismatch

You probably picked Web application instead of Desktop app in Step 4. Delete the OAuth client and create a new one with the right type — Web application clients require a registered redirect URI, which GAIA can’t provide because it picks an ephemeral port.

Access blocked: ... has not completed Google verification

Add yourself as a test user (Step 3, “Add yourself as a test user”). Apps in Testing mode only allow listed test users.

Something went wrong / accounts.google.com/info/unknownerror after you click Allow

Google’s own opaque failure page, shown after you complete the consent screen — not the “Google hasn’t verified this app” interstitial from Step 5. GAIA never sees why: the browser lands on Google’s error page instead of coming back to GAIA’s loopback callback, so the callback handler never runs and the CLI/UI just times out after two minutes (OAuth flow ... timed out after 120s. Restart the flow.) — that message will not name the real cause. Nothing found while researching this guide ties this error page authoritatively to one cause for a loopback (Desktop-app) OAuth flow. Check these, roughly in order of how often they’re reported for this exact error text:
  1. Third-party cookies blocked in the browser. The most concrete reported match for this literal error page — though most reports are for Google Identity Services JS sign-in widgets embedded in a web page, not a Desktop-app loopback redirect, so treat this as a lead rather than a confirmed cause here. Allow third-party cookies for accounts.google.com and retry.
  2. A stale or mismatched OAuth parameterredirect_uri, state, or PKCE code_challenge that no longer matches what GAIA sent. Restart the flow from GAIA rather than re-using an old consent tab.
  3. Double-clicking Allow, or finishing consent in a second tab/window after the original flow’s state already expired. Restart the flow and complete it in a single tab.
  4. A transient Google-side incident. Wait a few minutes and retry.
Unverified: whether an undeclared Restricted or Sensitive scope (missing from Data access) can itself trigger this page has not been confirmed end-to-end — see the note in that section. Don’t assume adding a Data access declaration fixes this without testing it.

Error 400: invalid_grant after a long while

Refresh tokens expire if your app stays in Testing mode for more than seven days without use. Reconnect from the Agent UI’s Connections panel — it will issue a new refresh token.

client_secret is missing

You either skipped pasting the Client Secret in Step 5, or the connection blob in your keyring predates a GAIA upgrade. Disconnect and reconnect from the UI to reset.

Revoking access

Two places — both work:
  • From GAIA: Settings → Connections → Google → Disconnect. Removes the refresh token from the keyring; the next API call errors with NOT_CONNECTED.
  • From Google: myaccount.google.com/permissions → find your OAuth client → Remove access. Useful if you’ve lost the laptop the keyring lives on.

See also