MeetStream Guide: Outlook Calendar Integration & Auto-Scheduling

View as Markdown

This guide explains how to connect Microsoft Outlook / Microsoft 365 Calendar accounts to MeetStream so bots can automatically join your meetings — no manual API calls required.

Applies to: Google Meet, Zoom, Microsoft Teams meetings on your Outlook Calendar. Support: docs.meetstream.ai | API: api.meetstream.ai


What you get with Calendar Integration

Once connected, MeetStream can:

  • Connect many Microsoft accounts per user — one MeetStream user can attach a personal Outlook.com account, a work Microsoft 365 tenant, a contractor account in a third tenant, and so on. Up to 200 per user (Google + Outlook combined).
  • See upcoming meetings across every connected account — synced directly from Microsoft Graph with per-account delta tokens for speed.
  • Schedule bots for specific meetings — one click or one API call.
  • Auto-schedule bots for all meetings — hands-free, every day, across every connected account.
  • Handle recurring meetings — automatically reschedule bots for the next occurrence.
  • React to calendar changes in real time — if a meeting is rescheduled, the bot’s join time updates automatically; if a meeting is cancelled, the bot is cancelled too. Notifications are routed per-account so one tenant’s churn never disturbs another’s.
  • Manage scheduled bots — list, update, or delete scheduled bots at any time.
  • Disconnect one account at a time — remove a single Microsoft account without touching the others.
  • Keep subscriptions alive — Microsoft Graph change notification subscriptions are automatically renewed before they expire.

Mental model. A MeetStream user owns a set of calendar connections. Each connection is uniquely identified by a (provider, account_id) pair — for Outlook, account_id is the account’s primary email (e.g. jane@acme.com) as returned by Microsoft Graph’s me endpoint. Every endpoint in this guide either operates across all of a MeetStream user’s connections (the default) or scopes to a single connection via ?provider=&account_id= query parameters or a path parameter. The provider key is outlook everywhere; the alias microsoft is accepted too. If you only ever connect one Outlook account per MeetStream user, the multi-account surface is invisible to you — everything just works.


1) Get your Microsoft OAuth credentials

Before connecting your calendar, you need three things from Microsoft: a Client ID, Client Secret, and Refresh Token. Follow these steps to get them.

Step 1: Register an app in Azure Portal

  1. Go to the Azure Portal.
  2. Navigate to Azure Active Directory > App registrations.
  3. Click New registration.
  4. Give your app a name (e.g. “MeetStream Calendar”).
  5. Under Supported account types, select Accounts in any organizational directory and personal Microsoft accounts.
  6. Under Redirect URI, select Web and enter:
    http://localhost:3000/api/microsoft/oauth-callback
  7. Click Register.
  8. On the app overview page, copy your Application (client) ID — this is your Client ID.

Step 2: Create a client secret

  1. In your app registration, go to Certificates & secrets.
  2. Click New client secret.
  3. Give it a description and choose an expiry period.
  4. Click Add and immediately copy the Value — this is your Client Secret. It won’t be shown again.

Step 3: Add Microsoft Graph API permissions

  1. In your app registration, go to API permissions.
  2. Click Add a permission > Microsoft Graph > Delegated permissions.
  3. Add the following permissions:
PermissionPurpose
Calendars.ReadRead calendar events
User.ReadRead user profile and email
offline_accessObtain refresh tokens for long-lived access
  1. Click Grant admin consent if you have admin rights, or ask your tenant admin to grant consent.

Step 4: Run the OAuth helper to get your refresh token

MeetStream provides a lightweight Node.js helper that runs the OAuth consent flow locally and returns your refresh token.

Prerequisites: Node.js installed on your machine.

  1. Create a .env file in the project root with your credentials:
MICROSOFT_CLIENT_ID=<YOUR_CLIENT_ID>
MICROSOFT_CLIENT_SECRET=<YOUR_CLIENT_SECRET>
  1. Install dependencies and start the helper server:
$npm install express @azure/msal-node dotenv
$node server_microsoft.js

You’ll see:

Open http://localhost:3000 to start the OAuth flow
Redirect URI: http://localhost:3000/api/microsoft/oauth-callback
Make sure this redirect URI is added in Azure Portal → App registrations → Authentication → Redirect URIs
  1. Open http://localhost:3000 in your browser.
  2. Sign in with your Microsoft account and grant calendar access.
  3. After authorization, the page displays your Refresh Token and Access Token.
  4. Copy the Refresh Token — you’ll need it in the next step.

If the refresh token is not returned, make sure offline_access is included in your permissions and that you clicked Accept on the consent screen. Microsoft only returns a refresh token when offline_access is in the requested scope.

Required scopes

The helper requests these scopes:

ScopePurpose
Calendars.ReadRead calendar events and calendar list
User.ReadIdentify the Microsoft account and profile
offline_accessObtain long-lived refresh tokens

You now have everything you need: Client ID, Client Secret, and Refresh Token.


2) Connect an Outlook Calendar account

Each call to this endpoint connects one Microsoft account. To attach a second, third, or twentieth account to the same MeetStream user, call it again with a different refresh token. MeetStream resolves the account from Microsoft Graph’s me endpoint and stores each connection separately.

API endpoint

POST https://api.meetstream.ai/api/v1/calendar/create_outlook_calendar

Request body

1{
2 "microsoft_client_id": "<YOUR_MICROSOFT_CLIENT_ID>",
3 "microsoft_client_secret": "<YOUR_MICROSOFT_CLIENT_SECRET>",
4 "microsoft_refresh_token": "<YOUR_MICROSOFT_REFRESH_TOKEN>"
5}

Optional:

FieldTypeDefaultDescription
replaceboolfalseWhen true, allows the call to overwrite an existing connection for the same account_id (token rotation, scope refresh). Without this flag, reconnecting an already-connected account returns 409 Conflict.

What happens behind the scenes

  1. MeetStream exchanges the refresh token at Microsoft’s OAuth endpoint to validate the credentials.
  2. Calls Microsoft Graph’s me endpoint to resolve the account’s primary email — that email becomes the account_id for this connection.
  3. Fetches all calendars on that account (default, secondary, shared) via Microsoft Graph.
  4. Stores your credentials securely (encrypted at rest in AWS SSM Parameter Store) under a per-account path, isolated from any other accounts you’ve connected.
  5. Registers Microsoft Graph change notification subscriptions on every calendar on that account, pointing at a per-account webhook URL with a per-subscription clientState secret so notifications for this account never cross-contaminate any other connection on the same user.
  6. Returns the connection record (no token material).

Example cURL

$curl -X POST "https://api.meetstream.ai/api/v1/calendar/create_outlook_calendar" \
> -H "Authorization: Token <YOUR_API_KEY>" \
> -H "Content-Type: application/json" \
> -d '{
> "microsoft_client_id": "<YOUR_MICROSOFT_CLIENT_ID>",
> "microsoft_client_secret": "<YOUR_MICROSOFT_CLIENT_SECRET>",
> "microsoft_refresh_token": "<YOUR_MICROSOFT_REFRESH_TOKEN>"
> }'

Response

1{
2 "calendar_id": "outlook_calendar_usr_abc123",
3 "platform": "outlook_calendar",
4 "provider": "outlook",
5 "account_id": "jane@acme.com",
6 "user_email": "jane@acme.com",
7 "user_name": "Jane Doe",
8 "calendars": [
9 {
10 "id": "AQMkADAwATM0MDAA...",
11 "summary": "Calendar",
12 "isPrimary": true,
13 "accessRole": "owner",
14 "selected": true
15 },
16 {
17 "id": "AQMkADAwATM0MDAB...",
18 "summary": "Team Meetings",
19 "isPrimary": false,
20 "accessRole": "writer",
21 "selected": true
22 }
23 ],
24 "primary_calendar_id": "AQMkADAwATM0MDAA...",
25 "watch_setup": {
26 "success": true,
27 "subscriptions_setup": 2,
28 "subscription_results": [
29 {
30 "calendar_id": "AQMkADAwATM0MDAA...",
31 "calendar_summary": "Calendar",
32 "subscription_id": "a1b2c3d4-...",
33 "expiration": "2026-05-23T22:00:00Z"
34 }
35 ],
36 "failed_calendars": []
37 },
38 "message": "Calendar connected successfully"
39}

Connecting a second account

Run the OAuth helper again signed in as the second Microsoft account, get a new refresh token, then call POST /create_outlook_calendar again with that token. The new account is added to the MeetStream user; existing connections — Outlook or Google — are untouched. There’s no separate “add account” endpoint, and there’s no provider mode: the same endpoint handles both Microsoft and Google credentials based on which fields you send.

$# Connect a second Outlook account (e.g. a work tenant) to the same MeetStream user.
$curl -X POST "https://api.meetstream.ai/api/v1/calendar/create_outlook_calendar" \
> -H "Authorization: Token <YOUR_API_KEY>" \
> -H "Content-Type: application/json" \
> -d '{
> "microsoft_client_id": "<YOUR_MICROSOFT_CLIENT_ID>",
> "microsoft_client_secret": "<YOUR_MICROSOFT_CLIENT_SECRET>",
> "microsoft_refresh_token": "<REFRESH_TOKEN_FOR_THE_SECOND_ACCOUNT>"
> }'

Reconnecting an existing account (token rotation)

If the same account_id is already connected for this MeetStream user, the call fails with 409 Conflict by default:

1{
2 "error": "Outlook account 'jane@acme.com' is already connected for this user. Pass {\"replace\": true} in the request body to reconnect."
3}

Pass "replace": true in the body to rotate the refresh token / refresh the scopes for an already-connected account in place:

1{
2 "microsoft_client_id": "...",
3 "microsoft_client_secret": "...",
4 "microsoft_refresh_token": "<NEW_REFRESH_TOKEN>",
5 "replace": true
6}

This is the right flow when your Azure client secret rotated or when you’ve extended the granted scopes — the credentials are updated without losing event history or notification subscriptions for that account.

Limits

LimitDefault
Calendar connections per MeetStream user (Google + Outlook combined)200

At the limit, POST /create_outlook_calendar returns 400 with "Maximum of 200 outlook calendar connections per user reached…". Disconnect one before adding another (see §11), or contact support to raise the limit.


3) View your connections and calendars

You have two read endpoints: one for the connections themselves (which Microsoft accounts are attached, with metadata) and one for the calendars within those connections (live from Graph).

3a) List your calendar connections

GET https://api.meetstream.ai/api/v1/calendar/connections

Returns every connection on the authenticated MeetStream user, grouped by provider. No token material is ever returned.

$curl -X GET "https://api.meetstream.ai/api/v1/calendar/connections" \
> -H "Authorization: Token <YOUR_API_KEY>"
1{
2 "user_id": "usr_abc123",
3 "google": [],
4 "outlook": [
5 {
6 "account_id": "jane@acme.com",
7 "email": "jane@acme.com",
8 "display_name": "Jane Doe (work)",
9 "primary_calendar_id": "AQMkADAwATM0MDAA...",
10 "scopes": ["Calendars.Read", "User.Read", "offline_access"],
11 "created_at": "2026-04-10T11:20:00Z",
12 "updated_at": "2026-04-10T11:20:00Z",
13 "calendars_count": 12,
14 "subscriptions_count": 12
15 },
16 {
17 "account_id": "jane.personal@outlook.com",
18 "email": "jane.personal@outlook.com",
19 "display_name": "Jane Doe (personal)",
20 "primary_calendar_id": "AQMkADAwATM0XYZ...",
21 "scopes": ["Calendars.Read", "User.Read", "offline_access"],
22 "created_at": "2026-04-12T09:05:00Z",
23 "updated_at": "2026-04-12T09:05:00Z",
24 "calendars_count": 4,
25 "subscriptions_count": 4
26 }
27 ],
28 "total": 2
29}

Inspect one connection

GET https://api.meetstream.ai/api/v1/calendar/connections/{provider}/{account_id}

{provider} is outlook (the alias microsoft is accepted). {account_id} must be URL-encoded (@ becomes %40, etc.).

$curl -X GET "https://api.meetstream.ai/api/v1/calendar/connections/outlook/jane%40acme.com" \
> -H "Authorization: Token <YOUR_API_KEY>"

Returns a single connection record like one of the entries above. 404 if the user has no connection for that account.

3b) List the calendars inside those connections

GET https://api.meetstream.ai/api/v1/calendar/calendars

Returns calendars across every connected account by default (Outlook + Google), with a per-connection summary so you can group them in your UI.

Query parameters

ParameterRequired when…Example
providerYou want to filter by provider only?provider=outlook
account_idYou want only one specific account’s calendars. Requires provider.?provider=outlook&account_id=jane%40acme.com

Example cURL

$# All calendars from every connected account (Outlook + Google)
$curl -X GET "https://api.meetstream.ai/api/v1/calendar/calendars" \
> -H "Authorization: Token <YOUR_API_KEY>"
$
$# Only calendars from one specific Outlook account
$curl -X GET "https://api.meetstream.ai/api/v1/calendar/calendars?provider=outlook&account_id=jane%40acme.com" \
> -H "Authorization: Token <YOUR_API_KEY>"

Response

1{
2 "calendars": [
3 {
4 "id": "AQMkADAwATM0MDAA...",
5 "summary": "Calendar",
6 "isPrimary": true,
7 "accessRole": "owner",
8 "timeZone": "America/New_York",
9 "backgroundColor": "#0078d4",
10 "selected": true,
11 "provider": "outlook",
12 "account_id": "jane@acme.com"
13 },
14 {
15 "id": "AQMkADAwATM0XYZ...",
16 "summary": "Personal",
17 "isPrimary": true,
18 "accessRole": "owner",
19 "timeZone": "America/New_York",
20 "backgroundColor": "#0078d4",
21 "selected": true,
22 "provider": "outlook",
23 "account_id": "jane.personal@outlook.com"
24 }
25 ],
26 "connections": [
27 { "provider": "outlook", "account_id": "jane@acme.com", "calendars_count": 1, "is_legacy": false },
28 { "provider": "outlook", "account_id": "jane.personal@outlook.com", "calendars_count": 1, "is_legacy": false }
29 ],
30 "total": 2,
31 "user_id": "usr_abc123"
32}

This is a live call to the Microsoft Graph API and always returns the latest calendar list. Each entry carries provider and account_id so the dashboard / client can tell which connection it came from.

If one connection fails to respond (revoked token, Graph throttling, expired client secret), the call still succeeds and surfaces the error under a partial_errors map keyed by "{provider}::{account_id}".


4) Sync and view your events

MeetStream syncs events from every connected Outlook Calendar account and stores them locally. It detects meeting links for supported platforms (Google Meet, Zoom, Microsoft Teams, Webex, GoToMeeting, BlueJeans, and Whereby).

Sync events from Outlook Calendar

GET https://api.meetstream.ai/api/v1/calendar/events

This is the primary events endpoint. It syncs with Outlook Calendar (using per-account Microsoft Graph delta tokens for speed), stores events in MeetStream’s database, and returns them with pagination and linked bot information. By default, events from every connected account on the MeetStream user (Outlook + Google) are returned.

Query parameters

ParameterTypeDefaultDescription
calendar_idstringprimary calendar of each connectionSpecific calendar to sync
time_minISO 8601now - 1 dayStart of the time window
time_maxISO 8601now + 28 daysEnd of the time window
sync"true" / "false"autoForce a sync from Outlook Calendar
limitint (1-100)50Number of events per page
cursorstringPagination cursor from next field
cleanup_duplicates"true" / "false""false"Deduplicate events
provider"outlook" / "google"allScope to one provider
account_idstringall accountsScope to one specific connection. Requires provider. Use to fetch events from just one Outlook account when the user has several connected.

Example cURL

$curl -X GET "https://api.meetstream.ai/api/v1/calendar/events?limit=20" \
> -H "Authorization: Token <YOUR_API_KEY>"

Response

1{
2 "next": "eyJFdmVudElEIjogIm...",
3 "previous": null,
4 "results": [
5 {
6 "id": "evt_abc123",
7 "start_time": "2026-04-08T15:00:00Z",
8 "end_time": "2026-04-08T16:00:00Z",
9 "calendar_id": "AQMkADAwATM0MDAA...",
10 "platform": "outlook_calendar",
11 "provider": "outlook",
12 "account_id": "jane@acme.com",
13 "platform_id": "outlook_evt_456",
14 "ical_uid": "abc123@outlook.com",
15 "meeting_platform": "Teams",
16 "meeting_url": "https://teams.microsoft.com/l/meetup-join/...",
17 "is_deleted": false,
18 "created_at": "2026-04-01T12:00:00Z",
19 "updated_at": "2026-04-07T09:30:00Z",
20 "raw": { },
21 "bots": [
22 {
23 "id": "bot_111",
24 "status": "Scheduled",
25 "scheduled_join_time": "2026-04-08T14:59:00+00:00",
26 "bot_username": "MeetStream Calendar Bot",
27 "platform": "Teams",
28 "is_scheduled": true
29 }
30 ]
31 }
32 ],
33 "has_more": false
34}

Every event row carries a provider and account_id so you can group / filter client-side without re-issuing scoped requests. Use the next cursor value in a subsequent request to fetch the next page:

$curl -X GET "https://api.meetstream.ai/api/v1/calendar/events?cursor=eyJFdmVudElEIjogIm..." \
> -H "Authorization: Token <YOUR_API_KEY>"

Scoping to a single account

When the user has multiple connections, you can scope the sync + return to just one of them. This is faster (only one account is touched) and useful for per-tenant UIs.

$curl -X GET "https://api.meetstream.ai/api/v1/calendar/events?provider=outlook&account_id=jane%40acme.com&limit=20" \
> -H "Authorization: Token <YOUR_API_KEY>"

Get events from local database only

GET https://api.meetstream.ai/api/v1/calendar/get_events

This is a lightweight endpoint that returns events already synced to MeetStream without calling the Microsoft Graph API. Useful when you want fast reads and don’t need the latest sync. Accepts the same provider / account_id scoping query parameters as /calendar/events.

$# All accounts (default)
$curl -X GET "https://api.meetstream.ai/api/v1/calendar/get_events" \
> -H "Authorization: Token <YOUR_API_KEY>"
$
$# One specific account
$curl -X GET "https://api.meetstream.ai/api/v1/calendar/get_events?provider=outlook&account_id=jane%40acme.com" \
> -H "Authorization: Token <YOUR_API_KEY>"

5) Schedule a bot for a specific meeting

Pick a meeting from your synced events and schedule a bot for it.

API endpoint

POST https://api.meetstream.ai/api/v1/calendar/schedule/{event_id}

Path parameters

ParameterDescription
event_idMeetStream event ID (the id field from the events list)

Request body

1{
2 "bot_config": {
3 "bot_name": "My Meeting Bot",
4 "audio_required": true,
5 "video_required": false,
6 "bot_message": "Bot joining to record this meeting",
7 "callback_url": "https://your-domain.com/webhooks/meetstream",
8 "transcription": {
9 "deepgram": { "model": "nova-3", "language": "en" }
10 },
11 "automatic_leave": {
12 "no_one_joined_timeout": 300,
13 "everyone_left_timeout": 60
14 },
15 "recording_config": {
16 "video_recording": false
17 }
18 }
19}

The bot_config accepts the same fields you’d normally pass to the Create Bot API.

Example cURL

$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": {
> "video_required": false,
> "callback_url": "https://your-domain.com/webhooks/meetstream"
> }
> }'

Response

1{
2 "scheduled": true,
3 "schedule_id": "bot-usr_abc1-bot_111aaa12",
4 "bot_id": "bot_111...",
5 "schedule_group": "teams",
6 "event_id": "evt_abc123",
7 "scheduled_time": "2026-04-08T14:59:00Z",
8 "bot_config": { },
9 "is_recurring_occurrence": false
10}

Scheduling options for recurring events

Body fieldTypeDefaultDescription
occurrence_dateISO 8601Schedule a bot for a specific occurrence of a recurring event
schedule_all_occurrencesboolfalseSchedule bots for all future occurrences at once
occurrence_limitint52Maximum number of occurrences to schedule when using schedule_all_occurrences
recurring_eventboolfalseEnable auto-rescheduling — after the bot joins this occurrence, automatically schedule the next one

Deduplication

If you schedule a bot for the same event twice, MeetStream returns a 409 Conflict with the existing bot’s ID. To update the bot config, use PATCH /calendar/scheduled_bots/{bot_id} instead.


6) Unschedule a bot

Cancel a scheduled bot for a specific event.

API endpoint

DELETE https://api.meetstream.ai/api/v1/calendar/schedule/{event_id}

Request body (optional)

1{
2 "cancel_all_occurrences": false,
3 "from_date": "2026-05-01T00:00:00Z"
4}
Body fieldTypeDefaultDescription
cancel_all_occurrencesboolfalseCancel bots for all occurrences of a recurring series
from_dateISO 8601Only cancel occurrences from this date forward

Example cURL

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

Response

1{
2 "unscheduled": true,
3 "event_id": "evt_abc123",
4 "cancelled_schedules": ["bot-usr_abc1-bot_111aaa12"],
5 "schedules_cancelled": 1,
6 "bots_deleted": 1,
7 "cancel_all_occurrences": false,
8 "is_recurring_series": false
9}

7) Manage scheduled bots

View, update, or delete your scheduled bots across all events.

List all scheduled bots

GET https://api.meetstream.ai/api/v1/calendar/scheduled_bots

Returns all bots with a scheduled join time in the future.

Query parameters

ParameterTypeDefaultDescription
limitint (1-100)100Maximum number of bots to return

Example cURL

$curl -X GET "https://api.meetstream.ai/api/v1/calendar/scheduled_bots" \
> -H "Authorization: Token <YOUR_API_KEY>"

Response

1{
2 "scheduled_bots": [
3 {
4 "bot_id": "bot_111...",
5 "platform": "Teams",
6 "status": "Scheduled",
7 "scheduled_join_time": "2026-04-08T14:59:00+00:00",
8 "bot_username": "MeetStream Calendar Bot",
9 "meeting_link": "https://teams.microsoft.com/l/meetup-join/...",
10 "custom_attributes": {
11 "source": "calendar_integration",
12 "event_id": "outlook_evt_456",
13 "event_summary": "Weekly Standup"
14 },
15 "is_scheduled": true,
16 "created_at": "2026-04-07T10:00:00Z"
17 }
18 ]
19}

Update a scheduled bot

PATCH https://api.meetstream.ai/api/v1/calendar/scheduled_bots/{bot_id}

Update the join time, display name, or other properties of a scheduled bot.

Request body

All fields are optional — include only what you want to change.

1{
2 "scheduled_join_time": "2026-04-08T16:59:00Z",
3 "bot_username": "Updated Bot Name",
4 "custom_attributes": { "note": "VIP meeting" }
5}
Body fieldTypeDescription
scheduled_join_timeISO 8601New join time (must be in the future). Updates the EventBridge schedule too.
bot_usernamestringDisplay name for the bot in the meeting
custom_attributesobjectCustom metadata attached to the bot

Response

1{
2 "message": "Scheduled bot updated successfully",
3 "bot_id": "bot_111...",
4 "updated_fields": ["scheduled_join_time", "bot_username"],
5 "schedule_updated": true
6}

Delete a specific scheduled bot

DELETE https://api.meetstream.ai/api/v1/calendar/scheduled_bots/{bot_id}
$curl -X DELETE "https://api.meetstream.ai/api/v1/calendar/scheduled_bots/bot_111..." \
> -H "Authorization: Token <YOUR_API_KEY>"
1{
2 "message": "Scheduled bot deleted successfully",
3 "bot_id": "bot_111..."
4}

8) Enable auto-scheduling

Auto-scheduling is a hands-free mode: MeetStream scans your calendar every 24 hours and automatically schedules bots for all upcoming meetings that have a meeting link.

Enable auto-scheduling

POST https://api.meetstream.ai/api/v1/calendar/auto-schedule/enable

Request body

Provide a default bot configuration that will be used for all auto-scheduled bots:

1{
2 "default_bot_config": {
3 "bot_name": "MeetStream Auto Bot",
4 "audio_required": true,
5 "video_required": false,
6 "callback_url": "https://your-domain.com/webhooks/meetstream",
7 "transcription": {
8 "deepgram": { "model": "nova-3", "language": "en" }
9 },
10 "automatic_leave": {
11 "no_one_joined_timeout": 300,
12 "everyone_left_timeout": 60
13 }
14 }
15}

Example cURL

$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": {
> "video_required": false,
> "callback_url": "https://your-domain.com/webhooks/meetstream"
> }
> }'

Response

1{
2 "message": "Auto-scheduling enabled successfully",
3 "auto_schedule_enabled": true,
4 "default_bot_config": { }
5}

Disable auto-scheduling

POST https://api.meetstream.ai/api/v1/calendar/auto-schedule/disable
$curl -X POST "https://api.meetstream.ai/api/v1/calendar/auto-schedule/disable" \
> -H "Authorization: Token <YOUR_API_KEY>"

Check auto-schedule settings

GET https://api.meetstream.ai/api/v1/calendar/auto-schedule/settings
1{
2 "auto_schedule_enabled": true,
3 "default_bot_config": {
4 "bot_name": "MeetStream Auto Bot",
5 "audio_required": true,
6 "video_required": false
7 }
8}

How auto-scheduling works

  1. A background job runs every 24 hours at midnight UTC.
  2. It finds all users who have auto-scheduling enabled.
  3. For each user, it looks at upcoming events in the next 24 hours that have a valid meeting link.
  4. It skips events that already have a bot scheduled (using deduplication keys).
  5. It creates bot schedules using your default_bot_config.
  6. Bots are scheduled to join 1 minute before the meeting starts.

You can override individual meetings by manually scheduling them with POST /calendar/schedule/{event_id} and a custom bot_config.


9) Recurring event auto-rescheduling

For recurring meetings (weekly standups, bi-weekly syncs, etc.), MeetStream can automatically schedule a bot for the next occurrence after each meeting ends.

How it works

  1. A bot joins a recurring meeting.
  2. After the meeting ends, MeetStream detects it was a recurring event.
  3. It calculates the next occurrence from the event’s recurrence rule.
  4. A new bot is automatically scheduled for the next occurrence using the same configuration.

This continues indefinitely — every recurring meeting gets a bot, without manual intervention.

Enable auto-rescheduling when scheduling

When scheduling a bot for a recurring event, set recurring_event: true in the request body:

$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": { "video_required": false },
> "recurring_event": true
> }'

Toggle auto-rescheduling for an existing event

You can enable or disable auto-rescheduling per event at any time:

POST https://api.meetstream.ai/api/v1/calendar/toggle-recurrence
1{
2 "event_id": "evt_abc123",
3 "recurring_enabled": true
4}

Response

1{
2 "event_id": "evt_abc123",
3 "recurring_enabled": true,
4 "recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR",
5 "message": "Recurrence enabled for event",
6 "summary": "Weekly Team Standup",
7 "start_time": "2026-04-07T10:00:00Z",
8 "end_time": "2026-04-07T10:30:00Z"
9}

This endpoint returns 400 if the event is not actually a recurring event (no recurrence rule).

Trigger rescheduling manually

MeetStream also exposes an endpoint to manually trigger rescheduling for the next occurrence:

POST https://api.meetstream.ai/api/v1/calendar/auto-reschedule
1{
2 "user_id": "usr_abc123",
3 "event_id": "evt_abc123",
4 "recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR",
5 "current_start_time": "2026-04-07T10:00:00Z",
6 "bot_config": { "video_required": false },
7 "meeting_link": "https://teams.microsoft.com/l/meetup-join/..."
8}

This is primarily used internally by MeetStream after a meeting ends. You typically don’t need to call it directly.

Supported recurrence patterns

MeetStream supports standard iCalendar recurrence rules (RRULE) as surfaced by Microsoft Graph:

PatternRRULE example
DailyFREQ=DAILY
WeeklyFREQ=WEEKLY;BYDAY=MO,WE,FR
Bi-weeklyFREQ=WEEKLY;INTERVAL=2;BYDAY=TU
MonthlyFREQ=MONTHLY;BYDAY=1MO (first Monday)
YearlyFREQ=YEARLY;BYMONTH=3;BYMONTHDAY=15

10) Real-time calendar change detection

When you connect your calendar, MeetStream sets up Microsoft Graph change notification subscriptions on your calendars. This enables real-time reactions to calendar changes without polling.

What MeetStream handles automatically

Calendar changeWhat MeetStream does
Meeting rescheduled (time changed)Updates the bot’s EventBridge schedule and ScheduledJoinTime to match the new time. The bot joins at the correct time.
Meeting cancelled or deletedCancels the EventBridge schedule and marks the bot record as Cancelled. No bot is created.
Meeting time moved to the pastDeletes the schedule and cancels the bot (since the meeting already happened or won’t happen).
New meeting addedIf auto-scheduling is enabled, a bot is scheduled for it during the next auto-schedule run.

How it works under the hood

  1. Microsoft Graph detects a change and sends an HTTPS POST to a per-account webhook URL of the form …/api/v1/admin/schedule/users/{user_id}/{provider}/{account_id} — so a notification for jane@acme.com cannot be confused with a notification for jane.personal@outlook.com even when both are connected to the same MeetStream user.
  2. MeetStream validates the notification using the per-subscription clientState secret issued at registration time and rejects mismatches.
  3. MeetStream fetches updated event data from the Microsoft Graph API using the per-account delta token (one token per account_id, isolated from every other account’s sync state).
  4. For each event that has an active bot scheduled:
    • Single events: The existing EventBridge schedule is updated in place with the new time. The bot record’s ScheduledJoinTime is also updated so GET /scheduled_bots reflects the correct time.
    • Recurring events: All existing schedules are deleted and recreated with the updated recurrence times.
    • Cancelled events: The schedule is deleted and the bot record is marked Cancelled.

Only events from the account that fired the notification are touched. Changes on other connections wait for their own notifications. You don’t need to re-sync manually or make any API calls — changes are picked up in real time via webhooks from Microsoft.

Subscription auto-renewal

Microsoft Graph change notification subscriptions for calendar events expire after approximately 3 days (4,230 minutes). MeetStream automatically renews expiring subscriptions:

  • A background job runs daily at 3:00 AM UTC.
  • It checks all connected users’ Graph subscriptions.
  • Any subscription expiring within 2 days is renewed automatically.
  • No user action is required — webhooks stay active indefinitely.

This is more frequent than Google Calendar’s 30-day watch channels. MeetStream handles the renewal schedule transparently — you will not notice any gap in change detection.


11) Disconnect a calendar account

There are two disconnect endpoints. For multi-account users, prefer the per-account variant — it’s explicit about which account is being removed and leaves every other connection alone.

DELETE https://api.meetstream.ai/api/v1/calendar/connections/{provider}/{account_id}

{provider} is outlook (the alias microsoft is accepted). {account_id} must be URL-encoded (@%40).

What this does

  1. Stops the Microsoft Graph change notification subscriptions registered for this connection (no more push notifications).
  2. Deletes the per-account OAuth credentials from secure storage.
  3. Removes the connection record from GET /calendar/connections.
  4. Cleans up events and scheduled bots that came from this accountEvents rows tagged with this (provider, account_id) are deleted; bots in Scheduled status for those events move to Cancelled with reason: "calendar_disconnected". Bots that have already run are not touched.

Other Outlook (and Google) connections on this MeetStream user are completely unaffected.

Query parameter

ParameterDefaultDescription
purge_eventstrueSet to false to disconnect the account without deleting its event history. The credentials and subscriptions are still removed (no more updates), but historical event rows stay in the database.

Example cURL

$# Disconnect the work account and purge its events + scheduled bots.
$curl -X DELETE "https://api.meetstream.ai/api/v1/calendar/connections/outlook/jane%40acme.com" \
> -H "Authorization: Token <YOUR_API_KEY>"
$
$# Disconnect but keep the event history for reporting.
$curl -X DELETE "https://api.meetstream.ai/api/v1/calendar/connections/outlook/jane%40acme.com?purge_events=false" \
> -H "Authorization: Token <YOUR_API_KEY>"

Response

1{
2 "found": true,
3 "disconnected": true,
4 "provider": "outlook",
5 "account_id": "jane@acme.com",
6 "purge_events": true,
7 "webhook_cleanup": {
8 "attempted": 12,
9 "stopped": 12,
10 "skipped_expired_or_invalid": 0
11 },
12 "events_cleanup": {
13 "scope": { "provider": "outlook", "account_id": "jane@acme.com" },
14 "events_matched": 47,
15 "events_deleted": 47,
16 "bots_cancelled": 6,
17 "schedules_deleted": 6,
18 "events_failed": 0
19 },
20 "message": "Outlook connection removed. Account events and scheduled bots cleaned up."
21}

This endpoint is idempotent — re-calling it for an already-removed account returns 404 Not Found without changing state.

11b) Disconnect (legacy single-account endpoint)

DELETE https://api.meetstream.ai/api/v1/calendar/disconnect

Original endpoint, kept for backward compatibility. Behaviour depends on how many connections the MeetStream user has:

ScenarioBodyBehaviour
User has one connection{}Tears down that one connection.
User has multiple connections{}400 Bad Request — refuses to disconnect everything implicitly. Use 11a per account or pass an explicit account_id.
Explicit per-account disconnect{"provider": "outlook", "account_id": "jane@acme.com"}Same effect as the §11a endpoint.
Provider-scoped disconnect{"provider": "outlook"}Removes every Outlook connection on the user (and only Outlook). Useful when migrating tenants.
Same as above, keep events{"provider": "outlook", "account_id": "jane@acme.com", "purge_events": false}Or ?purge_events=false in the query string.

The 400 response includes a next_steps hint so callers know exactly what to do:

1{
2 "error": "Multiple calendar connections present; refusing to disconnect all without explicit account_id",
3 "scope": "all",
4 "connection_count": 3,
5 "next_steps": "Call DELETE /api/v1/calendar/connections/{provider}/{account_id} per account, or POST this endpoint with body {\"provider\": \"...\", \"account_id\": \"...\"}."
6}

This safety check exists so that one careless API call cannot accidentally wipe out a multi-tenant user’s entire calendar history.

Both disconnect paths are irreversible. To reconnect an account, call POST /calendar/create_outlook_calendar again with its credentials.


End-to-end setup checklist

Here’s the full flow to get Outlook Calendar integration running:

  1. Get credentials — Register an app in Azure Portal, add Microsoft Graph permissions, and run the OAuth helper to get a refresh token for each Microsoft account you want to connect
  2. ConnectPOST /calendar/create_outlook_calendar once per account. Each call attaches that account to the same MeetStream user.
  3. VerifyGET /calendar/connections to confirm every account you wanted is attached
  4. SyncGET /calendar/events to pull in meetings from every connected account (or pass ?provider=outlook&account_id=… to scope)
  5. SchedulePOST /calendar/schedule/{event_id} to add a bot to a specific meeting
  6. Or auto-schedulePOST /calendar/auto-schedule/enable to cover all meetings across all connected accounts automatically
  7. Recurring — Set recurring_event: true when scheduling, or use POST /calendar/toggle-recurrence
  8. Relax — MeetStream handles calendar changes, rescheduling, cancellations, and subscription renewal per-account from here

API quick reference

MethodEndpointDescription
Calendar connections
POST/api/v1/calendar/create_outlook_calendarConnect one Outlook (or Google) account. Call repeatedly for additional accounts. Pass {"replace": true} to rotate tokens on an existing account.
GET/api/v1/calendar/connectionsList every connected account on the user, grouped by provider
GET/api/v1/calendar/connections/{provider}/{account_id}Inspect one connection’s metadata
DELETE/api/v1/calendar/connections/{provider}/{account_id}Disconnect one account (preferred). Optional ?purge_events=false keeps event history.
DELETE/api/v1/calendar/disconnectLegacy disconnect. Returns 400 when the user has >1 connection unless body includes {provider, account_id}.
GET/api/v1/calendar/calendarsList calendars across every connected account (live from Microsoft Graph). ?provider=&account_id= to scope to one.
Events
GET/api/v1/calendar/eventsSync and list events. Default = every account. ?provider=&account_id= to scope.
GET/api/v1/calendar/get_eventsList events from local database only (fast). Same ?provider=&account_id= scoping.
Bot scheduling
POST/api/v1/calendar/schedule/{event_id}Schedule a bot for an event
DELETE/api/v1/calendar/schedule/{event_id}Unschedule a bot for an event
Scheduled bot management
GET/api/v1/calendar/scheduled_botsList all upcoming scheduled bots
PATCH/api/v1/calendar/scheduled_bots/{bot_id}Update a scheduled bot (time, name, attributes)
DELETE/api/v1/calendar/scheduled_bots/{bot_id}Delete a specific scheduled bot
Auto-scheduling
POST/api/v1/calendar/auto-schedule/enableEnable auto-scheduling with default bot config
POST/api/v1/calendar/auto-schedule/disableDisable auto-scheduling
GET/api/v1/calendar/auto-schedule/settingsGet current auto-schedule settings
Recurring events
POST/api/v1/calendar/auto-rescheduleTrigger rescheduling for next recurring occurrence
POST/api/v1/calendar/toggle-recurrenceEnable/disable auto-rescheduling per event

FAQ

Which calendar providers are supported?

Google Calendar and Outlook Calendar (Microsoft 365 / Outlook.com) are both supported on the same MeetStream user. Your calendar can contain meetings from any platform — MeetStream detects Google Meet, Zoom, Microsoft Teams, Webex, GoToMeeting, BlueJeans, and Whereby links. See the Google Calendar Integration Guide for the Google flow.

Do I need a Microsoft 365 (work/school) account?

No. Both personal Microsoft accounts (Outlook.com, Hotmail, Live) and Microsoft 365 work/school accounts are supported. When registering your app in Azure Portal, choose Accounts in any organizational directory and personal Microsoft accounts to cover both. You can mix and match: one MeetStream user can connect a personal Outlook.com account and multiple work tenants at the same time.

Can one MeetStream user connect multiple Microsoft accounts?

Yes — up to 200 calendar connections per MeetStream user (Outlook + Google combined). Call POST /create_outlook_calendar once per refresh token. Each connection is identified by (provider="outlook", account_id=<the account's primary email>). Use GET /calendar/connections to inspect them, and DELETE /calendar/connections/{provider}/{account_id} to remove one without affecting the others.

What happens if I call POST /create_outlook_calendar for the same Microsoft account twice?

The second call returns 409 Conflict with the existing account_id. To rotate the refresh token or refresh the scopes for an already-connected account, pass "replace": true in the request body — the credentials are updated in place without losing event history or notification subscriptions.

Can I scope GET /calendar/events to just one of my connected accounts?

Yes. Pass ?provider=outlook&account_id=<URL-encoded-email>. Without those query parameters, events from every connected account are returned (each row carries provider and account_id so you can group them client-side).

How does MeetStream avoid mixing up webhook notifications between my connected accounts?

Each connection has its own webhook URL of the form …/api/v1/admin/schedule/users/{user_id}/{provider}/{account_id}, and an opaque per-subscription clientState issued at registration. Notifications that arrive on the wrong URL or carry a mismatched clientState are rejected. Delta tokens, calendar lists, and notification subscriptions are all stored per-account in isolated storage paths.

I only have one Outlook account connected — do I need to worry about any of this multi-account stuff?

No. Every endpoint behaves the same way when you have exactly one connection — you can keep ignoring provider, account_id, and /connections. They become useful the moment you connect a second account.

How do I get a Microsoft OAuth2 refresh token?

Follow Section 1 of this guide — register an app in Azure Portal, add the required Microsoft Graph permissions, and run the provided OAuth helper (node server_microsoft.js) to complete the consent flow. The helper displays your refresh token in the browser.

How far in advance does auto-scheduling look?

The auto-schedule job runs every 24 hours at midnight UTC and schedules bots for meetings happening in the next 24 hours. Meetings further out will be picked up in subsequent runs.

When does the bot join relative to the meeting start time?

Bots are scheduled to join 1 minute before the meeting’s start time.

What happens if I reschedule a meeting in Outlook?

MeetStream receives a real-time push notification from Microsoft Graph and automatically updates the bot’s scheduled join time to match the new meeting time. Both the EventBridge schedule and the bot record in MeetStream’s database are updated, so GET /scheduled_bots always shows the correct time.

What if I cancel a meeting?

MeetStream detects the cancellation via the Microsoft Graph notification, deletes the EventBridge schedule, and marks the bot as Cancelled. No bot will be created for a cancelled meeting.

What if a meeting is moved to a time that already passed?

MeetStream detects that the new time is in the past, deletes the EventBridge schedule, and cancels the bot record. This prevents a bot from being created for a meeting that can no longer be joined.

No. MeetStream requires a valid meeting link (Google Meet, Zoom, Teams, etc.) to join. Events without a detected meeting link are skipped.

What happens if I schedule a bot for the same event twice?

MeetStream deduplicates by event. The second call returns a 409 Conflict with the existing bot’s ID. To update the bot config, use PATCH /calendar/scheduled_bots/{bot_id}.

Can I use different bot configurations for different meetings?

Yes. When you manually schedule a bot via POST /calendar/schedule/{event_id}, you provide the bot_config per event. Auto-scheduling uses your default_bot_config for all meetings, but you can override individual events by scheduling them manually.

Does auto-rescheduling work with all recurrence patterns?

MeetStream supports standard iCalendar recurrence rules (RRULE) — daily, weekly, bi-weekly, monthly, yearly, and custom patterns. The next occurrence is calculated from the event’s recurrence rule as returned by Microsoft Graph.

How do I stop a recurring event from being rescheduled?

Use the toggle endpoint: POST /calendar/toggle-recurrence with "recurring_enabled": false for that event.

Do I need to worry about Microsoft Graph subscriptions expiring?

No. MeetStream automatically renews Microsoft Graph change notification subscriptions before they expire. A daily background job checks for subscriptions expiring within 2 days and renews them. Your real-time calendar sync stays active indefinitely without any action on your part.

What data is deleted when I disconnect my calendar?

  • Per-account disconnect (DELETE /calendar/connections/{provider}/{account_id}): Graph subscriptions for that account are stopped, its credentials are wiped from secure storage, the connection record is removed, and (by default) events from that account along with their scheduled bots are deleted. Pass ?purge_events=false to keep the event history. Other connected accounts are completely untouched.
  • Full-user disconnect (DELETE /calendar/disconnect): the legacy endpoint. When only one connection exists, it tears that one down. When multiple connections exist, it refuses (returns 400) unless you specify which account in the body — to prevent accidentally wiping out a multi-tenant user’s entire history.

Both paths are irreversible. To reconnect, call POST /calendar/create_outlook_calendar again.

Is my Outlook Calendar data stored securely?

Yes. OAuth credentials are stored in AWS Systems Manager Parameter Store as encrypted SecureString parameters. Event data is stored in DynamoDB with encryption at rest. MeetStream does not store your Microsoft password.

Can I schedule bots for all occurrences of a recurring event at once?

Yes. Pass "schedule_all_occurrences": true in the request body when scheduling. You can limit the number of occurrences with "occurrence_limit" (default 52). Each occurrence gets its own bot and EventBridge schedule.

How do I schedule a bot for a specific occurrence of a recurring event?

Pass "occurrence_date": "2026-04-14T10:00:00Z" in the request body. MeetStream will schedule a bot for that specific occurrence only.

Can I cancel bots for future occurrences only?

Yes. When unscheduling a recurring event, pass "from_date": "2026-05-01T00:00:00Z" to cancel only occurrences from that date forward. Occurrences before that date keep their scheduled bots.

My client secret expired — what do I do?

Generate a new client secret in Azure Portal under your app’s Certificates & secrets, then re-run the OAuth helper to get a fresh refresh token, and call POST /calendar/create_outlook_calendar again with the new credentials. MeetStream will replace the old connection automatically.


For webhook event handling, see the Webhook Events Guide. For creating your first bot without calendar integration, see the First Bot Quickstart. For Google Calendar integration, see the Google Calendar Integration Guide.