Hi Jinish,
Thanks for your detailed explanation. Here's a structured response to help you handle the _Directory_ExpiredPageToken
error when using Microsoft Graph's /v1.0/users
endpoint:
Problem:
You’re paginating through https://graph.microsoft.com/v1.0/users
using @odata.nextLink
, but due to processing delays, the page token (skip token) in the nextLink
sometimes expires, resulting in this error:
error: _Directory_ExpiredPageToken
Cause:
Skip tokens in Microsoft Graph are time-sensitive and can expire within minutes. If there’s too much delay between requests, the token becomes invalid. Restarting from the beginning is inefficient for large datasets.
Solutions & Workarounds:
- Process Data Incrementally
If your use case allows it, filter by a timestamp field to fetch only changed or new users.
Example:
GET /v1.0/users?$filter=createdDateTime ge 2025-07-01T00:00:00Z
Limitation:
createdDateTime
is not always filterable or orderable, especially on/users
. Consider using delta queries instead.
- Use Delta Query (Recommended for Large/Changed User Sets)
Delta queries allow tracking changes to the directory without relying on skip tokens.
Step 1: Initial request:
GET https://graph.microsoft.com/v1.0/users/delta?$select=id,displayName,mail
Step 2: Page through responses using @odata.nextLink
.
Step 3: Once complete, save the @odata.deltaLink
. Use this link later to fetch only changes since last query (instead of fetching all users).
Advantage: Avoids skip token expiration and improves efficiency for large datasets.
Docs: Use delta query to track changes in Microsoft Graph
- Parallelize Calls (If Applicable)
Split your query (e.g., by UPN prefix, domain, or group) to fetch multiple smaller result sets in parallel rather than sequentially, reducing the chance of expiration.
- Minimize Delay Between Requests
If delta queries aren't feasible:
Reduce enrichment logic or move it outside the pagination loop.
Process data after all pages have been fetched, not during.
- Graceful Error Handling and Retry Logic
When _Directory_ExpiredPageToken
is encountered:
Retry with an earlier page if cached.
Optionally use logical bookmarks (e.g., userPrincipalName
sorting) if supported, and store the last item processed.
Conclusion:
The most robust solution is to use delta queries, which are built for exactly this type of scenario—long-running, incremental syncs without skip token expiration.
Let me know if you'd like an example of a delta query implementation.
Best regards,
Martin