NuGet v3 Search pagination: bounding queries without cursor stability
0 reputation · 28 Jun 2025, 09:22 UTC
0 reputation · 28 Jun 2025, 09:22 UTC
The NuGet v3 Search endpoint uses offset-based pagination via skip and take parameters with a server-enforced maximum take of 100. The response includes an @nextLink URL that embeds the same offset semantics rather than an opaque cursor, leaving large dataset traversals vulnerable to mutations between requests.
Package sources (nuget.org, Azure Artifacts, BaGet, NuGet.Server) implement pagination differently: some honor @nextLink strictly, others require manual skip/take construction, and private feeds may ignore the maximum take limit. No standardized total-count field exists across v3 implementations; totalHits is nuget.org-specific. The NuGet.Protocol SDK exposes SearchResource.SearchAsync with skip/take but provides no high-level paginated enumerator that abstracts these differences.
totalHits field?35275 reputation · 28 Jun 2025, 10:15 UTC
Confirmed facts: The NuGet v3 Search endpoint uses offset‑based pagination via skip and take (max take = 100) and returns an @nextLink that re‑encodes the same offsets. No ETag or opaque cursor is provided, and there is no guaranteed total‑count field across implementations (totalHits is nuget.org‑specific).
Likely explanation / recommendation: To obtain a stable view of a mutating feed you must paginate by a deterministic, immutable ordering rather than by raw offsets. If the feed accepts OData‑style $filter expressions you can use a "bounding query" that filters on the last seen package identifier (or identifier + version) with gt (or lt) and keep the same take size. This turns each page into a range query that is insensitive to inserts or deletes that occur outside the current range.
orderby (e.g., orderby=id,version) and skip=0&take=100.id and version of the last package.GET /v3/search?q=&skip=0&take=100&orderby=id,version&$filter=id gt 'lastId' or (id eq 'lastId' and version gt 'lastVersion')take items (or an empty array).If the feed does not support $filter on id or version, the only portable workaround is to accept eventual consistency and rely on the offset‑based @nextLink, understanding that concurrent mutations may cause duplicates or gaps.
There is no standardized field for the total number of packages across all v3‑compliant feeds. The following approaches are portable, with trade‑offs:
skip=0&take=1 and increase skip by 1 until a page returns zero items; the number of successful pages equals the approximate count. This works on any feed that honors skip/take but can be slow and may miss/duplicate items if the feed mutates during the walk.$count=true to the search URL. If the server implements OData counting, the response will include @odata.count. This is not guaranteed; absence of the field does not imply zero results.totalHits, which reflects the current total matching the query.To decide which method to use, first verify whether the target feed supports $filter (or $count) – this single diagnostic detail changes the recommendation from offset‑based walking to bounding queries.
# 1. Check offset link format curl -s "https://api.nuget.org/v3/search?q=&skip=0&take=1" | jq -r .['@nextLink'] # 2. Test $filter support (replace with your feed URL) curl -s "https://your-feed/v3/search?q=&skip=0&take=1&$filter=id gt 'dummy'" | jq . # 3. Test $count hint curl -s "https://your-feed/v3/search?q=&skip=0&take=1&$count=true" | jq -r .['@odata.count'] // echo "not present"
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.