Fix it now
Microsoft documents 429 as the caller having sent too many requests in a time window and exceeded a predetermined limit, and 503 as the service not being ready to handle the request, commonly through temporary load spikes. Both carry a Retry-After header, and honouring it is documented as the fastest way to stop being throttled.
- Read the Retry-After header and wait exactly that long. Microsoft states that throttled requests count towards usage limits, so ignoring it produces more throttling, not less.
- Do not build against RateLimit headers. SharePoint Online does not return or support them; honour Retry-After instead.
- Identify what is generating the load – a migration tool, a flow, a reporting script – before changing any code.
- Decorate the traffic with a User-Agent in the documented format:
ISV|CompanyName|AppName/Version, orNONISV|CompanyName|AppName/Versionfor an in-house application. Well-decorated traffic is prioritised. - Batch the work rather than making one call per item, and add exponential backoff between batches.
- For a list view threshold error, index the column the query filters on and narrow the filter so it returns a small set.
- Check the Service Health Dashboard before assuming it is you. Widespread 503 with no pattern is an incident.
If the job completes without 429s in the log, you are done. If it still throttles at modest volume, the next section covers the published per-second limits and how the cost model works.
Why it happens
SharePoint Online is multi-tenant and the resources behind it are shared. To stop one busy tenant degrading everyone else, the service meters usage and refuses requests once a caller exceeds its allowance. The allowance is not a simple request count – in batching, Microsoft documents that requests in a batch are evaluated individually by resource units – which is why a small number of expensive queries can be throttled while thousands of cheap ones pass without complaint.
Microsoft documents both codes as including a Retry-After header indicating how long the caller should wait, and describes honouring it as the fastest way to handle being throttled, because the service works out the right time dynamically. The warning attached matters as much as the advice: throttled requests still count towards usage limits, so a tight retry loop makes things worse. Two published figures are worth knowing – app-only access with Sites.Read.All is throttled at 25 requests per second, and delegated user permissions at 10 requests per second per user, aggregated across all applications.
The list view threshold is a different limit with the same philosophy. A view showing more than 5,000 items can hit it, and in SharePoint in Microsoft 365 that figure cannot be adjusted – it exists to keep performance predictable on a shared service. An indexed column changes the arithmetic: the service can find the matching rows directly rather than scanning, so a filtered view over a very large list works when the filter is on an indexed column and returns a modest result set.
The client retries immediately instead of backing off
You have this one if Throttling starts mildly and escalates until nothing succeeds, and the logs show retries within milliseconds.
- Read and honour Retry-After on every 429 and 503.
- Add exponential backoff with jitter for responses that carry no header.
- Cap concurrency deliberately; parallelism is usually what turns a slow job into a throttled one.
- Run bulk work outside business hours so your allowance is not competing with real users.
Ignoring Retry-After is the single most common cause of prolonged throttling, because the refused requests are still counted against you.
The traffic is not identified
You have this one if An integration is throttled harder than its volume seems to justify, and sends a default or empty user agent.
- Set a User-Agent in the documented format so the service can attribute the traffic – ISV or NONISV, company name, application name and version.
- Use an application identity rather than a user identity for background work, so a person’s interactive allowance is not competing with your job.
- Keep one identity per workload rather than sharing one service account across every script you own.
- Log the correlation identifier from responses so support can trace a specific call if you escalate.
One call per item instead of one call per batch
You have this one if A job touching thousands of items generates thousands of requests and is throttled part way through, every time.
- Group operations using the batch endpoint for your platform, remembering that requests in a batch are evaluated individually by resource units.
- Request only the fields you need, and page results rather than fetching everything.
- Cache anything static, such as site or list identifiers, instead of looking it up every iteration.
- For large migrations, use a migration API or supported tool, which is built for volume.
A very large list is being queried without an index
You have this one if The list view threshold message appears on a specific view, query or filter while the rest of the site behaves normally.
- Index the column the query filters on, from the list’s indexed columns settings.
- Make the view’s filter select a small subset, and set a row limit so it pages rather than returning everything.
- Sort and filter on indexed columns only; an unindexed sort trips the same limit.
- Where a list has grown beyond what views handle comfortably, split the content across lists or use folders to reduce the working set.
Indexing a very large list can itself be slow and may need a quiet period. Plan it rather than doing it at month end.
It is a genuine service incident
You have this one if Widespread 503s affecting interactive users as well as scripts, with no change on your side.
- Check the Service Health Dashboard in the Microsoft 365 admin center for an active advisory.
- Confirm the problem is not limited to one client, network or region before raising it.
- Pause bulk jobs while an incident is active rather than letting them retry into it.
Full reference
Which limit you are hitting
| What you see | What it usually is |
|---|---|
| 429 with a Retry-After value | Per-caller throttling; wait exactly that long |
| 503 during heavy automation | Load shedding, or a genuine service problem |
| Throttling only for one script | That script is undecorated, unbatched or looping |
| The list view threshold message | An unindexed or unfiltered query against a very large list |
| activityLimitReached | The app or user has been throttled |
| Everything fails everywhere at once | Check the Service Health Dashboard before changing any code |
Headers, and one that does not exist
| Header | Status in SharePoint Online |
|---|---|
| Retry-After | Returned on both 429 and 503, and the documented way to know when to try again |
| IETF RateLimit headers | Not returned and not supported. Applications must not depend on them |
| User-Agent | Should be set in the documented ISV or NONISV format; well-decorated traffic is prioritised |
That middle row costs people real time. RateLimit headers are common enough elsewhere that developers write against them by habit, find nothing, and conclude the service is not sending throttling signals at all. It is – in Retry-After.
The published per-second limits
- Application-only access with Sites.Read.All is throttled at 25 requests per second.
- Delegated user permissions are throttled at 10 requests per second per user, aggregated across all applications using that user’s context.
- Because the delegated limit aggregates, two of your own tools sharing one service account halve what each can do.
- That aggregation is the practical argument for an application identity per workload, ahead of any argument about credentials.
Large lists in more detail
The threshold is 5,000 items in a list view, and in SharePoint in Microsoft 365 it cannot be raised – it exists to keep a shared service predictable. The documented ways to work within it are indexing, filtering, folders, the search box, personal views with fewer items, splitting data across related lists, RSS, working with exported data offline, and using the modern experience, which handles large views better than the classic one. The point of all of them is the same: reduce the number of items the query has to consider, rather than asking the service to consider more.
Designing a job that does not get throttled
- Decide the identity first: an application identity for background work, one per workload.
- Decorate the traffic with a User-Agent in the documented format.
- Batch operations, and request only the fields you need.
- Honour Retry-After exactly, and add exponential backoff with jitter for anything without a header.
- Cap concurrency, and schedule bulk work away from the interactive peak.
- Log correlation identifiers throughout, so an escalation has evidence rather than a description.
Every code this article covers
| Code | What it points at | Source |
|---|---|---|
HTTP 429 |
The calling application sent too many requests in a time window and exceeded a predetermined limit; a Retry-After header indicates how long to wait | Microsoft Learn |
HTTP 503 |
The service is not ready to handle the request, commonly because of temporary load spikes; a Retry-After header is included | Microsoft Learn |
The attempted operation is prohibited because it exceeds the list view threshold |
The query would examine too many list items. The threshold is 5,000 items and cannot be changed in SharePoint in Microsoft 365; the literal message string is not published by Microsoft | not published by the vendor |
activityLimitReached |
The app or user has been throttled | Microsoft Learn |
SPQueryThrottledException |
No Microsoft reference page for this exception could be sourced. The condition it names, the list view threshold, is documented; diagnose it as that rather than as a distinct error | not published by the vendor |
TooManyRequests |
The status message for HTTP 429: the client application has been throttled and should not repeat the request until an amount of time has elapsed | Microsoft Learn |
Confirm the fix worked
- The job re-runs and completes with no 429 responses in the log.
- Your client logs show it honouring Retry-After when a throttle does occur, rather than retrying immediately.
- The affected list view renders without the threshold message.
- The columns you indexed appear in the list’s indexed columns settings.
- Background work is running under an application identity with a decorated User-Agent, not under a person’s account.
Questions people ask about this
Can I pay to have the limits raised?
No. Throttling limits are part of how the shared service protects itself and are not sold as an upgrade. The only route is to make the workload cheaper and better behaved.
How long does throttling last?
As long as the Retry-After value says, and longer if you keep calling during it, because throttled requests still count towards your usage. A client that backs off properly usually recovers within minutes.
Why can I not see RateLimit headers?
Because SharePoint Online does not return or support them. Microsoft says so explicitly and directs applications to honour Retry-After instead. Writing against RateLimit headers here will silently do nothing.
Is the list view threshold still enforced?
Yes. A view showing more than 5,000 items can hit it, and in SharePoint in Microsoft 365 the limit cannot be adjusted. Modern lists handle large volumes far better, but filtering on an indexed column remains the reliable answer.
Should background jobs use a user account or an application identity?
An application identity. The delegated limit is 10 requests per second per user aggregated across every application using that user’s context, so sharing one account between tools divides the same allowance between them.
