Skip to content

Microsoft Graph API Rate Limits ​

Atlas talks to Microsoft 365 exclusively through the Microsoft Graph API. Graph's throttling model determines how far a backup pipeline can be parallelized, which matters most when a job queue (such as pg-boss) orchestrates backups across many mailboxes or tenants.

Two-layer throttling model ​

Every Graph request is evaluated against two independent categories of limits at once. The first limit reached triggers a 429 Too Many Requests response:

LayerScopeLimit
GlobalPer app across all tenants130,000 requests / 10 seconds
Service-specificVaries by service (see below)Separate pool per service

Atlas is unlikely to hit the global limit unless it runs hundreds of tenants simultaneously from a single app registration. Service-specific limits are the operational bottleneck for most deployments.

Independent service pools ​

Microsoft Graph enforces separate, independent throttling pools for each service. Consuming quota in one pool does not affect quota in another. For Atlas operators that means:

  • A mailbox backup (Outlook pool, per-mailbox) does not compete with a future OneDrive backup (SharePoint pool, per-tenant) for the same user.
  • Running 50 mailbox backups in parallel uses 50 independent Outlook quota budgets, one per mailbox.
  • All SharePoint/OneDrive operations for a single tenant share one budget regardless of how many users or drives are targeted.

Atlas uses three pools:

PoolScopeCost modelUsed by
OutlookPer app per mailboxFlat (1 req = 1)Mail backup and restore
SharePoint/OneDrivePer app per tenantResource units (1–5 RU/req)OneDrive backup (future)
IdentityPer app per tenantResource units (1–5 RU/req)Mailbox discovery

Pool 1: Outlook / Exchange Online ​

Official source: Graph throttling limits: Outlook service limits

Scope: Per app ID per mailbox. Limits for one mailbox are completely independent of limits for any other mailbox, even within the same tenant.

Applies to: Mail API, Calendar API, Personal Contacts API, Search API, To-do Tasks API, Mailbox Import/Export API.

Cost model: flat. Every request counts as 1, regardless of endpoint or HTTP method.

Limits ​

LimitValue
API requests per 10-minute window10,000
Maximum concurrent requests per mailbox4
Upload body size per 5-minute window (POST/PATCH/PUT)150 MB

Batching behavior ​

Microsoft Graph sends up to 4 individual Outlook requests from a batch at a time, regardless of target mailboxes, which is consistent with the 4-concurrent-request limit per mailbox. Using dependsOn in batch requests forces sequential execution (1 at a time).

Throttle response ​

429 Too Many Requests with a Retry-After header specifying how many seconds to wait. Throttled requests still count toward usage limits.

How Atlas backs off ​

A throttled response does two things. The call that received it enters exponential backoff, honoring Retry-After, for up to 12 retries. It also raises a global throttle fence for the length of the Retry-After window, and every mailbox operation in the process waits on that fence before issuing its next HTTP request.

The fence engages on the first throttled response, not after a call has spent its retry budget, and calls already inside a retry loop check it between attempts. One throttled mailbox therefore pauses the whole Outlook path for the cooldown Microsoft asked for, instead of leaving every other mailbox to keep sending requests that are counted against the same limit and, being throttled, accomplish nothing.

Retry lives in exactly one layer. The Graph SDK's own retry handler is disabled, so a single logical call is bounded at 13 HTTP attempts. Leaving both enabled multiplied the budgets and let one call reach roughly 52 attempts against the limits the backoff exists to respect.

SharePoint and OneDrive have no equivalent fence yet. Their calls back off individually on the same schedule.

Atlas operations in the Outlook pool ​

OperationGraph endpointCost
list_mail_foldersGET /users/{id}/mailFolders, /mailFolders/{wellKnownName}1 request per folder page, plus up to 9 well-known folder reads
fetch_deltaGET /users/{id}/mailFolders/{id}/messages/delta1 request per page
fetch_messageGET /users/{id}/messages/{id}1 request
fetch_attachmentsGET /users/{id}/messages/{id}/attachments1 request
create_mail_folderPOST /users/{id}/mailFolders1 request
create_messagePOST /users/{id}/mailFolders/{id}/messages1 request
add_attachmentPOST /users/{id}/messages/{id}/attachments1 request + upload bytes
create_upload_sessionPOST /users/{id}/messages/{id}/attachments/createUploadSession1 request
upload_chunkPUT {upload_url}1 request + upload bytes
count_folder_messagesGET /users/{id}/mailFolders/{id}?$select=totalItemCount1 request
list_folder_messagesGET /users/{id}/mailFolders/{id}/messages1 request

Why Outlook is the most parallelizable pool ​

The 10,000 req/10min budget is per mailbox, not per tenant, so N mailboxes can be backed up in parallel without competing for each other's quota. The real constraints are concurrency (4 parallel requests per mailbox) and the Atlas sliding window limiter (9,600 requests per mailbox per 10 minutes, leaving a 4% safety margin).


Pool 2: SharePoint / OneDrive ​

Official source: How to avoid getting throttled or blocked in SharePoint Online

Scope: Per app per tenant. All SharePoint and OneDrive calls from a single app registration share one quota bucket per tenant, regardless of the number of sites, drives, or users targeted.

Cost model: resource units (RU). Each Graph request has a predetermined cost.

OneDrive cost data

OneDrive and SharePoint backup are implemented, but Atlas does not yet emit cost data for this pool: graph_cost is populated for Outlook operations only. The limits below are documented so operators can size drive schedules against a budget Atlas cannot currently measure for them.

Resource unit costs ​

Operation typeCost
Single-item GET (by ID, delta with token)1 RU
Multi-item list/search, delta without token2 RU
Permission operations, $expand=permissions5 RU

Microsoft reserves the right to change these costs. The Atlas GRAPH_SERVICE_LIMITS.sharepoint_onedrive constant provides the current values.

Per-app-per-tenant limits (scale with tenant license count) ​

LicensesRU / minuteRU / 24 hours
0–1,0001,2501,200,000
1,001–5,0002,5002,400,000
5,001–15,0003,7503,600,000
15,001–50,0005,0004,800,000
50,000+6,2506,000,000

Tenant-level limits (all apps combined) ​

LicensesRU / 5 minutes
0–1,00018,750
1,001–5,00037,500
5,001–15,00056,250
15,001–50,00075,000
50,000+93,750

User-level limits ​

LimitValue
Requests3,000 / 5 minutes
Ingress50 GB / hour
Egress100 GB / hour

Per-app-per-tenant bandwidth ​

DirectionLimit
Ingress400 GB / hour
Egress400 GB / hour

RateLimit headers (preview) ​

SharePoint proactively returns RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers (IETF draft-03) when an app consumes ≥ 80% of its 1-minute resource unit budget, letting clients back off before hitting 429. Atlas will read these headers when OneDrive backup support is implemented.

Source: RateLimit headers, preview

Throttle responses ​

ResponseMeaning
429 Too Many RequestsApp exceeded the rate limit. Includes Retry-After.
503 Service UnavailableService-level load spike. Includes Retry-After.

Throttled requests still count toward limits. Persistent offenders may be blocked completely (503 indefinitely, with notification via the Office 365 Message Center).

Scheduling implication ​

The SharePoint budget is per-tenant, not per-user, so a SaaS operator must track and coordinate OneDrive backup jobs across all users of a given tenant in their external scheduler. You cannot run N OneDrive backups in parallel for the same tenant the way you can for N mailbox backups.

Future Atlas operations in the SharePoint/OneDrive pool ​

OperationGraph endpointRU cost
delta_drive (with token)GET /drives/{id}/root/delta?token=...1 RU
delta_drive (without token)GET /drives/{id}/root/delta2 RU
list_drive_itemsGET /drives/{id}/items/{id}/children2 RU
download_fileGET /drives/{id}/items/{id}/content1 RU + egress bytes
upload_filePUT /drives/{id}/items/{id}/content1 RU + ingress bytes
get_driveGET /users/{id}/drive1 RU

Pool 3: Identity / Directory (Microsoft Entra ID) ​

Official source: Graph throttling limits: Identity and access service limits

Scope: Per app per tenant, plus a global per-app limit across all tenants. Uses a token-bucket algorithm.

Cost model: resource units (RU). Each operation has a base RU cost that query parameters can modify.

Per-app-per-tenant limits (scale with tenant user count) ​

Tenant tierUsersRU / 10 seconds
S< 503,500
M50–5005,000
L> 5008,000

Write quota per app+tenant: 3,000 requests / 2 minutes 30 seconds.

Global per-app limits (across all tenants) ​

LimitValue
Read150,000 RU / 20 seconds
Write35,000 requests / 5 minutes

Per-tenant limits (all apps combined) ​

LimitValue
Write requests18,000 / 5 minutes

Base resource unit costs (selection) ​

Atlas uses only read operations in the Identity pool. Relevant base costs:

EndpointBase cost
GET /users2 RU
GET /users/{id}1 RU (default)
GET /reports/getMailboxUsageDetail1 RU (default)
Unlisted read paths1 RU

Cost modifiers ​

Applied on top of the base cost:

ModifierEffect
$select present−1 RU
$expand present+1 RU
$top with value < 20−1 RU

Atlas uses $select on /users calls, reducing effective cost to approximately 1 RU per call in practice.

Atlas operations in the Identity pool ​

OperationGraph endpointEffective cost
mailbox_existsGET /users/{id}?$select=id~1 RU (1 base − 0, $select modifier applies)
list_users (mailbox discovery)GET /users?$select=...&$filter=...~1 RU per page (2 base − 1 for $select)

Atlas touches the Identity pool only during mailbox discovery (listing all mailboxes in a tenant) and individual mailbox existence checks, typically a handful of requests at job startup. It becomes a scheduling concern only at very large scale, with frequent tenant-wide mailbox listing across many tenants simultaneously.


Cross-pool summary ​

PoolScopeCost modelBottleneck for
OutlookPer mailboxFlatMail backup and restore (primary bottleneck)
SharePoint/OneDrivePer tenantResource unitsOneDrive backup (future)
IdentityPer tenantResource unitsMailbox discovery at scale
GlobalPer appFlat100+ concurrent tenants

Key implications for SaaS scheduling:

  • Outlook is the most parallelizable pool. N parallel mailbox backups use N independent budgets, so scale horizontally.
  • SharePoint requires tenant-level coordination. Treat all OneDrive jobs for one tenant as sharing one budget.
  • Identity cost is minimal per job. Ignore it for individual jobs and monitor at the tenant-wide discovery level.
  • Global limit is a backstop. Unlikely to be reached unless you operate hundreds of active tenants simultaneously.

Observing and using the limits in code ​

graph_cost is measured at the transport, so one recorded request is one HTTP request: each @odata.nextLink page and each retried attempt is counted separately. Cooldowns computed from it therefore reflect the quota actually consumed rather than the number of connector calls made.

Atlas exports the GRAPH_SERVICE_LIMITS constant so your SaaS layer can use the same authoritative numbers for scheduling decisions:

typescript
import { getGraphCost, GRAPH_SERVICE_LIMITS } from '@wisecom/atlas-sdk';
import type { OperationCost } from '@wisecom/atlas-sdk';

// After a backup job finishes -- successfully or not. A run that threw has
// usually spent MORE quota than one that completed, so cool down on it too.
const cost: OperationCost | undefined = result?.graph_cost ?? getGraphCost(err);
if (!cost) return;

const outlook = GRAPH_SERVICE_LIMITS.outlook;

// Estimate cooldown: how much of the 10-min window did we consume?
const outlook_used = cost.by_service.outlook?.requests ?? 0;
const usage_ratio = outlook_used / outlook.requests_per_window;
const cooldown_ms = Math.ceil(usage_ratio * outlook.window_duration_ms);

console.log(`Used ${outlook_used}/${outlook.requests_per_window} Outlook requests`);
console.log(`Suggested cooldown: ${cooldown_ms}ms`);

See the Programmatic SDK reference for the full OperationCost type and a complete pg-boss orchestration example.


Official Microsoft documentation ​

ResourceURL
Global and all service-specific limitshttps://learn.microsoft.com/en-us/graph/throttling-limits
General throttling guidancehttps://learn.microsoft.com/en-us/graph/throttling
Outlook service limitshttps://learn.microsoft.com/en-us/graph/throttling-limits#outlook-service-limits
SharePoint/OneDrive throttlinghttps://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online
SharePoint RateLimit headershttps://learn.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online#ratelimit-headers---preview
Identity and access service limitshttps://learn.microsoft.com/en-us/graph/throttling-limits#identity-and-access-service-limits

Released under the Apache-2.0 License.