Skip to content

Throttling

Microsoft Graph and SharePoint both throttle with HTTP 429 and a Retry-After header; Graph also reports how close an app is to its limit. The library reads these signals, retries with the server's delay (falling back to exponential backoff with jitter) and can pace a whole fleet proactively.

Unlike SharePoint's static service limits, Graph throttling is dynamic — evaluated per scope (per app, per tenant, per app + tenant, per resource) and per request type — so the library reacts to the signals rather than enforcing a fixed budget. The quotas below are still declared as data (one quotas module per product, plus the one row no product owns) so a workload can be designed from the numbers instead of discovered by being throttled.

Signals

Header Direction Meaning
Retry-After response Seconds to wait before retrying a 429/503
x-ms-throttle-limit-percentage response Share of the limit consumed (0.8–1.8); >= 1.0 means throttling
x-ms-resource-unit response The cost of this request (Identity resource units)
x-ms-throttle-scope response The throttled scope (<Scope>/<Limit>/<AppId>/<TenantId...>)
x-ms-throttle-information response Why (CPULimitExceeded, WriteLimitExceeded, ...)
x-ms-throttle-priority request low / normal / high — low is throttled first

What the library does

result = client.users.top(10).get().execute_query_retry()   # honor Retry-After + backoff
client.execute_batch(concurrency=5)                         # retries throttled sub-requests

client.with_rate_limit()          # pace a fleet from the signals (proactive)
client.with_rate_limit(per_scope=True)   # pace each x-ms-throttle-scope separately
client.with_throttle_priority("low")     # mark background work low priority

client.with_rate_limit() reads every response (including batch sub-responses) and gates the group on Retry-After, Graph's x-ms-throttle-limit-percentage and SharePoint's health score. When a response reports x-ms-resource-unit, the proactive pace is scaled by that request cost, so an expensive call rests proportionally longer. With per_scope=True a throttled x-ms-throttle-scope pauses only requests tagged with the matching RequestOptions.throttle_scope. See office365.runtime.http.throttling and office365.runtime.retry.

Best practices to avoid throttling

  • Honor Retry-After — it is the server telling you exactly how long to wait; retrying earlier only extends the throttle.
  • Pace proactively with with_rate_limit(); mark interactive calls with_throttle_priority("high") and background jobs "low" so low-priority work is throttled first.
  • Share the load: avoid a large fan-out of concurrent connections; spread requests over time rather than bursting.
  • Keep responses small: use $select/$top/$expand deliberately; avoid oversized $top and deep expansions that inflate server cost.
  • Prefer delta queries and change notifications over polling/scanning.
  • Batch related operations (JSON batching) and reduce operations per request. Outlook also caps how many requests a mailbox may have in flight: four at a time, and four sub-requests per JSON batch — so execute_batch(concurrency=4) is the ceiling a mailbox workload should ask for (OUTLOOK_QUOTAS in office365.outlook.quotas). A higher concurrency does not go faster: the surplus queues inside the service.
  • Watch X-SharePointHealthScore — when the farm is busy, ease off before it throttles you (the rate limiter already does this).
  • Set a descriptive User-Agent (e.g. ISV|Contoso|MyApp/1.0) so Microsoft can attribute and, if needed, raise your limits.
  • For bulk extraction, use Microsoft Graph Data Connect (not throttled).

Quotas (reference)

The numbers below are the limits Microsoft publishes; the first one reached triggers throttling. A Window of in flight means concurrency rather than a rate. Every row answers catalog(product=...); the Bound at column names the model that declares it (most rows are referenced by no model, and are reference data only).

Product Limit Value Window Scope Type Bound at Notes
graph any Microsoft Graph request 130,000 requests 10s app any — across all tenants
cloud-communications cloud communications 50,000 requests 15s app+tenant any Call calls
cloud-communications call records 15,000 requests 20s app any CallRecord all tenants; callRecord, participant and session resources
cloud-communications call records 10,000 requests 20s tenant any CallRecord all applications
cloud-communications call records 1,500 requests 20s app+tenant any CallRecord
cloud-communications call records 40 requests 20s call record any CallRecord.participants_v2, CallRecord.sessions sessions, participants and segments of one call record
cloud-communications call records 40 requests 20s collection any CallRecordCollection list call records
cloud-communications PSTN call records 1,000 requests 60s tenant any CallRecordCollection.get_direct_routing_calls directRoutingLogRow, pstnCallLogRow, pstnBlockedUsersLogRow, smsLogRow
cloud-communications PSTN call records 200 requests 60s app+tenant any CallRecordCollection.get_direct_routing_calls
cloud-communications PSTN call records 50 requests 60s collection any CallRecordCollection.get_direct_routing_calls per collection
cloud-communications cloud communications 10,000 requests 30s app+tenant any Presence presence
cloud-communications cloud communications 750 requests 30s app read VirtualEvent virtual events; across all tenants
cloud-communications cloud communications 15 requests 30s app write VirtualEvent virtual events; across all tenants
directory identity 3,500 resource_units 10s app+tenant any DirectoryObject, Domain, SubscribedSku small tenants (<50 users); 5,000 medium (50-500), 8,000 large (>500)
directory identity 150,000 resource_units 20s app any DirectoryObject, Domain, SubscribedSku
directory identity 3,000 requests 150s app+tenant write DirectoryObject, Domain, SubscribedSku
directory identity 35,000 requests 300s app write DirectoryObject, Domain, SubscribedSku
directory identity 18,000 requests 300s tenant write DirectoryObject, Domain, SubscribedSku
directory invitations 150 requests 5s tenant any Invitation any operation on /invitations, all apps
directory identity protection and conditional access 1 request 1s tenant any ConditionalAccessPolicy riskDetection, namedLocation, conditionalAccessPolicy — no Retry-After on 429
excel excel 5,000 requests 10s app any Workbook all tenants
excel excel 1,500 requests 10s app+tenant any Workbook
files files and lists 3,000 requests 300s user any BaseItem, Permission
files files and lists 53,687,091,200 bytes 3600s user any BaseItem, Permission ingress
files files and lists 107,374,182,400 bytes 3600s user any BaseItem, Permission egress
files files and lists 1,250 resource_units 60s app+tenant any BaseItem, Permission tiers by license count: 1,250 (<1,000 users); 2,500 (<5,000); 3,750 (<15,000); 5,000 (<50,000); 6,250 (50,000+)
files files and lists 300 requests 300s app+tenant any BaseItem, Permission specific sharing APIs
files files and lists 429,496,729,600 bytes 3600s app+tenant any BaseItem, Permission ingress
files files and lists 429,496,729,600 bytes 3600s app+tenant any BaseItem, Permission egress
files files and lists 3,000 requests 300s site any BaseItem, Permission anonymous links
files files and lists 107,374,182,400 bytes 7200s site any BaseItem, Permission anonymous egress (download)
onenote onenote 120 requests 60s app+user any — requests per minute
onenote onenote 400 requests 3600s app+user any — requests per hour
onenote onenote 5 requests in flight app+user any — in flight at any moment
onenote onenote 240 requests 60s app any — requests per minute
onenote onenote 800 requests 3600s app any — requests per hour
onenote onenote 20 requests in flight app any — in flight at any moment
outlook outlook 10,000 requests 600s app+mailbox any Calendar, MailFolder, OutlookItem, OutlookUser v1.0 and beta endpoints
outlook outlook 4 requests in flight app+mailbox any Calendar, MailFolder, OutlookItem, OutlookUser in flight at any moment
outlook outlook 157,286,400 bytes 300s app+mailbox any AttachmentCollection upload (PATCH, POST, PUT)
outlook outlook 4 requests in flight batch any GraphClient.execute_batch sub-requests in flight per JSON batch that addresses Outlook; dependsOn serializes them instead
reports microsoft 365 reports 14 requests 600s app+tenant any ReportRoot CSV, per report API
reports microsoft 365 reports 40 requests 600s tenant any ReportRoot CSV, per report API, all apps
reports microsoft 365 reports 100 requests 600s app+tenant any ReportRoot JSON (beta), per report API
security security 150 requests 60s app+tenant any Security alert, securityActions, secureScore
security security 10,000 requests 600s app+tenant any Security secureScore, secureScorecontrolProfile
security security 4 requests in flight app+tenant any Security secureScore, secureScorecontrolProfile — in flight at any moment
security security 1,000 requests 60s app+tenant any — tiIndicator
security security 5 requests 60s app+tenant any CasesRoot eDiscovery cases
security security 200 requests 86400s tenant any AuditCoreRoot.queries audit log query submissions per rolling 24 hours; baseline, may be higher
security security 50 requests in flight tenant any AuditCoreRoot.queries audit log queries queued or running at any moment
security security 1,000,000 records — query any AuditLogQuery per audit log query; exceeding it ends the query successfully, with isRecordCountLimitExceeded set
subscriptions subscription change 2,000 requests 20s app write — POST, PUT, DELETE, PATCH
subscriptions subscription change 500 requests 20s app+tenant write — POST, PUT, DELETE, PATCH
subscriptions subscription reauthorize 4,000 requests 20s app write — POST /subscriptions/{id}/reauthorize
subscriptions subscription reauthorize 1,000 requests 20s app+tenant write — POST /subscriptions/{id}/reauthorize
subscriptions subscription 2,000 requests 20s app read — GET /subscriptions/{id}
subscriptions subscription 500 requests 20s app+tenant read — GET /subscriptions/{id}
subscriptions subscription list 40 requests 20s app read SubscriptionCollection GET /subscriptions
subscriptions subscription list 25 requests 20s app+tenant read SubscriptionCollection GET /subscriptions
teams microsoft teams 1,500 requests 1s app read — default — any request a resource table does not name
teams microsoft teams 30 requests 1s app+tenant read — default — any request a resource table does not name
teams microsoft teams 1 request 1s team, channel or chat read — default — any request a resource table does not name
teams microsoft teams 1 request 1s user read — default — any request a resource table does not name
teams microsoft teams 300 requests 1s app write — default — any request a resource table does not name
teams microsoft teams 30 requests 1s app+tenant write — default — any request a resource table does not name
teams microsoft teams 1 request 1s team, channel or chat write — default — any request a resource table does not name
teams microsoft teams 1 request 1s user write — default — any request a resource table does not name
teams microsoft teams 150 requests 1s app delete — default — any request a resource table does not name
teams microsoft teams 15 requests 1s app+tenant delete — default — any request a resource table does not name
teams microsoft teams 1 request 1s team, channel or chat delete — default — any request a resource table does not name
teams microsoft teams 1 request 1s user delete — default — any request a resource table does not name
teams microsoft teams 1,500 requests 1s app read Team GET /teams/{id}
teams microsoft teams 30 requests 1s app+tenant read Team GET /teams/{id}
teams microsoft teams 4 requests 1s team, channel or chat read Team GET /teams/{id}
teams microsoft teams 1,200 requests 1s app read Team.channels GET /teams/{id}/channels
teams microsoft teams 60 requests 1s app+tenant read Team.channels GET /teams/{id}/channels
teams microsoft teams 4 requests 1s team, channel or chat read Team.channels GET /teams/{id}/channels
teams microsoft teams 200 requests 1s app read Channel.messages GET /teams/{id}/channels/{id}/messages
teams microsoft teams 20 requests 1s app+tenant read Channel.messages GET /teams/{id}/channels/{id}/messages
teams microsoft teams 1 request 1s team, channel or chat read Channel.messages GET /teams/{id}/channels/{id}/messages
teams microsoft teams 500 requests 1s app write Channel.messages POST /teams/{id}/channels/{id}/messages
teams microsoft teams 50 requests 1s app+tenant write Channel.messages POST /teams/{id}/channels/{id}/messages
teams microsoft teams 1 request 1s team, channel or chat write Channel.messages POST /teams/{id}/channels/{id}/messages
teams microsoft teams 1 request 1s user write Channel.messages POST /teams/{id}/channels/{id}/messages
teams microsoft teams 200 requests 1s app read Chat.messages GET /chats/{id}/messages
teams microsoft teams 20 requests 1s app+tenant read Chat.messages GET /chats/{id}/messages
teams microsoft teams 1 request 1s team, channel or chat read Chat.messages GET /chats/{id}/messages
teams microsoft teams 200 requests 1s app write Chat.messages POST /chats/{id}/messages
teams microsoft teams 20 requests 1s app+tenant write Chat.messages POST /chats/{id}/messages
teams microsoft teams 1 request 1s team, channel or chat write Chat.messages POST /chats/{id}/messages
teams microsoft teams 1 request 1s user write Chat.messages POST /chats/{id}/messages