Skip to navigation

Google Calendar Integration

Connect Google Calendar with your own OAuth app so MeetStream bots join your meetings automatically
View as Markdown

Connect a Google Calendar once and MeetStream schedules a bot for your upcoming Google Meet, Zoom and Microsoft Teams meetings, then keeps those bots in sync as events move or get cancelled.

You bring your own OAuth app. MeetStream connects to Google Calendar with an OAuth client that you create in your own Google Cloud project (the dashboard calls this “BYOK”). You stay in control of the consent screen, the scopes and the credentials, and you can revoke access from Google at any time.

Before you start

  • A MeetStream API key that is active. Every calendar feature runs under your API key. Without one, the dashboard’s Calendar page shows “No active API key found. Create one on the API Keys page.” See Dashboard Setup.
  • Access to a Google Cloud project where you can enable APIs and create OAuth clients.
  • Pick how you will connect:
    • Dashboard (recommended). MeetStream runs the Google consent screen for you and stores the resulting refresh token. You only paste a Client ID and Client Secret.
    • API. You run the consent flow yourself, obtain a refresh token, and send all three values to POST /api/v1/calendar/create_calendar. See Connect with the API instead.

Step 1: Create the Google OAuth app

These steps use the Google Cloud console. Menu names below match Google’s current documentation for the Google Auth platform and OAuth client creation.

1

Enable the Google Calendar API

In the Google Cloud console, select (or create) the project you want to use. Go to Menu > APIs & Services > Library, search for Google Calendar API, open it and click Enable.

3

Add the calendar scopes (Data Access)

Go to Google Auth platform > Data Access, click Add or Remove Scopes, and add the four scopes MeetStream requests. They are read-only: MeetStream never writes to your Google Calendar.

ScopeWhy MeetStream needs it
https://www.googleapis.com/auth/calendar.events.readonlyRead events and their meeting links
https://www.googleapis.com/auth/calendar.readonlyRead the list of calendars on the account
https://www.googleapis.com/auth/userinfo.emailIdentify the account. The email becomes the connection’s account_id
https://www.googleapis.com/auth/userinfo.profileShow the account’s display name
4

Create the OAuth client (Web application)

Go to Google Auth platform > Clients > Create Client. Set Application type to Web application and give it a name (for example “MeetStream Calendar”).

Under Authorized redirect URIs, click Add URI and enter the redirect URI for the way you will connect:

You connect withAuthorized redirect URI
The MeetStream dashboardhttps://app.meetstream.ai/calendar/callback
The local refresh-token helper in this guidehttp://localhost:3000/api/google/oauth-callback

You can add both. Click Create, then copy the Client ID and Client secret.

The dashboard redirect URI must match exactly: https://, no trailing slash, all lowercase. A mismatch makes Google stop the flow with a redirect_uri_mismatch error before you ever reach MeetStream.

5

Add test users or publish the app

If you chose External, the app starts in Testing:

  • Add every Google account you plan to connect under Google Auth platform > Audience > Test users > Add users.
  • Google issues refresh tokens that expire after 7 days for External apps in Testing that request calendar scopes (Google OAuth 2.0 docs). When that happens, the calendar stops syncing until you reconnect.
  • For production use, click Publish app on the Audience page. The calendar scopes are sensitive scopes, so Google may ask you to complete app verification first.

Internal apps are not affected by the 7-day Testing limit.

Step 2: Connect Google Calendar in the dashboard

MeetStream dashboard Connect a calendar page with the redirect URI, a Google Calendar card and an Outlook Calendar card, each with Client ID, Client Secret and a Connect button
The Connect a calendar page: (1) redirect URI with Copy, (2) Setup guide links, (3) Client ID and Client Secret, (4) Connect buttons
1

Open Calendar and start a connection

In the dashboard sidebar, under Configure, click Calendar (1). With no calendars connected yet, the page shows Connect a calendar. Click + Connect calendar (2).

Calendars page with no connections, the Calendar sidebar item and the Connect calendar button highlighted
Calendar in the sidebar (1) and the Connect calendar button (2)

Already have a calendar connected? Use + Connect another account on the Calendars page instead.

2

Copy the redirect URI

Step 1 on the page, Add this redirect URI first, shows the exact value to register in Google Cloud (1). Click Copy (2) and make sure the same value is in your OAuth client’s Authorized redirect URIs.

Add this redirect URI first card showing https://app.meetstream.ai/calendar/callback and a Copy button
Copy the redirect URI and register it exactly in Google Cloud
3

Paste your Client ID and Client Secret

On the Google Calendar card (“Connect with Google OAuth (BYOK).”), paste the Client ID (2) and Client Secret (3) from Google Cloud. Setup guide (1) links back to this page. Click Connect Google (4).

Google Calendar card with the Setup guide link, Client ID and Client Secret fields, and the Connect Google button highlighted
Google Calendar card: Setup guide (1), Client ID (2), Client Secret (3), Connect Google (4)

The redirect URI registered on your OAuth client does not match https://app.meetstream.ai/calendar/callback exactly. Copy it again from the dashboard, paste it under Authorized redirect URIs, save, wait a minute and retry.

Both fields on the card must be filled in before you click Connect Google.

The Client ID and Secret are held in your browser tab while you visit Google. If you finished the Google step in a different tab or browser, or the tab was reloaded in between, start again from Calendar > + Connect calendar in the same tab.

You cancelled or denied the Google consent screen, or Google returned an error. Retry and approve access. For External apps in Testing, make sure the account is listed under Test users.

Your OAuth app is External and still in Testing, so Google expired the refresh token after 7 days. Publish the app (or use an Internal app), then reconnect the account.

Step 3: Turn on auto-join

On the Calendars page, the Auto-schedule bots card has a switch. When it is on, “MeetStream automatically schedules a bot for new meetings that have a join link.”

  • Click Default bot settings to set the bot name, image URL, join message, audio and video, and automatic leave timeouts used for calendar bots.
  • These defaults are saved in your browser and sent to MeetStream when you switch Auto-schedule bots on. After you change them, switch auto-join off and on again so the new defaults apply.
  • Click a connected account to see its events. Sync events pulls the latest from Google, and Schedule bots lets you schedule individual events by hand.

Prefer the API? Turn auto-join on with POST /api/v1/calendar/auto-schedule/enable (see Enable auto-join with the API).

How auto-join works

RuleWhat happens today
API keyAuto-join needs an active API key on your account. MeetStream uses it to create the bots. If the account has no active key, no bots are scheduled.
Which events get a botEvery synced event that has a meeting link and starts within the next 24 hours. There is no filter on RSVP status (declined events are included), organizer, or internal versus external attendees. If you need that kind of filtering, leave auto-join off and schedule events individually.
When MeetStream checksWhenever Google notifies MeetStream that a connected calendar changed, and once a day at 00:00 UTC. Each check looks 24 hours ahead, so a meeting next week gets its bot once it is less than 24 hours away. Events that were already on the calendar when you turned auto-join on are picked up at the next check.
When the bot joinsThe bot is dispatched about 2 minutes before the event start time so it is ready when the meeting opens. scheduled_join_time on the bot is the event start time.
One bot per meetingMeetStream schedules at most one bot per meeting occurrence per account. The meeting is identified by its link (host and path; query strings such as passcodes are ignored) plus its start time, so the same meeting on two connected calendars still gets one bot.
Event movedThe bot’s join time moves with the event.
Event deleted or cancelledThe bot is cancelled and its status becomes Cancelled.
Meeting link changedThe bot keeps the link it was scheduled with. Update it with PATCH /api/v1/calendar/scheduled_bots/{bot_id} (meeting_link), or unschedule and schedule the event again.
Recurring eventsOccurrences are booked one at a time. Each occurrence gets its own bot when it comes within the 24-hour window.
Account disconnectedScheduled bots for that account are cancelled.

MeetStream looks for a link in this order: the event’s Google Meet link (hangoutLink), a video entry in the conference data, URLs in the location, then meeting-platform URLs in the description.

PlatformRecognized hosts
Google Meetmeet.google.com
Zoomzoom.us and its subdomains (for example us02web.zoom.us), zoomgov.com and its subdomains
Microsoft Teamsteams.microsoft.com, teams.live.com

If an event has no recognized meeting link but its location contains some other URL (or its description links to a conferencing service MeetStream does not support), that URL can still be saved as the event’s link, and auto-join will try to send a bot to it. The bot then fails with an invalid meeting link error. Keep such URLs out of events on calendars that use auto-join, or unschedule those events.

Platform guides: Zoom, Google Meet, Microsoft Teams. To follow each bot’s progress, subscribe to webhooks; for every status and error, see the status and error code reference.

Transcripts for calendar bots. A transcription provider inside a calendar bot configuration is not applied when the bot is dispatched today. To get a transcript, run post-call transcription once the bot has finished: call POST /api/v1/bots/{bot_id}/transcribe after you receive audio.processed or bot.done. For live transcription or a provider applied at join time, create the bot yourself with create_bot and join_at (see Scheduling Bots).

Connect with the API instead

Use this path when you want to run the Google consent flow in your own product, for example to connect your end users’ calendars.

Get a refresh token with the local helper

This small Node.js helper runs the Google consent flow on your machine and prints the refresh token. Register http://localhost:3000/api/google/oauth-callback as an Authorized redirect URI on your OAuth client first.

  1. Create a .env file:
GOOGLE_CLIENT_ID=<YOUR_CLIENT_ID>
GOOGLE_CLIENT_SECRET=<YOUR_CLIENT_SECRET>
  1. Install dependencies:
npm install express googleapis dotenv
  1. Save this as server.js:
const express = require('express');
const { google } = require('googleapis');
require('dotenv').config();
const app = express();
const PORT = 3000;
const REDIRECT_URI = `http://localhost:${PORT}/api/google/oauth-callback`;
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
REDIRECT_URI
);
const scopes = [
'https://www.googleapis.com/auth/calendar.events.readonly',
'https://www.googleapis.com/auth/calendar.readonly',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];
app.get('/', (_req, res) => {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline', // required to receive a refresh token
scope: scopes,
prompt: 'consent', // forces Google to issue a new refresh token
include_granted_scopes: true,
});
res.redirect(authUrl);
});
app.get('/api/google/oauth-callback', async (req, res) => {
const { code, error } = req.query;
if (error) return res.status(400).send(`<h1>OAuth error</h1><p>${error}</p>`);
if (!code) return res.status(400).send('<h1>Missing authorization code</h1>');
try {
const { tokens } = await oauth2Client.getToken(code);
res.send(`
<h1>Success</h1>
<h3>Refresh token</h3>
<pre style="word-break:break-all">${tokens.refresh_token || 'Not returned: revoke access at https://myaccount.google.com/permissions and try again'}</pre>
<p>Copy the refresh token, then stop this server.</p>
`);
console.log('Refresh token:', tokens.refresh_token);
} catch (err) {
res.status(500).send(`<h1>Token exchange failed</h1><pre>${err.message}</pre>`);
}
});
app.listen(PORT, () => {
console.log(`Open http://localhost:${PORT} to start the OAuth flow`);
console.log(`Redirect URI: ${REDIRECT_URI}`);
});
  1. Run node server.js, open http://localhost:3000, sign in with the Google account to connect, and approve access. Copy the refresh token from the page.

Google returns a refresh token only on the first consent or after you revoke access. If none is returned, remove the app at myaccount.google.com/permissions and run the flow again.

Connect the account

curl -X POST "https://api.meetstream.ai/api/v1/calendar/create_calendar" \
-H "Authorization: Token <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"google_client_id": "<YOUR_GOOGLE_CLIENT_ID>",
"google_client_secret": "<YOUR_GOOGLE_CLIENT_SECRET>",
"google_refresh_token": "<YOUR_GOOGLE_REFRESH_TOKEN>"
}'
FieldRequiredDescription
google_client_idYesOAuth client ID from Google Cloud
google_client_secretYesOAuth client secret
google_refresh_tokenYesRefresh token for the Google account to connect
replaceNoSet true to reconnect an account that is already connected (for example to rotate credentials). Default false.

MeetStream validates the credentials with Google, reads the account’s email (this becomes the connection’s account_id), fetches its calendars, stores the credentials encrypted, and subscribes to change notifications for the calendars the account owns or can edit. Each call connects one Google account; call it again with another account’s refresh token to add more.

Example response (trimmed):

{
"calendar_id": "google_calendar_<user_id>_user@example.com",
"platform": "google_calendar",
"provider": "google",
"account_id": "user@example.com",
"is_replacement": false,
"user_email": "user@example.com",
"user_name": "Jane Smith",
"calendars": [
{ "id": "user@example.com", "summary": "My Calendar", "accessRole": "owner", "selected": true }
],
"primary_calendar_id": "user@example.com",
"watch_setup": { "success": true, "failed_calendars": [] },
"message": "Calendar connected successfully. Use /calendar/events and /calendar/schedule endpoints to manage events and bots."
}

Reconnecting the same account without replace returns 409:

{ "error": "Google account 'user@example.com' is already connected for this user. Pass {\"replace\": true} in the request body to reconnect." }

Calendar API reference

All endpoints use Authorization: Token <YOUR_API_KEY> and the base URL https://api.meetstream.ai. The same endpoints serve Google and Outlook connections; add ?provider=google&account_id=<email> to scope a call to one connected account.

List calendars

curl "https://api.meetstream.ai/api/v1/calendar/calendars" \
-H "Authorization: Token <YOUR_API_KEY>"

Returns the calendars of every connected account, read live from the provider. Each entry carries provider and account_id.

Sync and list events

curl "https://api.meetstream.ai/api/v1/calendar/events?limit=20" \
-H "Authorization: Token <YOUR_API_KEY>"
ParameterDefaultDescription
calendar_idprimary calendarCalendar to sync
time_minnow minus 1 dayStart of the window (ISO 8601)
time_maxnow plus 28 daysEnd of the window (ISO 8601)
syncautomatic"true" forces a sync from the provider
limit50Page size, 1 to 100
cursornoneValue of next from the previous page
provider, account_idall accountsScope to one connection (account_id requires provider)

Each result includes meeting_url, meeting_platform (GMeet, Zoom or Teams) and a bots array with any bots scheduled for the event:

{
"next": null,
"results": [
{
"id": "evt_abc123",
"start_time": "2026-10-08T15:00:00Z",
"end_time": "2026-10-08T16:00:00Z",
"calendar_id": "user@example.com",
"provider": "google",
"account_id": "user@example.com",
"meeting_platform": "GMeet",
"meeting_url": "https://meet.google.com/abc-defg-hij",
"is_deleted": false,
"bots": [
{
"id": "<bot_id>",
"status": "Scheduled",
"scheduled_join_time": "2026-10-08T15:00:00+00:00",
"platform": "GMeet",
"is_scheduled": true
}
]
}
],
"has_more": false
}

GET /api/v1/calendar/get_events returns events already synced to MeetStream without calling the provider, which is faster when you do not need the latest changes.

Schedule a bot for one event

curl -X POST "https://api.meetstream.ai/api/v1/calendar/schedule/evt_abc123" \
-H "Authorization: Token <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"bot_config": {
"bot_name": "Notetaker",
"audio_required": true,
"video_required": false,
"bot_message": "Hi, I am recording this meeting for notes.",
"callback_url": "https://your-domain.com/webhooks/meetstream",
"automatic_leave": { "waiting_room_timeout": 300, "everyone_left_timeout": 60 }
}
}'

bot_config takes the same bot fields as create_bot (the meeting link comes from the event). Calendar bots default to audio on and video off.

Body fieldDefaultDescription
occurrence_datenoneSchedule one specific occurrence of a recurring event (ISO 8601)
schedule_all_occurrencesfalseSchedule every upcoming occurrence of a recurring event at once
occurrence_limit52Maximum occurrences for schedule_all_occurrences
recurring_eventfalseWhen the bot for this occurrence is dispatched, schedule the next occurrence automatically

Response (trimmed):

{
"scheduled": true,
"bot_id": "<bot_id>",
"schedule_group": "gmeet",
"event_id": "evt_abc123",
"scheduled_time": "2026-10-08T14:58:00+00:00",
"is_recurring_occurrence": false
}

scheduled_time is when the bot is dispatched (about 2 minutes before the start). Scheduling the same event twice returns 409 with "error": "A bot is already scheduled for this event", the existing bot_id, and a hint to use PATCH instead. If your account has no active API key, the call returns 400 with “No API key found. Please ensure your account has an active API key.”

Unschedule an event

curl -X DELETE "https://api.meetstream.ai/api/v1/calendar/schedule/evt_abc123" \
-H "Authorization: Token <YOUR_API_KEY>"

For recurring events, pass {"cancel_all_occurrences": true} to cancel the whole series, optionally with "from_date" to cancel only occurrences from that date forward.

Manage scheduled bots

Method and pathWhat it does
GET /api/v1/calendar/scheduled_bots?limit=100List bots that are scheduled to join in the future (limit 1 to 100)
PATCH /api/v1/calendar/scheduled_bots/{bot_id}Update a scheduled bot, for example scheduled_join_time, meeting_link, bot_name, automatic_leave, callback_url or custom_attributes
DELETE /api/v1/calendar/scheduled_bots/{bot_id}Cancel one scheduled bot

Enable auto-join with the API

curl -X POST "https://api.meetstream.ai/api/v1/calendar/auto-schedule/enable" \
-H "Authorization: Token <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"default_bot_config": {
"bot_name": "Notetaker",
"audio_required": true,
"video_required": false,
"callback_url": "https://your-domain.com/webhooks/meetstream"
}
}'

If you omit default_bot_config, calendar bots use the name “MeetStream Calendar Bot” with audio on and video off. POST /api/v1/calendar/auto-schedule/disable turns auto-join off, and GET /api/v1/calendar/auto-schedule/settings returns auto_schedule_enabled and the stored default_bot_config. The rules in How auto-join works apply.

Disconnect an account

curl -X DELETE "https://api.meetstream.ai/api/v1/calendar/connections/google/user%40example.com" \
-H "Authorization: Token <YOUR_API_KEY>"

This removes the connection, stops change notifications for it and cancels its scheduled bots. Synced events are deleted too; add ?purge_events=false to keep them. GET /api/v1/calendar/connections lists every connected account. The older DELETE /api/v1/calendar/disconnect works when only one account is connected; with several, it returns 400 (“Multiple calendar connections present; refusing to disconnect all without explicit account_id”) unless you pass provider and account_id.

FAQ

Which calendar providers are supported?

Google Calendar and Microsoft Outlook (Microsoft 365 and Outlook.com). See Outlook Calendar Setup. You can connect several accounts, from either provider, to the same MeetStream account.

Do I need a Google Workspace account?

No. Personal Google accounts and Google Workspace accounts both work. An Internal OAuth app only works for accounts in your own Workspace organization; use External to connect other accounts.

Why did a bot join a meeting I declined?

Auto-join does not look at RSVP status today. Every event with a meeting link in the next 24 hours gets a bot. Use per-event scheduling if you need to skip declined or external meetings.

How early does the bot join?

The bot is dispatched about 2 minutes before the event’s start time. scheduled_join_time on the bot is the start time itself.

What happens if I reschedule or cancel a meeting in Google Calendar?

Google notifies MeetStream. A moved event moves its bot’s join time; a deleted or cancelled event cancels its bot (Cancelled).

No. Events need a recognized Google Meet, Zoom or Microsoft Teams link. Events without one are shown in the dashboard but cannot be scheduled.

Can different meetings use different bot settings?

Yes. Auto-join uses your default bot settings for every meeting. To use different settings for one event, schedule it yourself with POST /api/v1/calendar/schedule/{event_id} and its own bot_config before auto-join picks it up, or update the scheduled bot with PATCH.

How is my calendar data protected?

OAuth credentials are stored encrypted and are never returned by the API. MeetStream requests read-only calendar scopes and never stores your Google password. You can revoke access at any time from your Google account or by disconnecting in MeetStream.

For webhook handling, see Webhooks and Events. To create a bot without a calendar, see Create your first bot.