azuremicrosoft-graph-apimicrosoft-graph-sdks

Is it possible to manually construct a skiptoken to paginate search results in Microsoft Graph API?


When I use Microsoft Graph API to search emails via Graph Explorer with the following query:

https://graph.microsoft.com/v1.0/me/messages?$search="keyword"&$top=10

I receive a list of messages with the @odata.nextLink property at the end of the response. This link includes a skiptoken parameter, which allows me to fetch the next set of results by using it directly in Graph Explorer, like this:

"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/messages?$search="keyword"&$top=10&$skiptoken=MSZZVlF4YkZwVVRUUk9SR04zVG1reGExbDZRWGhNVkZKdFRVZEpkRmxVVFRKWmFUQXlXbXBCTUU1RVNtbE9SR2Q0V1dwRmJXTjZNSGhOUVQwOQ%3d%3d"

Problem: I am trying to implement a pagination system that would allow me to perform a search and move directly to any page (e.g., jump from page 1 to page 3) without sequentially fetching each page. However, since $search and $skip cannot be used together in Microsoft Graph API, attempting to combine them results in a searchAndSkip error.

To work around this limitation, I am considering whether it is possible to construct the skiptoken manually to skip directly to a desired page while still using $search.

Questions:

  1. Is it possible to decode or construct this skiptoken manually?
    • If the skiptoken is encoded or encrypted, is there any way to decode it and understand its structure?
  2. Would constructing a skiptoken based on certain parameters allow me to effectively combine $search and pagination functionality, bypassing the searchAndSkip limitation?
    • If so, are there any tools or libraries that can assist with understanding or generating the skiptoken for Graph API pagination? Understanding the structure of skiptoken or any method to customize it would be extremely helpful for implementing a more flexible pagination system with $search, especially given the current limitation where $search and $skip cannot be used together.

Any help or guidance would be appreciated. Thank you!


Solution

  • As an alternative, you can use the Microsoft Search API to search Outlook messages. It's very similar to searching implemented by Outlook clients.

    POST https://graph.microsoft.com/v1.0/search/query
    {
        "requests": [
            {
                "entityTypes": [
                    "message"
                ],
                "query": {
                    "queryString": "keyword"
                },
                "from": 0,
                "size": 10
            }
        ]
    }
    

    To get results from other page, increment from by size.

    POST https://graph.microsoft.com/v1.0/search/query
    {
        "requests": [
            {
                "entityTypes": [
                    "message"
                ],
                "query": {
                    "queryString": "keyword"
                },
                "from": 10,
                "size": 10
            }
        ]
    }
    

    The Microsoft Graph .NET SDK:

    var requestBody = new QueryPostRequestBody
    {
        Requests = new List<SearchRequest>
        {
            new SearchRequest
            {
                EntityTypes = new List<EntityType?>
                {
                    EntityType.Message,
                },
                Query = new SearchQuery
                {
                    QueryString = "keyword",
                },
                From = 0,
                Size = 10
            }
        }
    };
    
    // first page
    var searchResponse = await graphClient.Search.Query.PostAsQueryPostResponseAsync(requestBody);
    foreach (var hit in searchResponse.Value[0].HitsContainers[0].Hits)
    {
        if (hit.Resource is Message message)
        {
            Console.WriteLine($"Message Id: {message.Id}");
        }
    }
    // check if there are more results
    while (searchResponse.Value[0].HitsContainers[0].MoreResultsAvailable.GetValueOrDefault())
    {
        // next page
        requestBody.Requests[0].From += requestBody.Requests[0].Size;
        searchResponse = await graphClient.Search.Query.PostAsQueryPostResponseAsync(requestBody);
        foreach (var hit in searchResponse.Value[0].HitsContainers[0].Hits)
        {
            if (hit.Resource is Message message)
            {
                Console.WriteLine($"Message Id: {message.Id}");
            }
        }
    }
    

    Limitation is that you can't sort messages. Search results are sorted by receivedDateTime in descending order.

    https://learn.microsoft.com/en-us/graph/search-concept-messages