Result envelope and async polling
How async report endpoints return results, the _links shape, and how to poll.
The reporting endpoints (POST /api/v1/reports/*) are asynchronous. Submitting one queues a report run and returns immediately with a placeholder. You poll a result URL until the run finishes.
If you’re using a no-code tool (n8n, Zapier, Power Automate) that handles polling for you, you can skip the lifecycle section and jump straight to the field reference.
Every async response uses the same envelope shape regardless of whether the run is still running, done, or in error.
Top-level fields
_linksobjectNavigation links — see below.
status"running" | "done" | "error"Current state of the run.
progressintegerPercentage complete, 0–100. 100 once status is done.
result?object | nullResult payload. Empty data while running. Populated once done. Omitted when status is error.
error?objectPresent only when status is error. See Errors.
_links
self{ href, method }Always present. URL to call for this exact result page. Use it to poll, and to re-fetch a page later.
origin{ href, method, body }Always present. The original request that started this run. Re-issuing it would start a new run.
next?{ href, method }Present when more rows exist after this page.
prev?{ href, method }Present when rows exist before this page.
next and prev follow the pagination cursor convention. They appear only when results are paginated and there’s something to navigate to.
result
dataobject[]Array of result rows. Empty while status is running. Schema depends on the endpoint and requested field groups.
totalinteger | nullTotal rows across all pages, if known. Some reports return null until you have paginated through to the end.
dataCutoffTimestamp?string (ISO 8601 datetime)For reports that include the current period, the timestamp up to which activity is included. Omitted for finalized historical periods — meaning the result will not change if you re-run the same query later. The value typically includes a trailing .0 (e.g. 2026-05-01T08:14:00.0); parse it with any standard ISO 8601 / RFC 3339 parser.
timeZonestringIANA time zone of the mailbox. Reports are grouped by date in this zone, regardless of the time zone in your start and end parameters.
Polling lifecycle
Submit the request
POST to a reports endpoint. The response is the envelope with status: "running" and progress < 100.
Wait, then call _links.self
Use exponential backoff starting at 2 seconds, capped at 30 seconds. Each poll returns the same envelope, with status and progress reflecting the run’s current state.
Stop when status is done
result.data is now populated. If _links.next is present, follow it for the next page.
Result expiry
Result IDs expire 24 hours after creation. Calls to /api/v1/reports/results/{id} after the expiry return 404. To re-fetch the same data later, re-issue the original request using _links.origin — combine its href, method, and body to recreate the call.
See also
- Poll report results — the polling endpoint.
- Pagination — how
_links.nextand_links.prevare constructed. - Rate limits — how concurrent runs are capped.