Microsoft Graph exposes every Teams call and meeting as a call record, with participants, start and end times, modalities and quality data. It keeps them for 30 days. If you want call history beyond that, for reporting or for a records request, you have to collect it yourself. This is the Power Automate flow I built to archive call records into a database, and the four traps that each cost me a day.
The shape of the flow
- Read a checkpoint: the start time of the last record ingested.
- Call
GET /communications/callRecords?$filter=startDateTime ge {checkpoint}through a custom connector with application permissions (CallRecords.Read.All). - Page through
@odata.nextLinkin a Do until loop. - Upsert each record into Postgres through a small API, keyed on the call record ID, so re-runs are harmless.
- Write the new checkpoint. If the checkpoint is more than 12 hours behind now, send an alert email and terminate the run as Failed so it shows red in the run history.
Running every hour it keeps up easily. The initial catch-up from an older checkpoint pulled about 1,400 records in one run.
Trap 1: the connector’s schema was wrong about identities
I generated the custom connector’s OpenAPI definition from a sample response. Every identitySet in that sample happened to be a simple user, so the generator typed 72 identity members as strings. Real records contain nested objects there. The connector accepted the response and then the flow failed when it tried to use the fields. Retype them as objects in the definition before you build anything on top.
Trap 2: the first phone participant turned the flow off
The first time someone dialled in from a PSTN number, the phone member arrived as an object where my schema said string. Schema validation failed, the run failed, and after a few consecutive failures Power Automate switched the flow off entirely. Nobody told me. Two lessons: make every identity member an object with optional properties, and alert on the flow’s own state, not only on its runs.
Trap 3: saving the connector silently disabled service principal support
At one point every call returned 401 with “invalid token lifetime”. Nothing in the flow had changed. Opening the custom connector in the portal and saving it, even without edits, had cleared the Enable Service Principal support toggle in the security tab. Re-enabling it fixed the 401 instantly. If you edit a connector that uses client credentials, check that toggle before you close the page.
Trap 4: Do until always runs once
A Do until loop evaluates its condition after the body, so it always executes at least once. My paging loop made one extra request past the checkpoint on every run and quietly overshot it. The fix was an If condition in front of the loop that only enters it when there is a nextLink, and a lessOrEquals comparison on the checkpoint so the boundary record is not re-fetched.
Why bother with the database
Once the records are in Postgres you can answer questions Teams admin centre cannot: who called whom across the last year, average call quality per site, which meetings ran over, which external numbers appear most. And when a subject access request lands, the answer for calls is a query rather than an apology that the data has gone.