A Graph meeting export can return an empty page that still carries a next link
During a planned Microsoft Graph service update, paginated calls to getAllRecordings or getAllTranscripts can return HTTP 200 with an empty collection alongside a next link, then restart and re-serve items. An export loop that exits on no items stops there and reports success over a short archive.
A paginated call to getAllRecordings or getAllTranscripts can return HTTP 200 OK with an empty collection while still carrying an @odata.nextLink, after which pagination restarts and serves items it already served. Microsoft expects the service update behind this to complete by 31 August 2026. An expectation is not a commitment, and this note takes the narrower reading.
Last verified: 2026-08-20.
What the vendor says
From the Microsoft Graph known issues page, Teamwork and communications, read 2026-08-20:
A request can return a
200 OKresponse with an empty collection and an@odata.nextLink. Pagination then restarts and can return recording or transcript items that were returned previously.
The same entry calls it "a planned service update that is expected to be completed by August 31, 2026", and says such requests "may experience an automatic pagination token reset". The workaround names both halves: "Continue following @odata.nextLink even when the collection is empty. De-duplicate subsequent items by tracking the id property of each recording or transcript."
Why the empty page is easy to misread
The entry is headed around duplicate items, and duplicates are the half an administrator remembers: the quick reading is that an export over-collects for a few weeks and is cleaned up later. The misreading is not that duplicates are harmless, it is that they look like the only failure mode on offer while the same paragraph describes silent truncation. A loop that exits on an empty page rather than on a missing continuation token terminates at the reset, having seen no error, and records a clean run. Microsoft states the correct exit condition on its own Teams export guidance: "If no skipToken is present, it means that there are no more results to retrieve in the current batch".
sequenceDiagram accTitle: One meeting export loop over the wire accDescr: Four responses. The second is empty yet still carries a continuation link, the third repeats items already seen, and only the fourth omits the link. participant C as Export client participant G as Microsoft Graph C->>G: GET getAllTranscripts G-->>C: 200, three items, nextLink present C->>G: GET the nextLink verbatim G-->>C: 200, value is empty, nextLink still present Note left of C: WRONG EXIT. A loop keyed on no items stops here and logs success Note right of G: CORRECT. A loop keyed on no nextLink keeps going C->>G: GET the nextLink verbatim G-->>C: 200, items whose ids were already seen Note over C: over-counts unless de-duplicated on the id property C->>G: GET the nextLink verbatim G-->>C: 200, items, NO nextLink Note over C: ONLY correct exit condition
A run that stops at the empty page finishes early with 200 on every call, writes no error line, and produces an archive short by an unknown amount. Nothing separates "the tenant has no more artifacts" from "the token reset and the loop stopped", and the shortfall surfaces only when a named meeting is asked for. The mirror error inflates instead: following the next link past the empty page without de-duplicating on the id property reports a count that is too high, and once it is in a retention report the fix is a retraction.
Test conditions
Run on 2026-08-20: five Microsoft Learn pages fetched live and read in full. Two prerequisites the export guidance states and this check depends on: an active Microsoft Teams licence must be assigned to the users whose data is exported, and these are protected APIs, so the app registration must meet Microsoft's requirements for accessing without a user. No tenant run has been made; it is the outstanding step. Both functions are application permission only, so this cannot be run by a signed-in administrator. The getAllTranscripts reference prints "Not supported." for delegated accounts.
# Read-only. Every request is a GET. Nothing is created or deleted.
# Do NOT add $top: a separate known issue can then suppress the next link.
GET /v1.0/users/{organizerId}/onlineMeetings/getAllTranscripts(meetingOrganizerUserId='{organizerId}')
Authorization: Bearer {app-only token, OnlineMeetingTranscript.Read.All}
# Then replay the @odata.nextLink value EXACTLY as returned. Append nothing,
# reapply no filter. The token already encodes the parameters.
GET {value of @odata.nextLink from the previous response}
# Stop ONLY when there is no @odata.nextLink. Do not stop on an empty value array:
# that page is what this check exists to observe. Same shape for getAllRecordings.
Results
Record two numbers per run. TOTAL is the count of objects across every value array returned; DISTINCT is the count of unique id values among them. DISTINCT below TOTAL means the reset has already occurred here. Also record whether any page returned 200 with an empty value array while still carrying a next link: that page is the whole finding, because a loop keyed on emptiness would have stopped there and reported success.
Failures
Three checks against the vendor documentation itself failed on 2026-08-20. The guidance on $top contradicts itself: the known issues page says "Do not pass $top query parameter until the issue is fixed", while both endpoint references list it as a supported optional parameter. Plan against the known issues page: Microsoft updated it for this defect, both references link to it by note, and the delta reference repeats the warning.
| Page | What it says about $top | Carries the $top next-link warning |
|---|---|---|
| Known issues in Microsoft Graph | Do not pass it until fixed | Yes |
| onlineMeeting: getAllTranscripts | Supported optional parameter | No |
| onlineMeeting: getAllRecordings | Supported optional parameter | No |
| callTranscript: delta | Not flagged in its table | Yes, own Known issues section |
Second, the known issues page crosses its own delta links: in the change-tracking sentence, getAllTranscripts resolves to the callRecording delta page and getAllRecordings to the callTranscript delta page. Third, six of the eight language tabs on both references issue a Get onlineMeeting by id rather than calling these functions at all, so the check above is raw HTTP and names no cmdlet.
Limitations
The check detects duplicates, not truncation. A loop that already exits on the empty page never fetches the duplicates, so the totals look perfect while the completeness of the archive is unverifiable: Microsoft documents a restart, not a loss, so the shortfall may be every artifact past the reset point or none at all. Truncation needs an independent count of what the tenant should hold, and ordering gives no help: the same guidance states, of the recordings and transcripts responses themselves, that "Results aren't guaranteed to be sorted by createdDateTime."
A clean run proves nothing about the next. The vendor writes "may experience" and names no trigger, frequency, cloud, tenant class or licence tier, so absence in a lab tenant does not clear a customer tenant.
A short result has innocent explanations too. The known issues page states that "The getAllRecordings API does not return recordings for meetings that don't have transcription enabled", so the count is not the tenant's recording population, and neither function returns channel meeting artifacts. An administrator can also switch off Graph access to transcripts, returning 403 with GraphAccessToTranscriptsDisabled: branch on the inner error code first.
Decision: act now
Act now, for two independent reasons. The defect is live today and the vendor date is eleven days out, framed as an expectation rather than a commitment, so keep the check past 1 September until a run shows the behaviour is gone. The stronger reason: the absence of the continuation token is the correct exit condition at all times, so a loop rewritten to test the link rather than the item count is right whichever way the date falls. Read the export code and record which field its loop tests.
An archive whose completeness was never measured is an assertion. Workflow engineering is the path when a Teams export pipeline needs an independent review of what it retrieved.
Sources and further reading
Test the same boundary in your environment.
Use a focused diagnostic to compare the lab result with the controls and constraints in your own environment.
Choose a diagnosticMore from Ops Log
Teams call records left chat retention policies in late April 2026: the replacement policy is PowerShell only
Since late April 2026 a new Teams call data record is covered only by a retention policy for the Teams call logs location, which exists only in PowerShell and reaches nothing created before it went active. The old chat policy stays green. This note gives the read-only check and the boundary of the gap.
An empty Baseline scopes settings page proves nothing about the enforcement rollout
A Conditional Access policy targeting All resources with a resource exclusion now enforces on sign-ins requesting only baseline scopes, and the Baseline scopes settings page renders empty whether or not the rollout reached the tenant. Inventory the policy shape, the only part a read-only call can measure.
Anthropic in Excel and PowerPoint is a separate setting, on by default for EU tenants created after 25 March 2026
Copilot in Excel and PowerPoint has its own Anthropic setting, a different object from the global Anthropic subprocessor control, and it defaults to on for EU, EFTA and UK tenants created after 25 March 2026. Whether anything actually leaves the EU Data Boundary turns on the second control, so read the tenant creation date and then read both.