Poll opt-in changes reliably
Use audit logs to incrementally process opt-in creations, status changes, and deletions.
Use GET /v1/audit-logs to keep opt-in state synchronized. Poll with a small
time overlap and deduplicate events by audit-log id because audit-log
ingestion is asynchronous.
Query the change feed
For an organisation-wide incremental poll, always provide a bounded time range. Use RFC3339 timestamps for both bounds, and restrict the query to the opt-in events:
action[in]=opt_in.created,opt_in.status_changed,opt_in.deletedsubject_type=opt_inoccurred_at[gte]andoccurred_at[lte]include=subjects
For example:
curl --get 'https://api.getmateo.com/v1/audit-logs' \
--header "Authorization: Bearer $MATEO_API_TOKEN" \
--header "hellomateo-version: 2026-07-01" \
--data-urlencode 'action[in]=opt_in.created,opt_in.status_changed,opt_in.deleted' \
--data-urlencode 'subject_type=opt_in' \
--data-urlencode 'occurred_at[gte]=2026-01-01T00:00:00Z' \
--data-urlencode 'occurred_at[lte]=2026-01-01T01:00:00Z' \
--data-urlencode 'include=subjects' \
--data-urlencode 'limit=100'The endpoint orders results newest first, by occurred_at and then by event
id. Follow next_page_url exactly as returned until it is null; do not
construct page URLs or use offsets. An abbreviated page contains the audit
event's primary opt-in subject and, when present, related contact and
opt-in-group subjects:
{
"object": "list",
"data": [
{
"id": "01945f2e-7b7d-7a10-9f30-123456789abc",
"occurred_at": "2026-01-01T00:42:00Z",
"action": "opt_in.status_changed",
"actor": { "type": "employee", "id": "employee-123" },
"changes": [
{ "field": "status", "before": "requested", "after": "accepted" }
],
"subjects": [
{
"subject_type": "opt_in",
"subject_id": "opt-in-123",
"subject_role": "primary",
"external_id": null,
"secondary_external_id": null
},
{
"subject_type": "contact",
"subject_id": "contact-456",
"subject_role": "related",
"external_id": null,
"secondary_external_id": null
},
{
"subject_type": "opt_in_group",
"subject_id": "group-789",
"subject_role": "related",
"external_id": null,
"secondary_external_id": null
}
]
}
],
"next_page_url": "/v1/audit-logs?...",
"previous_page_url": null
}Process changes
For each poll, start a few minutes before the previous poll ended and use the
current time as the upper bound. Follow every next_page_url, deduplicate
results by audit-log id, and process them oldest first. Each subject's
external_id is its primary identifier and secondary_external_id is its
secondary identifier, both captured at the point in time of the audit event.
Both fields are always present and are null when the corresponding
identifier is unavailable.
See List Audit Logs for the complete request and response contract, and Pagination for shared cursor-pagination behavior.