Guide

Microsoft Graph getSchedule Free/Busy

Call the Microsoft Graph getSchedule action to read free/busy for Outlook and Exchange calendars, then check availability across providers from the command line.

Written by Nick Barraclough Product Manager

VerifiedCLI 3.1.22 · Microsoft · last tested June 19, 2026

What is the Microsoft Graph getSchedule action?

getSchedule is a single POST to /me/calendar/getSchedule (or /users/{id}/calendar/getSchedule) that reports free/busy status for one or more mailboxes. It answers "is this person open?" mainly through a packed availabilityView status string; the scheduleItems array can also include a subject and location when you have detailed access. One request reads up to 20 mailboxes at once.

This is the availability primitive for Outlook and Exchange Online. The request body needs four fields: schedules (an array of email addresses), startTime and endTime as dateTimeTimeZone objects, and availabilityViewInterval in minutes. According to the official calendar: getSchedule reference (the page may return 403 to automated fetchers but resolves in a browser), the docs describe it as a way to "get the free/busy availability information for a collection of users, distributions lists, or resources." The least-privileged permission is Calendars.ReadBasic; Calendars.Read and Calendars.ReadWrite also work.

The request below asks whether one person is busy over a working day. The interval is set to 30 minutes, which is the documented default, and both timestamps carry an explicit IANA time zone so Graph computes day boundaries correctly.

POST https://graph.microsoft.com/v1.0/me/calendar/getSchedule
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "schedules": ["alice@contoso.com"],
  "startTime": {
    "dateTime": "2026-06-20T09:00:00",
    "timeZone": "Pacific Standard Time"
  },
  "endTime": {
    "dateTime": "2026-06-20T17:00:00",
    "timeZone": "Pacific Standard Time"
  },
  "availabilityViewInterval": 30
}

What does the getSchedule response look like?

The response is a value array with one entry per requested mailbox. Each entry carries a scheduleId (the email address), a scheduleItems array of busy blocks, and an availabilityView string. The availabilityView string carries status codes only; the scheduleItems array can include a subject and location when the caller has detailed access, so request only the access the task needs.

The availabilityView string is the compact view: each character encodes one interval of length availabilityViewInterval. The codes are 0 free, 1 tentative, 2 busy, and 3 out of office; working elsewhere also folds into 0 in this string. So an 8-hour day at 30-minute intervals produces a 16-character string. The longer scheduleItems array carries the finer status of free, tentative, busy, oof, or workingElsewhere.

The payload below answers the earlier one-day query over a 09:00–17:00 window at 30-minute intervals. The busy block runs from 14:00 to 15:00, which is two slots, so the availabilityView carries two 2s in the matching positions. Read the string for a quick free/busy heatmap, or read scheduleItems when you need exact boundaries.

{
  "value": [
    {
      "scheduleId": "alice@contoso.com",
      "availabilityView": "0000000000220000",
      "scheduleItems": [
        {
          "status": "busy",
          "start": { "dateTime": "2026-06-20T14:00:00", "timeZone": "Pacific Standard Time" },
          "end":   { "dateTime": "2026-06-20T15:00:00", "timeZone": "Pacific Standard Time" }
        }
      ],
      "workingHours": {
        "daysOfWeek": ["monday","tuesday","wednesday","thursday","friday"],
        "startTime": "08:00:00.0000000",
        "endTime": "17:00:00.0000000"
      }
    }
  ]
}

How do the schedules and interval limits work?

Two limits shape every getSchedule call: the schedules array holds at most 20 email addresses, and availabilityViewInterval accepts a value from 5 to 1440 minutes. Cross either bound and Graph rejects the request rather than truncating the result, so batch mailboxes in groups of 20 when checking a large team.

The interval drives both resolution and string length. A 15-minute interval over an 8-hour window yields a 32-character availabilityView; a 60-minute interval over the same window yields 8 characters. Smaller intervals give finer slot detection but a longer string to parse. The default is 30 minutes, and the documented range runs from 5 minutes (fine-grained) to 1440 minutes (one block per day). Keep the start-to-end span tight: a one-week window at 30-minute resolution across 20 mailboxes is compact, while a multi-month span produces large arrays and slower responses.

The request below checks three people across a working week at 60-minute resolution. Each address maps to a separate entry in the response value array. Build the schedules array from a deduplicated set first, since two identical addresses still count against the 20-mailbox cap.

{
  "schedules": [
    "alice@contoso.com",
    "bob@contoso.com",
    "carol@contoso.com"
  ],
  "startTime": { "dateTime": "2026-06-22T00:00:00", "timeZone": "Pacific Standard Time" },
  "endTime":   { "dateTime": "2026-06-27T00:00:00", "timeZone": "Pacific Standard Time" },
  "availabilityViewInterval": 60
}

What errors and throttling does getSchedule return?

getSchedule is throttled like every other Graph call. When you exceed the per-app or per-mailbox limit, the service returns 429 Too Many Requests with a Retry-After header that names how many seconds to wait. Honoring that header is the single most important rule for reliable scheduling code.

The Microsoft Graph throttling limits guide (also browser-only for some fetchers) states the rule plainly: "When you get throttled, Microsoft Graph returns the HTTP status code 429 (Too Many Requests), and your request fails." The Outlook service category caps calendar reads per app per mailbox, so a loop checking 200 mailboxes in groups of 20 should pause on 429 and resume after the Retry-After delay. Other failures map to their HTTP code: a 403 usually means the app lacks Calendars.ReadBasic, a 400 means a malformed dateTimeTimeZone, and a 401 means the access token expired after its 3,600-second lifetime.

SignalMeaningFix
429ThrottledWait the Retry-After seconds, then retry
403Missing permissionGrant Calendars.ReadBasic and re-consent
400Bad request bodyFix the dateTimeTimeZone or interval range
401Expired tokenRefresh the access token

How do you check free/busy from the CLI?

The nylas calendar availability check command runs the same busy-interval lookup that getSchedule performs, but across Microsoft, Google, and other backends through one OAuth flow. You pass emails, a start, and a duration; the tool returns busy slots without you building dateTimeTimeZone objects or refreshing tokens by hand.

This removes four pieces of plumbing a direct integration owns: app registration with Calendars.ReadBasic, time-zone formatting, throttling retries on 429, and token refresh every 3,600 seconds. The command below checks two people for a 7-day window starting tomorrow morning. The -e/--emails flag takes a comma-separated list, -s/--start sets the window start, and -d/--duration accepts values like 8h, 1d, or 7d.

nylas calendar availability check \
  --emails alice@contoso.com,bob@contoso.com \
  --start "tomorrow 9am" \
  --duration 7d \
  --format json

How do you parse availability output with jq?

The --format json flag emits machine-readable output that pipes into jq, so a script or scheduling agent can pull busy intervals without screen scraping. The three accepted formats are text, json, and yaml; text is the default.

Piping to jq turns a one-line command into a building block for a cron job or agent tool. The example below requests JSON, then filters to the busy blocks so the next step in a pipeline sees only the data it needs. A 7-day check for two people typically returns a handful of intervals, small enough to inline into an LLM prompt under a few hundred tokens.

nylas calendar availability check \
  --emails alice@contoso.com,bob@contoso.com \
  --start "tomorrow 9am" \
  --duration 7d \
  --format json \
  | jq '.[].busy'

When should you call getSchedule directly instead?

Call Graph directly when you need a Microsoft-only control plane: room and equipment resource mailboxes, workingHours data per user, distribution-list expansion, or a UI that already embeds Microsoft's OAuth flow. Reach for the command-line path for terminal workflows, agent tools, cron jobs, and any script that should also work against Google without a second integration.

The trade-off is ownership, not capability. A direct getSchedule integration owns app registration with Calendars.ReadBasic, token refresh every 3,600 seconds, dateTimeTimeZone formatting, retry logic for 429 Retry-After, and the 20-mailbox batching loop. The CLI path owns command selection and output handling while the provider integration sits behind it. For a one-off availability check or a multi-provider scheduler, starting from the terminal keeps a short script from becoming a long-lived OAuth app maintained for one endpoint.

Next steps