Skip to content
Reference · REST API v1

Domains API

Programmatically manage the domains in your organization: add them, verify ownership, check what their live DNS actually says, and rotate report mailbox tokens.

01 — Before you start

Conventions on this page

Base URLhttps://api.ddmarc.com — every path below already includes the /api/v1 prefix.
Ids are integers{domain_id} is an integer. The API never emits prefixed string ids.
Lists are bare arraysList endpoints return a JSON array. There is no data or meta wrapper.
Mutations need write scopeA key with write scope or higher. See Authentication.
02 — The surface

Endpoints

GET/api/v1/domainsList all domains
POST/api/v1/domainsAdd a new domain
GET/api/v1/domains/{domain_id}Get domain details
DELETE/api/v1/domains/{domain_id}Remove a domain
GET/api/v1/domains/{domain_id}/dns-checkCheck the domain's live DNS records
POST/api/v1/domains/{domain_id}/verifyVerify domain ownership
POST/api/v1/domains/{domain_id}/rotate-tokenRotate the domain's RUA/RUF tokens
GET/api/v1/domains/{domain_id}/recommendationsGet configuration recommendations
There is no update endpoint for a domain. To change the reporting mailbox, rotate the token; to change anything else, delete and re-add.
03 — GET /api/v1/domains

List all domains

Returns every domain in your organization, newest first, each with its ingestion counters.

Request
curl -X GET "https://api.ddmarc.com/api/v1/domains?limit=100&offset=0" \
  -H "X-API-Key: $DDMARC_API_KEY"
ParameterTypeDefaultDescription
limitinteger100Maximum rows to return. Hard ceiling of 500.
offsetinteger0Rows to skip. Pagination is offset-based, not page-based.
200 OK
[
  {
    "id": 42,
    "domain": "example.com",
    "rua_token": "a1b2c3d4e5",
    "ruf_token": "f6g7h8i9j0",
    "verification_token": "ddmarc-verify-9f3c1a",
    "is_verified": true,
    "verified_at": "2026-01-15T11:00:00Z",
    "dmarc_verified": true,
    "spf_verified": true,
    "dkim_verified": false,
    "admin_email": null,
    "created_at": "2026-01-15T10:30:00Z",
    "updated_at": "2026-02-01T08:12:44Z",
    "reports_count": 1250,
    "last_report_at": "2026-02-01T06:00:00Z"
  }
]
04 — POST /api/v1/domains

Add a domain

Adds a domain for monitoring. How many domains you may hold depends on your plan; exceeding the limit returns 403.

Request
curl -X POST "https://api.ddmarc.com/api/v1/domains" \
  -H "X-API-Key: $DDMARC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com"
  }'
FieldTypeRequiredDescription
domainstringRequiredDomain name. Trimmed, lowercased and format-validated; an invalid value returns 422.
admin_emailstringOptionalContact address, used by MSPs so the end customer can sign in to the domain.
201 Created
{
  "id": 43,
  "domain": "example.com",
  "rua_token": "k1l2m3n4o5",
  "ruf_token": "p6q7r8s9t0",
  "verification_token": "ddmarc-verify-71ae4c",
  "is_verified": false,
  "verified_at": null,
  "dmarc_verified": null,
  "spf_verified": null,
  "dkim_verified": null,
  "admin_email": null,
  "created_at": "2026-07-19T15:00:00Z",
  "updated_at": "2026-07-19T15:00:00Z"
}
The rua_token is what routes your reports
Publish rua=mailto:{rua_token}@rua.ddmarc.com in your DMARC record. There is no shared reporting address, and a generic one will not route.
05 — GET /api/v1/domains/{domain_id}

Get domain details

Returns a single domain in the same shape as the list endpoint, including reports_count and last_report_at. A domain belonging to another organization returns 404, not 403.

Request
curl -X GET "https://api.ddmarc.com/api/v1/domains/42" \
  -H "X-API-Key: $DDMARC_API_KEY"
06 — POST /api/v1/domains/{domain_id}/verify

Verify domain ownership

Looks up the domain's TXT records and checks whether any contains the verification_token. This is a live DNS lookup, so it reflects what is published right now.

Token found · 200 OK
{
  "verified": true,
  "message": "Domain ownership verified successfully",
  "txt_records_found": [
    "ddmarc-verify-9f3c1a",
    "v=spf1 include:_spf.google.com ~all"
  ]
}
A failed verification is still 200
Branch on the verified boolean, not on the status code. A missing token returns 200 with verified: false and a message naming the value to publish.
07 — GET /api/v1/domains/{domain_id}/dns-check

Check live DNS records

Resolves and validates the domain's email-authentication records as published today. This returns check results, not a list of records for you to create. Results are cached; pass refresh=true to force a fresh lookup.

200 OK
{
  "domain": "example.com",
  "spf": {
    "exists": true,
    "valid": true,
    "record": "v=spf1 include:_spf.google.com ~all",
    "errors": [],
    "warnings": []
  },
  "dkim": {
    "exists": true,
    "valid": true,
    "record": "v=DKIM1; k=rsa; p=MIIBIjANBgkqh...",
    "errors": [],
    "warnings": [],
    "dkim_alignment_note": null
  },
  "dmarc": {
    "exists": true,
    "valid": true,
    "record": "v=DMARC1; p=none; rua=mailto:a1b2c3d4e5@rua.ddmarc.com",
    "errors": [],
    "warnings": ["Policy is p=none; no enforcement yet"]
  },
  "tls_rpt": null,
  "mta_sts": null,
  "bimi": null,
  "overall_status": "warning"
}

overall_status is one of pass, warning or fail. The tls_rpt, mta_sts and bimi blocks are only populated on plans that include advanced DNS checks, and each carries extra fields — policy mode, max age, logo URL — on top of the common check shape.

08 — POST /api/v1/domains/{domain_id}/rotate-token

Rotate report mailbox tokens

Issues fresh RUA and/or RUF mailbox tokens. Use ?type=rua, ruf or both (default). Limited to five rotations per hour.

200 OK
{
  "domain_id": 42,
  "domain": "example.com",
  "rua_token": "u1v2w3x4y5",
  "ruf_token": "z6a7b8c9d0",
  "rotated": ["rua", "ruf"],
  "dmarc_txt_record": "v=DMARC1; p=none; rua=mailto:u1v2w3x4y5@rua.ddmarc.com; ruf=mailto:z6a7b8c9d0@ruf.ddmarc.com; fo=1"
}
There is no grace period
The old mailbox stops accepting reports the moment you rotate, so publish the returned dmarc_txt_record before the next reporting window. Reports already in flight to the old address are dropped.
09 — DELETE /api/v1/domains/{domain_id}

Delete a domain

Removes a domain from your organization. Returns 204 No Content with an empty body on success.

This is irreversible
The domain's stored report history goes with it. Export anything you still need first via GET /api/v1/reports/export/csv.
10 — Schema

The domain object

FieldTypeDescription
idintegerDomain identifier, used as {domain_id} in paths
domainstringThe domain name, lowercased on write
rua_tokenstringMailbox token for aggregate reports
ruf_tokenstringMailbox token for forensic reports
verification_tokenstringValue to publish in a TXT record to prove ownership
is_verifiedbooleanWhether ownership has been verified
verified_atstring | nullISO 8601 timestamp of verification
dmarc_verifiedboolean | nullLast DNS sweep saw a valid DMARC record
spf_verifiedboolean | nullLast DNS sweep saw a valid SPF record
dkim_verifiedboolean | nullLast DNS sweep saw a valid DKIM record
admin_emailstring | nullOptional contact used for MSP customer login
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-modified timestamp
reports_countintegerList endpoints only: total reports ingested
last_report_atstring | nullList endpoints only: most recent ingestion
The three *_verified booleans are not live
They are the result of the last DNS sweep. Call /dns-check when you need the current state. There is no status, dmarc_policy or pass_rate field on a domain — pass rates come from /api/v1/reports/stats/summary.