How to handle _Directory_ExpiredPageToken error in /v1.0/users

Jinish Trivedi 0 Reputation points
2025-07-03T10:44:19.5166667+00:00

We're trying to fetch data from

https://graph.microsoft.com/v1.0/users

With following params

  1. $expand
  2. $select
  3. $filter
  4. $top

Note: $skip is not supported here.

Since it has some dependent api calls to enrich some other data it takes time to fetch the next page with @odata.nextLink

due to that skip token in nextLink sometimes expires before we have have chance to call the next page.

if this happens we have to start fetching data from all over again which is very inefficient, I have tried leveraging time based filter params like createdDateTime but api wasn't allowing $orderby to support it.

do we have any other solution apart from fetching everything from the start

Microsoft Security | Microsoft Entra | Microsoft Entra ID
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Martin Egli 435 Reputation points
    2025-07-03T11:46:11.31+00:00

    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:

    1. 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.

    1. 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

    1. 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.

    1. 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.

    1. 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


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.