Tags: Relational databasesAIDesktopCommand line
“The assistant can’t reach SharePoint right now, please try again later.” Buyers at a building-supplies distributor started getting that reply from their procurement agent on the last working day of September. Twenty minutes later the purchase request form in Power Apps began failing too, with a banner about too many requests, and the IT lead had two incidents open for what turned out to be one fault.
The distributor has about 1,800 Microsoft 365 licences. Procurement keeps a Purchase Orders list of roughly 38,000 items on one SharePoint site, with a Supplier lookup, a Requested By person column, a Depot choice, an Amount and a Status choice. A Copilot Studio agent answers buyers’ questions about orders. An n8n workflow runs a second agent every hour of the working day to chase late deliveries. Both read the list through Microsoft Graph, and to save an afternoon of admin work, the n8n workflow signs in as the same service account that runs the Power Automate flow behind the purchase request form.
Each of those choices is reasonable on its own. They fail together because each one draws on budgets that SharePoint keeps per user, per app and per tenant, and an agent spends those budgets in a way a report or a form never does.
Copilot Studio or n8n agent getting 429 from SharePoint
When SharePoint decides a caller has asked for too much, it answers HTTP 429, “Too many
requests”, or 503, “Server too busy”, with a Retry-After header giving the number of
seconds to wait. Microsoft Learn’s page
Avoid getting throttled or blocked in SharePoint Online
is plain about what comes next. Throttled requests still count towards the limits, so a
caller that retries early stays throttled for longer, and an app that keeps exceeding its
limits can be blocked outright.
The month-end logs showed both agents doing what that page warns against. In Copilot Studio, the agent’s connector retried after a short fixed delay. The n8n tool handed the error back to the model, and the model called the tool again with slightly different arguments, which is how language models usually treat a failed tool. Every retry was another request against a limit that had already run out.
The form failed for a duller reason. As far as SharePoint was concerned, its flow and the n8n agent were the same user, and SharePoint caps the requests made on behalf of one user, across every app, at 3,000 requests per 5 minutes. The agent had used them up by lunchtime.
How SharePoint throttling limits are shared
There are three budgets in play, and an agent can run any of them dry. The per-user limit is the one that took the form down. A Copilot Studio agent that acts with the signed-in user’s credentials shares that allowance with that person’s own browser and Teams, so a heavy agent session can slow down the person using it.
Apps are metered in resource units. Each Graph request has a published cost. A single item read or a delta request with a token is 1 resource unit, a page of items or a create, update or delete is 2, and anything on the permissions resource is 5. Every app registration gets its own allowance in each tenant, scaled to the licence count. A tenant with 1,001 to 5,000 licences, like the distributor, gives each app 2,500 resource units per minute and 2,400,000 per day. The smallest tenants get 1,250 resource units a minute.
Over both sits a tenant budget, 37,500 resource units per 5 minutes at the distributor’s size, and Microsoft’s page says that apps running against one tenant share that resource bucket. A second app registration buys nothing here. The same page lists separate app ids for the same application as a cause of tenant-wide throttling.
SharePoint Online also sends no RateLimit headers, so a tool gets no warning that a
budget is nearly spent. The first sign of trouble is a failed request.
Agent tool calls and resource units at month end
A Power BI report reads a list once per refresh. An agent reads it once per question, and usually more than once. A buyer asks which orders over 5,000 GBP for the Leeds depot are still waiting for goods to arrive. The model calls a list tool, reads the result, decides it needs supplier names, calls a second tool, then checks one order again before it answers. Four tool calls for one question is unremarkable.
Now price that against the Purchase Orders list. A tool that pages through it at 200 items a page reads 190 pages, and at 2 resource units a page that is 380 resource units for a single call. Four calls come to 1,520 resource units for one question. Two buyers asking in the same minute put the app past its 2,500 resource units per minute before anything else on the tenant has run. Divide the tenant’s 37,500 resource units per 5 minutes by the same figure and you get about two dozen such questions in five minutes, shared with every other integration on the tenant, the nightly backup included.
Month end makes it worse twice over. People edit orders fastest at the close, so the other integrations are busy too, and people ask the agent more questions at the very time the budget has least slack. A stuck retry loop then spends the headroom everyone else was counting on, which is how one assistant’s bad afternoon turns into a ticket from finance about a broken form.
Does the list view threshold apply to Graph filters?
SharePoint blocks a query that has to process more than 5,000 items of a list at once, whether it comes from a browser view or from Graph, and Microsoft’s support article “List View Threshold for large lists and libraries” says the limit cannot be changed in SharePoint Online. The Graph documentation for listing items adds that filtering works best on indexed columns and that the service can filter on only one indexed field at a time.
An agent cannot tell which of its filters are safe. A filter on Status works if Status is indexed and the matching orders stay under the threshold. Add Amount to the same filter and the query now depends on how many orders are open this month, which the model has no way to see. The tool that worked in a demo against 800 items fails in production against 38,000, and fails again at the close when more orders are open. What the model gets back is an error message with no explanation of the cause. The usual reaction is to page the whole list and filter in its own context, which is the expensive path from the section above.
Someone can add indexes, and for the list’s own views they should. Indexes do not change the agent’s position, though. The agent still guesses at filters it cannot test in advance, against a list whose size and shape change under it.
SharePoint lookup column limit in agent queries
Lookups are the other trap. Microsoft’s support article on large lists sets a list view lookup threshold of 12 join operations per query, counting lookup, person and workflow status columns, and the Power Query SharePoint connector documents the same cap. A tool that asks for every field on a wide list, which is the easy default when you want the model to see everything, can go over it on a list with many lookup and person columns. The whole request then fails.
Under the cap, lookups still cost round trips. Graph returns a lookup as the target
item’s id, plus its display value if you name the column in $select. Anything more about
the supplier, such as payment terms or the account manager, is another call to the
Suppliers list. A question that joins three lists through Graph becomes a chain of reads
the model has to plan, with every link charged to the same budget.
SharePoint delta token expired with 410 Gone
Some teams try to make agent tools cheaper with Graph delta queries, which return only
what changed since a token was issued and cost 1 resource unit per request with a token.
Delta is the right read path, but an agent is a poor place to keep the token. The
Microsoft Learn reference for list item delta
says the service can answer 410 Gone with a Location header that starts a fresh
enumeration, for example when a client reuses an old token after being disconnected for a
long time. A token kept in conversation memory or an n8n variable meets that response at
an unpredictable moment, usually in front of a user, and the recovery is a full re-read of
the list.
When should an agent query SharePoint live?
Live reads still have a place, and the first is checking before acting. Before an agent acts on one record, it should read that record by id. A single item read costs 1 resource unit, cannot hit the list view threshold, and returns the current state of the order the agent is about to chase. If the agent is going to email a supplier about order PO-21877, it should confirm in SharePoint that the order is still open.
Writes a person has approved also go live. When a buyer tells the agent to cancel an order and confirms it, that change belongs in SharePoint, made by a tool that writes one item and reports what it did. SPSync is read-only against SharePoint and has no write-back, so that path is yours to build with a Power Automate flow or a single Graph call, and it should stay narrow.
Freshness is the last case. On a five-minute schedule the replica can be up to five minutes behind, so “did the goods received note I saved a minute ago go through?” is a live read of one item. Everything else an agent does across a list, the counts, totals, joins and trends, belongs on a copy. CData Connect AI takes the opposite view and offers SharePoint to agents as a live connection, which works well when every question is about a single record.
Replicating SharePoint for AI agents with SPSync
SPSync reads the lists into a database and keeps them current with Graph delta queries. The agent queries Postgres or SQL Server, and the only process that talks to SharePoint is the sync. What follows is how that sync treats the budgets above, as set out in our Graph throttling policy.
Every request goes through one client and one app registration. The client sends a
decorated User-Agent in the ISV|Company|App/Version form Microsoft asks for, because
SharePoint prioritises decorated traffic over undecorated traffic. A token bucket per
tenant limits how many requests are in flight at once, four by default and configurable,
and every list in a run shares it, so adding lists does not add concurrency. On a 429 or
503 the client waits for the Retry-After value. If a response carries no
Retry-After, it backs off exponentially with jitter, starting at two seconds and capped
at five minutes.
The client keeps its own meter as well. Each response reports the resource units it cost,
and SPSync adds them up per tenant over a rolling minute and day, against the limit
Microsoft publishes for the smallest tenants unless it knows the tenant is larger. It slows
down at 80 percent of either. Three 429s in a row open a circuit breaker, which pauses all
requests to that tenant for the longest Retry-After it has seen in the streak, plus
jitter.
Reads are cheap by construction. After the first full load each round is a delta request
with a token, the delta link is stored exactly as Graph returned it, and when SharePoint
answers 410 the client follows the Location header. SPSync sends no $filter on list
items, so no filter of its own can trip the list view threshold, and it asks for person columns by
id alone, which keeps them out of the 12-join count.
On the database side, each list is a table and the Supplier lookup becomes a
supplier_id column you join on. The buyer’s month-end question is then one query:
SELECT po.title AS order_ref, s.title AS supplier, po.amount, po.sp_synced_atFROM procurement.proc_purchase_orders poJOIN procurement.proc_suppliers s ON s.sp_item_id = po.supplier_idWHERE po.sp_deleted_at IS NULL AND po.status = 'Awaiting goods' AND po.depot = 'Leeds' AND po.amount > 5000ORDER BY po.amount DESC;Postgres does the filtering, so there is no threshold and no join cap, and the agent can
ask as often as buyers do without spending a resource unit. Give it a database MCP server
for Postgres or SQL Server, signed in with a role that can SELECT from a few views and
nothing else. Each row carries sp_synced_at, and a line in the agent’s instructions
telling it to quote that time stops anyone mistaking a five-minute-old answer for a live
one. An MCP server for SharePoint data that queries SQL
walks through the role and the server setup step by step.
Live or copy: a checklist for each agent tool
Go through the agent’s tools one at a time. Most of them sort themselves once you ask what each one reads and who it runs as.
- Does it read one item by id? Live is fine, at 1 resource unit a call.
- Does it filter, count, total or join across a list? Point it at the copy.
- Could the list pass 5,000 items in the next year? Use the copy now, while the list is still small enough to load in minutes.
- Does it ask for many lookup or person columns at once? Use the copy, where a lookup is a join.
- Does it write to SharePoint? Keep it live, one item per call, behind a person’s approval.
- Does the answer need to be newer than the sync interval? Read that one item live and say so in the reply.
- Does it run as a shared service account or as the signed-in user? Moving it to the copy takes it out of that account’s 3,000 requests per 5 minutes.
A tool that ends up on the live side should still honour Retry-After and run under its
own account, so a busy agent never spends the allowance of the account behind a form. The
AI agents and tools over SharePoint
use case covers the replica setup for agents,
an agent data layer for SharePoint
covers one SQL copy shared by several agents, and
Read SharePoint once and let the AI pipeline query the copy
does the resource-unit arithmetic for retrieval jobs that ingest whole lists.