Write a note on this domain's timeline

Search Documentation

Search across all developer documentation

domain-auth

Write a note on this domain's timeline

POST /v1/domain-auth/domains/{domain}/timeline/notes
Auth required api.domain_auth.manage

Records the customer's own words against their own timeline — why a policy was published, what a source turned out to be, what to do if the next fortnight goes badly.

The narrowest write on this surface. A note carries text and, at most, a date. It cannot claim a record changed, cannot set a policy, and is always stored with origin user: a customer's note appearing on the timeline as though a detector had observed it would make every other row on the page less trustworthy.

Requires a user-delegated token. A note records the person who wrote it and the timeline shows their name. A client-credentials token identifies nobody, so this refuses with 422 rather than attributing a sentence to the organization and leaving the next reader with nobody to ask. Same rule as recording a source decision.

Returns the whole timeline, folded, so the caller renders the new note in the company of the events it sits between rather than appending it to a list that has not been through the same grouping.

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.

Request body

Content type: application/json

note string required

The text. Required and trimmed.

Constraints: length 0–2000

occurred_at string<date-time> optional

Optionally back-date the note. Annotating a change you made last week is a legitimate use of a timeline, and the alternative is a note sitting three days away from the event it describes. Omitted, the note is dated to when it was written — which for a person IS the evidence.

Responses

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

201 The domain's timeline, including the new note 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
422 Query parameter or path value failed validation 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