The real problem
Why this is harder than it looks
On the surface, Outlook's calendar API looks approachable. You register an app in Azure, get a client ID and secret, run through an OAuth flow, and start calling Microsoft Graph. The first happy-path test works in an afternoon. Then you try to ship it to real users.
The first real problem is Azure app registration. Unlike many OAuth providers, Outlook requires you to register your own Azure AD application — there is no Scalekit-managed option. You need to decide on supported account types upfront: personal Microsoft accounts only, organizational accounts only, or both. Getting this wrong means entire categories of users cannot authorize your app, and Microsoft's error messages are not helpful. For a B2B product where your customers' employees use corporate Microsoft 365 accounts, you need multitenant mode, which can trigger additional admin-consent requirements in the customer's own Azure AD tenant.
Then there's the permissions model. Microsoft Graph uses fine-grained OAuth scopes — Calendars.Read, Calendars.ReadWrite, Mail.Read, Mail.Send are entirely separate grants. Each must be declared in your Azure app registration and requested during the OAuth flow. If your agent needs to both read and create events, you must request both scopes upfront — or force users through re-authorization later. Some enterprise tenants require an IT admin to pre-approve your app's requested permissions before any user in the org can connect. That consent-admin workflow has nothing to do with your code, but it will block your go-live.
Access tokens from Microsoft expire in one hour. Refresh token behavior differs between personal accounts and organizational accounts, and organizational refresh tokens can be revoked at the tenant level by an admin — not just by the user. If a customer's IT team revokes third-party app access across the organization, your agent starts receiving 401s with no clear signal about why. Detecting this, stopping calls, and re-prompting the right user requires infrastructure that has nothing to do with scheduling a meeting.
None of this is impossible to build. But it's weeks of plumbing before your agent can reliably say "I've scheduled that for you." Scalekit handles the Azure registration flow, token lifecycle, and multi-tenant consent complexity. Your agent names a tool and passes parameters.
Capabilities
What your agent can do with Outlook
Once connected, your agent has 5 pre-built tools covering calendar management via Microsoft Graph:
- Create calendar events with full richness: required, optional, and resource-room attendees; recurrence patterns; Teams meeting links; reminders; sensitivity and free/busy status
- List and filter events with OData: date-range queries, subject filters, field selection, sorting, and pagination across large calendars
- Retrieve any event by ID: full metadata including attendee response status and online meeting join URL
- Update any event property in place: reschedule, change attendees, convert to online meeting, or modify recurrence without recreating the event
- Delete events cleanly: by event ID, with no dangling state
Setup context
What we're building
This guide connects a scheduling assistant agent to Outlook — helping users create meetings, check their calendar, and manage events without leaving your product.
🤖
Example agent
Scheduling assistant managing calendar events on behalf of each user
🔐
Auth model
B2B SaaS — each user connects their own Microsoft account. identifier = your user ID
🇦🇿
Azure app registration
Required — register at
portal.azure.com. Scalekit provides the redirect URI.
Setup
1 Setup: One SDK, One credential
Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Azure secrets, no user tokens, nothing belonging to your customers.
pip install scalekit-sdk-python
import scalekit.client
import os
from dotenv import load_dotenv
load_dotenv()
scalekit = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit.actions
Connected Accounts
2 Per-User Auth: Creating connected accounts
Each user gets their own Connected Account, giving them a dedicated auth context. The identifier is any unique string from your system — a UUID, email, whatever you use internally.
response = actions.get_or_create_connected_account(
connection_name="outlook",
identifier="user_outlook_789" # your internal user ID
)
connected_account = response.connected_account
print(f"Status: {connected_account.status}")
# Status: PENDING — user hasn't authorized yet
This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.
Authorization Flow
3 The authorization flow
The user authorizes your agent once. Scalekit generates the OAuth URL with correct Microsoft Graph scopes and redirect handling pre-configured. After approval, you never see the token.
if connected_account.status != "ACTIVE":
link = actions.get_authorization_link(
connection_name="outlook",
identifier="user_outlook_789"
)
# Redirect user → Microsoft consent screen
# Scalekit captures the token on callback
return redirect(link.link)
Token management is automatic
After the user approves, Scalekit stores encrypted tokens and the connected account moves to ACTIVE. Access
tokens refresh before the one-hour Microsoft expiry. If a user revokes access — or an IT admin revokes it
at the tenant level — the account moves to REVOKED with no silent failures. Check account.status before
critical operations.
Azure app setup: Bring Your Own Credentials (required)
In the Scalekit dashboard, go to Agent Auth → Create Connection → Outlook. Copy the redirect URI.
In portal.azure.com, register a new app under Microsoft Entra ID, set supported account types to
“Multitenant + personal accounts,” and add the Scalekit redirect URI. Then paste your Azure Client ID
and Secret into the Scalekit dashboard. Token management stays fully handled.
Already have Azure credentials configured?
Calling Outlook
4 Calling Outlook: What your agent writes
With the connected account active, your agent calls Outlook actions using execute_tool(). Name the tool, pass parameters. Scalekit handles token retrieval and Microsoft Graph request construction.
List upcoming calendar events
Fetch events with OData filtering. Use filter to scope by date range, select to limit fields, and top for pagination.
result = actions.execute_tool(
identifier="user_outlook_789",
tool_name="outlook_list_calendar_events",
tool_input={
"filter": "start/dateTime ge '2026-04-01T00:00:00' and end/dateTime le '2026-04-07T23:59:59'",
"select": "subject,start,end,location,attendees",
"orderby": "start/dateTime asc",
"top": 20
}
)
# { "value": [ { "subject": "...", "start": { "dateTime": "..." }, ... } ] }
Create a calendar event
Schedule a meeting with attendees, a Teams link, and a reminder. subject, start_datetime, start_timezone, end_datetime, and end_timezone are required.
result = actions.execute_tool(
identifier="user_outlook_789",
tool_name="outlook_create_calendar_event",
tool_input={
"subject": "Q2 Planning Kickoff",
"start_datetime": "2026-04-01T10:00:00",
"start_timezone": "America/New_York",
"end_datetime": "2026-04-01T11:00:00",
"end_timezone": "America/New_York",
"attendees_required": "alice@company.com,bob@company.com",
"attendees_optional": "carol@company.com",
"body_content": "Please review the Q2 roadmap doc before joining.",
"body_contentType": "text",
"isOnlineMeeting": True,
"onlineMeetingProvider": "teamsForBusiness",
"isReminderOn": True,
"reminderMinutesBeforeStart": 15
}
)
# Returns the full event object including the Teams join URL
Update an existing event
Reschedule or modify an event by ID. Only the fields you provide are changed — everything else stays untouched.
result = actions.execute_tool(
identifier="user_outlook_789",
tool_name="outlook_update_calendar_event",
tool_input={
"event_id": "AAMkAGI2...",
"start_datetime": "2026-04-01T14:00:00",
"start_timezone": "America/New_York",
"end_datetime": "2026-04-01T15:00:00",
"end_timezone": "America/New_York",
"attendees_required": "alice@company.com,bob@company.com,dave@company.com"
}
)
Create a recurring event
Set up a repeating meeting — weekly standups, bi-weekly reviews — using the recurrence parameters. recurrence_type sets the pattern; recurrence_range_type controls when it ends.
result = actions.execute_tool(
identifier="user_outlook_789",
tool_name="outlook_create_calendar_event",
tool_input={
"subject": "Weekly Team Standup",
"start_datetime": "2026-04-06T09:00:00",
"start_timezone": "America/Chicago",
"end_datetime": "2026-04-06T09:30:00",
"end_timezone": "America/Chicago",
"recurrence_type": "weekly",
"recurrence_days_of_week": "Monday",
"recurrence_interval": 1,
"recurrence_range_type": "endDate",
"recurrence_start_date": "2026-04-06",
"recurrence_end_date": "2026-06-30",
"isOnlineMeeting": True
}
)
Framework wiring
5 Wiring into your agent framework
Scalekit integrates directly with LangChain. The agent decides what to schedule or query; Scalekit handles auth on every invocation. No token plumbing in your agent logic.
from langchain_anthropic import ChatAnthropic
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from scalekit.langchain import get_tools
outlook_tools = get_tools(
connection_name="outlook",
identifier="user_outlook_789"
)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a scheduling assistant. Use the available tools to help manage the user's Outlook calendar."),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), outlook_tools, prompt)
result = AgentExecutor(agent=agent, tools=outlook_tools).invoke({
"input": "Schedule a 1-hour Q2 planning meeting next Monday at 10am EST with alice and bob, add a Teams link"
})
Other frameworks supported
Tool reference
All 5 Outlook tools
Calendar management via Microsoft Graph. Your agent calls tools by name — no API wrappers to write.
outlook_create_calendar_event
Create a calendar event with required/optional/resource attendees, recurrence patterns, Teams meeting links, reminders, and sensitivity settings
outlook_get_calendar_event
Retrieve a specific event by ID, including attendee response status and online meeting join URL
outlook_list_calendar_events
List events with OData filtering, sorting, field selection, and pagination — supports date range and subject-based queries
outlook_update_calendar_event
Update any field on an existing event — time, attendees, location, online meeting settings, recurrence — without recreating the event
outlook_delete_calendar_event
Permanently delete a calendar event by ID
Connector notes
Outlook-specific behavior
Azure app registration is required before any auth flow will work
Outlook does not offer a Scalekit-managed app. You must register your own application in Microsoft Entra ID
(portal.azure.com), configure the Scalekit redirect URI, and enter your Azure Client ID and Secret in the
Scalekit dashboard. This must be completed before any user can authorize. The Scalekit dashboard walks
through the exact steps.
Multitenant account type selection affects which users can authorize
During Azure app registration, you choose supported account types. For B2B products serving corporate
Microsoft 365 users, select “Accounts in any organizational directory (Any Azure AD directory —
Multitenant) and personal Microsoft accounts.” Choosing single-tenant locks out all users outside your
own Azure AD. This setting cannot be changed after registration without re-registering the app.
Enterprise tenant admin consent can block user authorization
Some Microsoft 365 organizations require an IT admin to pre-approve third-party OAuth apps before any
user in the org can authorize. If users report seeing a “need admin approval” screen, the customer's
IT admin must grant tenant-wide consent for your Azure app in their Microsoft Entra admin center.
This is a tenant policy decision — not a Scalekit or code issue.
Datetime format and timezone are both required
All start_datetime and end_datetime values must be in RFC3339 format without a UTC offset
(e.g. 2026-04-01T10:00:00). The timezone must be supplied as a separate field using an IANA
timezone string (e.g. America/New_York) or a Windows timezone name (e.g. Eastern Standard Time).
Omitting the timezone field causes a Microsoft Graph 400 error.
Infrastructure decision
Why not build this yourself
The Microsoft Graph OAuth flow is documented. Token storage isn’t technically hard. But here’s what you’re actually signing up for:
PROBLEM 01
Azure app registration with the correct multitenant account type — getting it wrong locks out entire customer organizations and requires re-registering the app
PROBLEM 02
Microsoft access tokens expire in one hour — refresh behavior differs between personal and organizational accounts, with silent failures if handled incorrectly
PROBLEM 03
Tenant-level revocation by IT admins — a user’s token can be invalidated without the user taking any action, requiring detection and graceful re-authorization flows
PROBLEM 04
Microsoft Graph permission scopes must be declared at registration and requested during auth — adding new capabilities later forces every user through re-authorization
That’s one connector. Your agent product will eventually need Salesforce, Gmail, Slack, Notion, and whatever else your customers ask for. Each has its own OAuth quirks and failure modes.
Scalekit maintains every connector. You maintain none of them.