What changed on this domain's DMARC configuration, and when

Search Documentation

Search across all developer documentation

domain-auth

What changed on this domain's DMARC configuration, and when

GET /v1/domain-auth/domains/{domain}/timeline
Auth required api.domain_auth.view

The notable moments in one domain's DMARC life, newest first: policy changes, record edits that did NOT move the policy, source decisions, acknowledgements, and the customer's own notes.

The unit here is an EVENT, not a stored annotation, and the difference matters. One real change usually produces two stored rows. A live-DNS poll sees the new record and records it with the exact text either side and a tight timestamp; a day or two later the receivers' aggregate reports arrive describing the same day, and the report-derived detector reconstructs the same transition with the policy alone and a date bucketed to midnight UTC. Both rows are correct and both are kept.

This endpoint folds them, so one change is one event. Nothing is hidden by the folding: every event lists its sightings, naming the annotation id, the origin and the timestamp behind each. Read anchor_origin to know which sighting supplied occurred_at and the record text — a dns anchor means a poll timestamp, a reports anchor means a day bucket, and the second is the only kind available for a change made before the domain was onboarded.

Two annotations fold into one event when they share a kind, share the same old_policynew_policy pair, come from different origins, and fall within three days of each other. Notes, acknowledgements and source decisions never fold: each is a separate act by a separate person.

This surface carries no email addresses. The dashboard's timeline names the colleague who published a change or wrote a note; this one does not, because api.domain_auth.view promises no personal data and a read-only DMARC token should not double as a way to enumerate an org's staff. Read anchor_origin to tell a moment a person created (user) from one a detector observed.

Path parameters

domain string required

The domain, by NAME (acme.com). A UUID is also accepted. Matched case-insensitively, and scoped to the calling org — a domain belonging to another org is a 404, never a 403.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 The domain's timeline, newest event first application/json
events array<object> required

Each entry in events:

id string<uuid> required

The ANCHOR sighting's annotation id — a row that really exists, not a synthetic identifier.

kind string enum required

policy_change moved the published p= or sp=. setup_change is a record edit that did NOT move the policy — a rewritten rua tag, a new reporting address — and it is a separate kind rather than a variant because it is how reporting silently stops, which is the commonest way a healthy domain becomes an unmonitored one.

One of: policy_change, setup_change, source_decisions, acknowledgement, note

anchor_origin string enum required

Which sighting supplied occurred_at and the record text. The dns sighting wins whenever there is one: its timestamp comes from a poll that either saw the record or did not, where the report-derived one is a day bucket assembled from mail receivers had already judged.

One of: dns, reports, user, system

occurred_at string<date-time> required
observed_at string<date-time> required

The EARLIEST observation across the sightings — when we first knew.

old_policy string enum required

The p= before the change. "" means no policy was published — a real prior state, not a missing value, and the state a first publish moves out of.

One of: ``, none, quarantine, reject

new_policy string enum required

One of: ``, none, quarantine, reject

old_record string required

The _dmarc TXT before the change, verbatim as observed. Empty when no sighting carried record text — the ordinary state of a change only the reports saw.

new_record string required

The _dmarc TXT after the change, verbatim as observed.

tightening boolean required

Whether this was a move UP the ladder. Always false for a setup_change however much of the record it altered: a rua rewrite is not a rung, and treating one as a rung would put a domain into a fourteen-day watch window for an edit receivers do not act on. Derived here rather than left to the caller, because re-deriving it from the two policy fields means reproducing the rule that an absent record ranks BELOW none — and a caller who misses that gets both the first publish and the total loss of a record backwards.

weakening boolean required

A move DOWN the ladder, including a record that disappeared entirely — which is the worst version of it and the one nothing else in the product can see.

note string optional

The customer's own text, for a note event.

sightings array<object> required

The annotations behind this event, oldest first. Always at least one.

Each entry in sightings:

annotation_id string<uuid> required

The stored row.

origin string enum required

Where the fact came from. dns is a live lookup of the published record: exact text, a tight timestamp, and it describes only the moment of the poll. reports is reconstructed from the policy receivers said they applied: it lags 24-48 hours, is coarse to the day, and is the ONLY origin that can describe a change made before the domain was onboarded. user is a person. system is us.

One of: dns, reports, user, system

occurred_at string<date-time> required

When the thing happened, read from this sighting's evidence.

observed_at string<date-time> required

When SendOps noticed. Differs from occurred_at by 24-48 hours whenever the evidence is a DMARC report, and reading the second as the first would place a policy change after mail that was already judged under it.

has_record_text boolean required

Whether this sighting carried the _dmarc TXT either side. Always false for a report-derived sighting: receivers report the policy they applied, never the record that set it.

annotations integer required

How many stored rows the events were folded from. Reported beside the event count rather than left implicit, so a caller can see the folding happened: 12 annotations, 7 events is the difference between a domain both detectors watch and one only the reports reach.

401 Missing, malformed, or unknown API key application/problem+json
403 Key lacks the required scope or plan limit violated application/problem+json
404 Resource not found application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json