Skip to docs content
Browse documentation
On this page

Connect Google

Connect Google Calendar, Gmail and Drive to Minion with your own OAuth client, avoid the 7-day Testing trap, and call the APIs.

Connecting Google lets your agents and packages call Google APIs such as Calendar, Gmail and Drive as you, without the token ever reaching their code.

Minion has a built-in Google preset. You create an OAuth client in Google Cloud, paste its client ID and secret into Minion, and approve the connection once. Minion stores the tokens encrypted, refreshes them, and only sends them to *.googleapis.com.

For a longer walkthrough of Gmail access, see /docs/google-oauth .

Before you start

  • You need to be an owner or admin of your Minion organization. Members can use an integration but can't add an app or connect one.
  • You need a Google account and access to the Google Cloud console . A personal OAuth client costs nothing.
  • The OAuth settings live under APIs & Services > Google Auth Platform, with the tabs Branding, Audience, Clients and Data Access. Older guides call this the "OAuth consent screen".
  • Gmail read scopes are "restricted" in Google's verification program. That is fine for a personal app that only you use.

Create the OAuth client on Google

  1. Open console.cloud.google.com and create or select a project.
  2. In APIs & Services > Library, enable each API you plan to call: Google Calendar API, Gmail API, Google Drive API.
  3. Open APIs & Services > Google Auth Platform and click Get started. Fill in Branding (app name, support email), choose External under Audience, and add contact details.
  4. On the Clients tab, click Create client. Choose Web application.
  5. Under Authorized redirect URIs, add exactly:
https://app.minion.codes/oauth/callback
  1. Create the client and copy the Client ID and Client secret.
  2. On the Data Access tab, add the scopes you plan to request.
  3. On the Audience tab, click Publish app to move it to In production.

Don't skip step 8. While an app is in Testing, Google expires its refresh tokens after seven days. The integration works during setup and then fails a week later. A published personal app shows an "unverified app" warning at consent. Click Advanced and continue. Verification only matters for apps you offer to other people.

If you keep the app in Testing anyway, add your own Google account as a test user on the Audience tab.

Add it in Minion

  1. Open app.minion.codes/app/integrations .
  2. In the OAuth apps card, set Provider to Google.
  3. Leave App name (optional) empty. It defaults to google.
  4. Paste the Client ID and Client secret.
  5. Set Default scopes (optional) to the full list you want, separated by spaces or commas. If you leave it empty, Minion requests only openid email. Keep openid email in your list so the Integrations page can show which account is connected. For example:
openid email calendar.readonly
  1. Leave API hosts empty. The preset allows *.googleapis.com, which covers www.googleapis.com, gmail.googleapis.com and the other Google API hosts.
  2. Click Save app.

You can type short scope names. Minion expands calendar.readonly to https://www.googleapis.com/auth/calendar.readonly before it sends you to Google. Full URLs work too.

Start small and widen only when a task needs it:

  • Calendar read: calendar.readonly, or the narrower calendar.events.readonly.
  • Drive read: drive.readonly.
  • Gmail read: gmail.readonly. Every Gmail read scope is restricted.
  • Gmail send only: gmail.send. It is sensitive, not restricted.
  • Gmail drafts: there is no drafts-only scope. gmail.compose allows drafts and sending.
  • Read-write: calendar or drive. These let code create and change events and files.

Every scope you request must also be listed on the Data Access tab.

Connect

  1. In the Integrations card on the same page, choose your app under OAuth app.
  2. Leave Name (optional) empty to call the integration google, or type another lowercase name such as google-work.
  3. Leave Extra scopes (optional) empty to use the app's default scopes. On a first connect, anything you type here replaces the defaults, so list every scope you need, including openid email.
  4. Click Connect, pick your Google account and approve.

Google sends you back to the Integrations page with Connected "google". The row shows status connected, your email, and a token expiry. Minion asks Google for offline access with a fresh consent each time, so it always gets a refresh token.

An agent can start the same flow with minion.integrations.connect({ provider: 'google', scopes: ['calendar.readonly'] }). It returns a connectUrl. An owner or admin has to open that link while signed in to Minion.

Check it works

Run this with execute. It reads up to three of your calendars and changes nothing.

export default async function main() {
  const res = await minion.integrations.fetch({
    name: 'google',
    url: 'https://www.googleapis.com/calendar/v3/users/me/calendarList?maxResults=3',
  })
  if (!res.ok) throw new Error(`Google returned ${res.status}: ${await res.text()}`)
  const data = await res.json()
  return { calendars: (data.items ?? []).map((c) => c.summary) }
}

For a Gmail connection, use https://gmail.googleapis.com/gmail/v1/users/me/profile instead. It returns your address and message count.

Use it in a package

In package code, import createAuthenticatedFetch from minion:runtime. It returns a fetch function bound to the integration. Minion adds the Authorization header and refreshes the token for you.

package.json:

{
  "name": "@your-org/google-tools",
  "exports": { "./calendars": "./src/calendars.ts" }
}

src/calendars.ts:

import { createAuthenticatedFetch } from 'minion:runtime'

const google = createAuthenticatedFetch('google')

export default async function main() {
  const res = await google('https://www.googleapis.com/calendar/v3/users/me/calendarList')
  if (!res.ok) throw new Error(`Google returned ${res.status}`)
  const data = await res.json()
  return (data.items ?? []).map((c) => ({ id: c.id, name: c.summary }))
}

Save and publish it with minion.packages.save and publish: true. Publishing needs an owner or admin and a README.md that says the package needs the google integration. Other code can only import a published version. Then call it from execute:

import calendars from 'minion:@your-org/google-tools/calendars'

export default async function main() {
  return calendars()
}

Replace your-org with your organization slug. See /docs/packages for saving and publishing.

Troubleshooting

  • Error 400: redirect_uri_mismatch on Google's consent page. The client's Authorized redirect URIs must include exactly https://app.minion.codes/oauth/callback. No trailing slash. The client type must be Web application.
  • It worked for a week, then the integration turned error. The app is still in Testing, so Google expired the refresh token. Publish the app on the Audience tab, then click Connect on the Integrations page.
  • 403 with ACCESS_TOKEN_SCOPE_INSUFFICIENT or "insufficient authentication scopes". The token lacks the scope for that API. Add the scope on the Data Access tab and to the app's Default scopes, or type it in Extra scopes and click Connect again.
  • 403 accessNotConfigured or "API has not been used in project". Enable that API in APIs & Services > Library for the same project.
  • "Google hasn't verified this app". Expected for a personal app. Click Advanced and continue.
  • An error saying integration "google" may not send its token to a host. The URL isn't on a *.googleapis.com host. Check the URL. To allow another host, save the app again with the full list in API hosts.
  • The integration turns error and says it needs a person to reconnect. You removed Minion's access in your Google account, or the refresh token stopped working. Minion fires an integration.auth.failed event with a reconnectUrl. Click Connect on the Integrations page. A package can subscribe to integration.auth.failed to alert you.

Disconnecting the integration in Minion also revokes the grant at Google.

For how OAuth integrations work in general, see /docs/oauth . To run code, see /docs/execute .