Documentation

Learn how to integrate and use DDMARC.

REST API

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.

Conventions on this page

  • • Base URL is https://api.ddmarc.com; every path below already includes the /api/v1 prefix.
  • {domain_id} is an integer. The API never emits prefixed string ids.
  • • List endpoints return a bare JSON array. There is no data or meta wrapper.
  • • Mutating calls need a key with write scope or higher. See Authentication.

Endpoints

MethodEndpointDescription
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.

GET

List All Domains

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

GET /api/v1/domains
curl -X GET "https://api.ddmarc.com/api/v1/domains?limit=100&offset=0" \
  -H "X-API-Key: $DDMARC_API_KEY"

Query Parameters

ParameterTypeDefaultDescription
limitinteger100Maximum rows to return. Hard ceiling of 500.
offsetinteger0Rows to skip. Pagination is offset-based, not page-based.
Response200 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"
  }
]
POST

Add New Domain

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

POST /api/v1/domains
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"
  }'

Request Body

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.
Response201 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 makes reports reach this specific domain. 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.

GET

Get Domain Details

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

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

Verify Domain Ownership

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

POST /api/v1/domains/{domain_id}/verify
curl -X POST "https://api.ddmarc.com/api/v1/domains/42/verify" \
  -H "X-API-Key: $DDMARC_API_KEY"
Token found200 OK
{
  "verified": true,
  "message": "Domain ownership verified successfully",
  "txt_records_found": [
    "ddmarc-verify-9f3c1a",
    "v=spf1 include:_spf.google.com ~all"
  ]
}
Token missing200 OK
{
  "verified": false,
  "message": "Verification token not found in DNS TXT
    records. Add a TXT record with value:
    ddmarc-verify-9f3c1a",
  "txt_records_found": [
    "v=spf1 include:_spf.google.com ~all"
  ]
}

A failed verification is still 200 — branch on the verified boolean, not on the status code.

GET

Check Live DNS Records

Resolves and validates the domain's email-authentication records as they are 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.

GET /api/v1/domains/{domain_id}/dns-check
curl -X GET "https://api.ddmarc.com/api/v1/domains/42/dns-check?refresh=true" \
  -H "X-API-Key: $DDMARC_API_KEY"
Response200 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.

POST

Rotate Report Mailbox Tokens

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

POST /api/v1/domains/{domain_id}/rotate-token
curl -X POST "https://api.ddmarc.com/api/v1/domains/42/rotate-token?type=both" \
  -H "X-API-Key: $DDMARC_API_KEY"
Response200 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.

DELETE

Delete Domain

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

DELETE /api/v1/domains/{domain_id}
curl -X DELETE "https://api.ddmarc.com/api/v1/domains/42" \
  -H "X-API-Key: $DDMARC_API_KEY"

Warning:this is irreversible, and the domain's stored report history goes with it. Export anything you still need first via GET /api/v1/reports/export/csv.

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 the result of the last DNS sweep, not a live lookup. Call /dns-check when you need the current state. There is no status, dmarc_policy or pass_ratefield on a domain — pass rates come from /api/v1/reports/stats/summary.

Continue to