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/v1prefix. - •
{domain_id}is an integer. The API never emits prefixed string ids. - • List endpoints return a bare JSON array. There is no
dataormetawrapper. - • Mutating calls need a key with
writescope or higher. See Authentication.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/domains | List all domains |
| POST | /api/v1/domains | Add 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-check | Check the domain's live DNS records |
| POST | /api/v1/domains/{domain_id}/verify | Verify domain ownership |
| POST | /api/v1/domains/{domain_id}/rotate-token | Rotate the domain's RUA/RUF tokens |
| GET | /api/v1/domains/{domain_id}/recommendations | Get 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.
List All Domains
Returns every domain in your organization, newest first, each with its ingestion counters.
GET /api/v1/domainscurl -X GET "https://api.ddmarc.com/api/v1/domains?limit=100&offset=0" \
-H "X-API-Key: $DDMARC_API_KEY"Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer | 100 | Maximum rows to return. Hard ceiling of 500. |
| offset | integer | 0 | Rows to skip. Pagination is offset-based, not page-based. |
[
{
"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"
}
]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/domainscurl -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
| Field | Type | Required | Description |
|---|---|---|---|
| domain | string | Required | Domain name. Trimmed, lowercased and format-validated; an invalid value returns 422. |
| admin_email | string | Optional | Contact address, used by MSPs so the end customer can sign in to the domain. |
{
"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 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"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}/verifycurl -X POST "https://api.ddmarc.com/api/v1/domains/42/verify" \
-H "X-API-Key: $DDMARC_API_KEY"{
"verified": true,
"message": "Domain ownership verified successfully",
"txt_records_found": [
"ddmarc-verify-9f3c1a",
"v=spf1 include:_spf.google.com ~all"
]
}{
"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.
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-checkcurl -X GET "https://api.ddmarc.com/api/v1/domains/42/dns-check?refresh=true" \
-H "X-API-Key: $DDMARC_API_KEY"{
"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.
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-tokencurl -X POST "https://api.ddmarc.com/api/v1/domains/42/rotate-token?type=both" \
-H "X-API-Key: $DDMARC_API_KEY"{
"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 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
| Field | Type | Description |
|---|---|---|
| id | integer | Domain identifier, used as {domain_id} in paths |
| domain | string | The domain name, lowercased on write |
| rua_token | string | Mailbox token for aggregate reports |
| ruf_token | string | Mailbox token for forensic reports |
| verification_token | string | Value to publish in a TXT record to prove ownership |
| is_verified | boolean | Whether ownership has been verified |
| verified_at | string | null | ISO 8601 timestamp of verification |
| dmarc_verified | boolean | null | Last DNS sweep saw a valid DMARC record |
| spf_verified | boolean | null | Last DNS sweep saw a valid SPF record |
| dkim_verified | boolean | null | Last DNS sweep saw a valid DKIM record |
| admin_email | string | null | Optional contact used for MSP customer login |
| created_at | string | ISO 8601 creation timestamp |
| updated_at | string | ISO 8601 last-modified timestamp |
| reports_count | integer | List endpoints only: total reports ingested |
| last_report_at | string | null | List 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.