---
title: "Changelog"
description: "Dated changes to the API contract and the documentation."
publishedAt: 2026-09-16
---
Changes to the v1 API contract and the guides that document it, newest first.

## 2026-10-07

- [`GET /domains/{domain}/health`](/docs/api/v1/domains/#getDomainHealth):
  `dns.mta_sts` carries an optional `hosted_cta` (`label`, `url`) when the
  domain's MTA-STS record is missing or failing, or a hosted setup is still in
  progress. The `url` is a path on the app. The `dns_mta_sts` action item now
  links there too, and a missing record raises a `low` item it did not before.

## 2026-10-02

- [`GET /reports/{id}`](/docs/api/v1/reports/#getReport) no longer adds a
  `parse_warnings` entry for a report whose `version` is `1` or `0.1`. Large
  reporters send both routinely and neither says anything about the report's
  structure. Any other unexpected version is still flagged.

## 2026-09-19

- [`GET /enums`](/docs/api/v1/vocabulary/#listEnums) returns every category
  the product uses, not only the ones the reference documents. The documented
  categories and their shape are unchanged; read the ones your integration
  needs by name.

## 2026-09-16

- Timestamps are RFC 3339 in UTC with whole seconds on every endpoint in the
  reference. A field naming a day rather than a moment is `YYYY-MM-DD`. An
  instant a reporter got wrong, one no RFC 3339 date can hold, renders `null`.
  See the [conventions](/docs/getting-started/#conventions).
- A request that authenticates with an API key is refused with `403` on
  `DELETE /api-keys/{id}`, matching the existing rule for creating keys: a
  full key gets `code: "api_key_cannot_revoke"`, a read key is stopped earlier
  with `permission_denied`. See [authentication](/docs/authentication/#lifetime).
- An unknown path under the base URL returns a JSON `404` instead of an HTML
  page.
- Lists in documented responses are never `null`; an empty list is `[]`.
- [`GET /enums`](/docs/api/v1/vocabulary/#listEnums) returns the categories
  the reference documents.
- `grade` documents its `N/A` value, returned before the first report.
- [`GET /domains`](/docs/api/v1/domains/#listDomains) and
  [`GET /top-sources`](/docs/api/v1/analytics/#listTopSources) now return a
  paged envelope (`total`, `limit`, `offset`) instead of a bare array.
- [`GET /events`](/docs/api/v1/events/#listEvents) echoes back the `limit`
  and `offset` it actually applied.
- A `403` permission denial now carries `code: "permission_denied"` and a
  `required` field naming the permission the key lacked. See
  [reading a rejection](/docs/authentication/#reading-a-rejection).
- [`GET /sources/{ip}`](/docs/api/v1/sources/#getSource) gained
  `spam_signals`, renamed from an earlier field.
- [`GET /enums`](/docs/api/v1/vocabulary/#listEnums) is now documented in the
  reference.
- A malformed `from` or `to` on [`GET /reports`](/docs/api/v1/reports/#listReports)
  now returns `400` instead of being silently dropped.
- The `domain` filter on statistics, compliance score, trends, top sources
  and sources is now case-insensitive, and a malformed value returns `400`
  instead of an empty result.
- [`GET /api-keys/usage`](/docs/api/v1/api-keys/#getAPIKeyUsage) documents
  its `503`.