API Reference

Pull DMARC data into your own tools. Build alerts, dashboards, and automations that fit your workflow.

The DMARCTrust dashboard shows you what is happening with your email authentication. The API lets you act on it. Pull your DMARC data into Slack, your SIEM, a cron job, or a custom dashboard, whatever your team already uses.

What people build with this API

Slack/Teams alerts when a new sender starts using your domain or authentication failures spike
SIEM integration to feed DMARC source data into Splunk, Datadog, or Grafana
Automated domain onboarding for MSPs managing hundreds of client domains
Scheduled CSV exports of daily stats and source data for compliance reporting
DNS health checks in CI/CD pipelines to catch misconfigurations before they go live

Prerequisites

  • An active DMARCTrust subscription (Starter or Pro plan).
  • At least one domain configured in your account.
  • An API key. See Getting started below.

Endpoint quick reference

The full API surface in one table. Each endpoint links to its detailed section.

Method Path Purpose Success
GET /account Account, plan, and 30-day usage 200
GET /domains List domains 200
GET /domains/:id Single domain with 30-day stats 200
POST /domains Add a domain (deactivated) 201
POST /domains/:id/activate Start receiving reports 200
POST /domains/:id/deactivate Stop processing reports 200
POST /domains/:id/refresh_dns Queue an immediate DNS check 202
GET /domains/:id/dns Current DMARC, SPF, BIMI records 200
GET /domains/:id/stats Daily statistics for a date range 200
GET /domains/:id/sources Senders grouped by domain and IP 200
GET /reports List aggregate reports 200
GET /reports/:id Single report with all records 200

Base URL for every endpoint: https://www.dmarctrust.com/api/v1

Getting started

Generate an API key

Go to Settings in your dashboard and scroll to the API keys section. Click Create API key, give it a name that describes its purpose (e.g., "Slack bot" or "Nightly export"), and copy the key. It is only shown once.

dt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Store this key in an environment variable or secrets manager. Do not commit it to source control.

Make your first request

Test your key by fetching your account info:

curl -H "Authorization: Bearer YOUR_API_KEY" \ https://www.dmarctrust.com/api/v1/account

Response:

{ "data": { "id": "usr_123", "email": "[email protected]", "reporting_address": "[email protected]", "subscription": { "plan": "pro", "status": "active", "included_domains": 5, "extra_domains": 1, "total_allowed_domains": 6, "active_domains": 4, "retention_days": 180, "current_period_end": "2026-04-15T00:00:00Z" }, "usage": { "reports_30d": 156, "messages_30d": 12847 } }, "meta": { "request_id": "abc123-def456-..." } }

If this works you are ready to call any endpoint. If you got 401, double-check that you copied the full key and prefixed it with Bearer  in the header. If you got 403, check your subscription status in the dashboard.

Authentication

Every request requires a Bearer token in the Authorization header:

Authorization: Bearer dt_live_your_api_key_here

What can go wrong:

  • Missing or malformed key: 401 invalid_api_key.
  • Revoked or unknown key: 401 invalid_api_key.
  • No active subscription: 403 subscription_required. The Free plan does not include API access.
  • Suspended account: 401 invalid_api_key.

Rate limits

Client type Limit Bucket
Authenticated (with API key) 30 requests/minute Per API key
Unauthenticated 2 requests/minute Per source IP

Every response includes the current state of your bucket:

  • X-RateLimit-Limit — the ceiling for the current bucket.
  • X-RateLimit-Remaining — how many requests you have left in this minute.
  • X-RateLimit-Reset — Unix timestamp when the counter resets.

When you exceed the limit you get 429 rate_limit_exceeded with a retry_after field (seconds to wait):

{ "error": { "code": "rate_limit_exceeded", "message": "Rate limit exceeded. Please wait before making more requests.", "retry_after": 42 } }

If you need a higher limit, contact us with the use case.

Conventions

Response envelope

Successful responses always have the same top-level shape:

{ "data": { ... }, // the resource (object) or list of resources (array) "meta": { // request and pagination metadata "request_id": "abc123-...", "total_count": 42, // list endpoints only "has_more": true, // list endpoints only "next_cursor": "eyJ…" // list endpoints only } }

Status codes

The API uses a small, predictable set of HTTP status codes:

Code Meaning
200 OK Successful read or synchronous update
201 Created A new resource was created (only on POST /domains)
202 Accepted An asynchronous job was queued (only on POST /domains/:id/refresh_dns)
4xx Client error. See Error handling.
5xx Server error. Safe to retry with exponential backoff. The request_id helps us trace it if you contact support.

Dates and times

All *_at timestamps are ISO 8601 in UTC, e.g., 2025-01-17T16:20:01Z. Date-only values (start_date, end_date, the per-day date field) use YYYY-MM-DD. An invalid date returns 400 invalid_date.

One exception: /domains/:id/stats resolves its date defaults and returns its day-bucket dates in the account's effective stats time zone (the user's time_zone on paid plans, UTC on the Free plan). Every stats response advertises that zone as meta.bucket_time_zone. Every other endpoint stays on UTC.

Caching

Every API response sets Cache-Control: private, no-store. Do not cache responses in shared proxies. If you need a cache, build your own with the request_id as the audit trail.

Endpoints

Base URL: https://www.dmarctrust.com/api/v1. Each section below shows the request, the parameters, the success response, and the error responses you should plan for.

Account

GET /account

Returns your account, current subscription, plan limits, and 30-day usage. Use this to gate features in your app, show usage in your own dashboard, or alert when you are close to your domain quota.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/account

Response: 200 OK. See the example in Getting started above.

Field reference

  • subscription.plan: free, starter, or pro.
  • subscription.status: Stripe-style status (active, trialing, past_due, etc.).
  • included_domains: domains included in the plan; extra_domains: paid add-on seats; total_allowed_domains: their sum, the hard ceiling for activations.
  • active_domains: domains currently activated and receiving reports.
  • retention_days: how far back report data is queryable. See Retention windows.
  • usage.reports_30d / usage.messages_30d: aggregate report and message counts in the last 30 days.

Domains

Domain lifecycle

A domain moves through four states. The API exposes one endpoint for each transition:

  1. Added — created via POST /domains. DNS check runs in the background; active: false.
  2. DNS verified — DNS check confirms the DMARC rua tag points to your reporting address. dns_status: "valid", still active: false.
  3. Activated — call POST /domains/:id/activate to count it against your plan and start processing reports. active: true.
  4. Deactivated — call POST /domains/:id/deactivate to stop processing without deleting the domain. Historical data is preserved subject to retention.

List domains

GET /domains

Returns every domain in your account, newest first. Use the filters to narrow the list and the cursor to paginate.

Query parameters (all optional)

Parameter Type Description
status string active or inactive
dns_status string pending, valid, or invalid
limit integer Page size. Default 25, max 100.
cursor string Opaque cursor from a previous response. See Pagination.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ "https://www.dmarctrust.com/api/v1/domains?status=active&limit=50"

Response: 200 OK

{ "data": [ { "id": "ud_19", "domain": "example.com", "active": true, "dns_status": "valid", "dns_status_message": "DNS Verified", "last_report_received_at": "2025-01-17T03:14:09Z", "activated_at": "2025-10-13T08:58:52Z", "created_at": "2025-10-13T08:58:48Z", "reporting_address": "[email protected]" } ], "meta": { "total_count": 4, "has_more": false, "next_cursor": null } }

Get a single domain

GET /domains/:id

Returns full details for one domain, including a 30-day stats summary and the parsed DMARC record. Use this for a domain detail page or to verify configuration after a DNS change.

Path parameter: id — domain ID with or without the ud_ prefix.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/domains/ud_19

Response: 200 OK

{ "data": { "id": "ud_19", "domain": "example.com", "active": true, "dns_status": "valid", "dns_status_message": "DNS Verified", "dns_status_details": { "dmarc_record": "v=DMARC1; p=quarantine; rua=mailto:[email protected]", "rua_configured": true, "last_checked_at": "2025-01-17T16:20:01Z" }, "stats": { "total_reports_30d": 156, "total_messages_30d": 12847, "spf_pass_rate_30d": 98.4, "dkim_pass_rate_30d": 96.1 }, "last_report_received_at": "2025-01-17T03:14:09Z", "activated_at": "2025-10-13T08:58:52Z", "created_at": "2025-10-13T08:58:48Z" }, "meta": { "request_id": "abc123-def456-..." } }

Errors: 404 not_found if the ID is unknown or belongs to another account.

Add a domain

POST /domains

Adds a domain to your account. The domain is created in the deactivated state and a DNS check is queued automatically. Once dns_status is valid, call activate to start processing reports.

Body parameter: domain (required) — the domain name. IDN (internationalized) names are supported. Whitespace is stripped and the value is lowercased before storage.

cURL example

curl -X POST \ -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "newdomain.com"}' \ https://www.dmarctrust.com/api/v1/domains

Response: 201 Created

{ "data": { "id": "ud_42", "domain": "newdomain.com", "active": false, "dns_status": "pending", "dns_status_message": "DNS check in progress", "created_at": "2026-04-25T09:12:33Z", "reporting_address": "[email protected]", "setup_instructions": { "step": "Add the following to your DMARC record's rua tag", "value": "mailto:[email protected]" } }, "meta": { "request_id": "abc123-def456-..." } }

Errors

  • 422 unprocessable_entity with param: "domain":
    • "Domain name is required" — body was empty.
    • "Invalid domain format" — the value did not match the domain regex.
    • "This domain is already in your account" — duplicate.

Note: adding a domain does not consume a plan seat. Only activating it does. This lets you stage many domains and turn them on as DNS gets verified.

Activate a domain

POST /domains/:id/activate

Activate a domain so DMARCTrust starts processing the reports it receives. Activation counts the domain against your plan's total_allowed_domains.

cURL example

curl -X POST \ -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/domains/ud_42/activate

Response: 200 OK

{ "data": { "id": "ud_42", "domain": "newdomain.com", "active": true, "activated_at": "2026-04-25T09:14:01Z" }, "meta": { "request_id": "abc123-def456-..." } }

Calling activate on an already-active domain is idempotent and returns 200 with the existing activated_at.

Errors

  • 422 domain_limit_reached — you have used all your seats. The error body includes the numbers so you can show a useful message:
{ "error": { "code": "domain_limit_reached", "message": "You cannot activate more domains on your current plan. Please upgrade or purchase additional domain seats.", "request_id": "abc123-...", "current_limit": 5, "active_domains": 5 } }
  • 404 not_found — unknown ID.

Deactivate a domain

POST /domains/:id/deactivate

Stops processing new reports for the domain and frees the plan seat. Historical data is kept (subject to retention) so you can re-activate later without losing context.

cURL example

curl -X POST \ -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/domains/ud_42/deactivate

Response: 200 OK

{ "data": { "id": "ud_42", "domain": "newdomain.com", "active": false, "deactivated_at": "2026-04-25T09:30:11Z" }, "meta": { "request_id": "abc123-def456-..." } }

Idempotent on already-inactive domains.

Refresh DNS

POST /domains/:id/refresh_dns

Queues an immediate DNS check for the domain. The check runs in the background (it does not block the response), so this endpoint returns 202 Accepted as a signal that work was scheduled, not that it is finished.

cURL example

curl -X POST \ -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/domains/ud_19/refresh_dns

Response: 202 Accepted

{ "data": { "id": "ud_19", "message": "DNS check initiated. Results will be available shortly." }, "meta": { "request_id": "abc123-def456-..." } }

How to read the result: poll GET /domains/:id/dns or GET /domains/:id and watch the last_checked_at field. A reasonable polling pattern is every 5 to 10 seconds with a 60-second cap. Most checks complete in under 10 seconds.

Domain DNS records

GET /domains/:id/dns

Returns the latest DMARC, SPF, and BIMI records we observed for the domain, along with parsed fields. Use this to power a status page, a CI/CD gate, or your own monitoring.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/domains/ud_19/dns

Response: 200 OK

{ "data": { "domain": "example.com", "last_checked_at": "2025-01-17T16:20:01Z", "dmarc": { "status": "valid", "record": "v=DMARC1; p=quarantine; rua=mailto:[email protected]", "policy": "quarantine", "subdomain_policy": "reject", "pct": 100, "rua": ["[email protected]"], "dmarctrust_configured": true }, "spf": { "status": "valid", "record": "v=spf1 include:_spf.google.com -all", "mechanism_count": 1, "all_mechanism": "-all" }, "bimi": { "status": "valid", "record": "v=BIMI1; l=https://example.com/logo.svg" } }, "meta": { "request_id": "abc123-def456-..." } }

Each block has its own status: "valid" if a record was found, "not_found" otherwise. dmarctrust_configured is true when your reporting address appears in the DMARC rua tag.

Domain statistics

GET /domains/:id/stats

Returns one row per day for the requested date range, plus pre-computed totals across the range. Use this for charts, dashboards, and CSV exports.

Day buckets are computed in the account's effective stats time zone, returned on every response as meta.bucket_time_zone. The query-parameter defaults below resolve in that same zone, not in server UTC.

Query parameters (all optional)

Parameter Default Description
start_date 30 days ago Inclusive start (YYYY-MM-DD). Clamped to the retention cutoff for your plan.
end_date today Inclusive end (YYYY-MM-DD).

An invalid date returns 400 invalid_date. A start_date older than your retention window is silently clamped to the cutoff (no error), so you always get the maximum amount of data your plan allows.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ "https://www.dmarctrust.com/api/v1/domains/ud_19/stats?start_date=2025-01-01&end_date=2025-01-31"

Response: 200 OK

{ "data": { "domain": "example.com", "period": { "start": "2025-01-01", "end": "2025-01-31" }, "daily_stats": [ { "date": "2025-01-01", "total_messages": 412, "spf_pass": 405, "spf_fail": 7, "dkim_pass": 398, "dkim_fail": 14, "disposition_none": 405, "disposition_quarantine": 5, "disposition_reject": 2, "unique_sources": 6 } ], "totals": { "total_messages": 12847, "spf_pass_rate": 98.4, "dkim_pass_rate": 96.1 } }, "meta": { "request_id": "abc123-def456-...", "bucket_time_zone": "America/Los_Angeles" } }

Days with no data are not returned (the array is sparse). If the totals block has zero messages, the rate fields are null, not 0, to distinguish "no data" from "all failures".

Domain sources

GET /domains/:id/sources

Returns the senders that used your domain in a time window, grouped by sender domain, with disposition breakdown (accepted, quarantined, rejected), alignment percentages, and per-IP details. This is the raw material for "who is sending as me" alerts and SIEM dashboards.

Query parameters (all optional)

Parameter Default Description
range 7d One of 48h, 7d, 30d, 90d, 180d. Clamped to the longest range your plan's retention allows.
date Specific day (YYYY-MM-DD), overrides range. Must be within retention.

Free plans cannot pass 180d and have it honored; the response will use the largest range that fits the 7-day retention. An invalid date returns 400 invalid_date.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ "https://www.dmarctrust.com/api/v1/domains/ud_19/sources?range=48h"

Response: 200 OK

{ "data": { "domain": "example.com", "range": "48h", "range_label": "Last 48 hours", "period": { "start": "2026-04-23T09:00:00Z", "end": "2026-04-25T09:00:00Z" }, "summary": { "total_messages": 4218, "unique_ips": 17, "resolved_ips": 15, "unresolved_ips": 2, "spf_aligned_percent": 97.8, "dkim_aligned_percent": 95.3 }, "sources": [ { "sender_domain": "google.com", "provider": "Google Workspace", "status": "resolved", "total_messages": 3104, "accepted": 3098, "quarantined": 4, "rejected": 2, "spf_aligned_percent": 99.8, "dkim_aligned_percent": 98.1, "ips": [ { "ip": "209.85.128.10", "hostname": "mail-yw1-f10.google.com", "messages": 1820 }, { "ip": "209.85.128.11", "hostname": "mail-yw1-f11.google.com", "messages": 1284 } ] } ] }, "meta": { "request_id": "abc123-def456-..." } }

status on a source is "resolved" when we identified the provider, "unresolved" when we have IPs but no provider attribution yet.

Reports

List reports

GET /reports

Returns the DMARC aggregate (RUA) reports we have processed for your domains, newest first. Filter by domain, sender organization, or date range; paginate with cursor.

Query parameters (all optional)

Parameter Description
domain_id Filter by domain ID (e.g., ud_19).
domain Filter by domain name (case-insensitive substring match).
sender_org Filter by sender organization (case-insensitive substring match), e.g., google.com.
start_date Reports received on or after this date (YYYY-MM-DD).
end_date Reports received on or before this date (YYYY-MM-DD).
limit Page size. Default 25, max 100.
cursor Opaque cursor from a previous response.

Date filters are silently ignored if they are unparseable, so check your input format. Results are always restricted to the retention window of your plan; older data is not returned.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ "https://www.dmarctrust.com/api/v1/reports?domain_id=ud_19&limit=50"

Response: 200 OK

{ "data": [ { "id": "rpt_915", "domain": "example.com", "domain_id": "ud_19", "report_id": "20250117030000.example.com.google.com", "sender_org": "google.com", "date_range": { "begin": "2025-01-16T00:00:00Z", "end": "2025-01-17T00:00:00Z" }, "summary": { "total_messages": 412, "passed": 405, "quarantined": 5, "rejected": 2, "spf_pass_rate": 98.3, "dkim_pass_rate": 96.6 }, "created_at": "2025-01-17T03:14:09Z" } ], "meta": { "total_count": 915, "has_more": true, "next_cursor": "eyJpZCI6OTE0LC..." } }

summary can be null for reports that contain no per-record entries (rare, but possible for empty submitter reports).

Get report details

GET /reports/:id

Returns one report with every per-IP record entry. Use this when you need the source IP, hostname, provider attribution, and per-record SPF/DKIM/DMARC results for forensics or SIEM enrichment.

cURL example

curl -H "Authorization: Bearer $DMARCTRUST_API_KEY" \ https://www.dmarctrust.com/api/v1/reports/rpt_915

Response: 200 OK

{ "data": { "id": "rpt_915", "domain": "example.com", "domain_id": "ud_19", "report_id": "20250117030000.example.com.google.com", "sender_org": "google.com", "date_range": { "begin": "2025-01-16T00:00:00Z", "end": "2025-01-17T00:00:00Z" }, "policy_published": "v=DMARC1; p=quarantine; rua=mailto:...", "records": [ { "source_ip": "209.85.128.10", "hostname": "mail-yw1-f10.google.com", "provider": "Google Workspace", "message_count": 320, "disposition": "none", "spf_pass": true, "dkim_pass": true, "spf_domain": "example.com", "dkim_domain": "example.com" } ], "created_at": "2025-01-17T03:14:09Z" }, "meta": { "request_id": "abc123-def456-..." } }

Errors

  • 404 not_found — unknown report ID.
  • 403 retention_exceeded — the report exists but is older than your plan's retention window. Upgrade to access older data.

Pagination

List endpoints (/domains and /reports) use cursor-based pagination ordered by created_at DESC. The meta object on every list response tells you whether more results exist:

{ "meta": { "total_count": 915, "has_more": true, "next_cursor": "eyJpZCI6OTE0LC..." } }

To fetch the next page, pass next_cursor back as cursor:

GET /reports?cursor=eyJpZCI6OTE0LC...

Stop when has_more is false or next_cursor is null.

Treat the cursor as opaque. The current format is base64-encoded JSON, but the format may change without notice. Pass it back exactly as you received it; do not parse, decode, or mint your own cursors.

ID formats

Resources use prefixed IDs so you can tell them apart at a glance:

Resource Prefix Example
User usr_ usr_123
Domain ud_ ud_19
Report rpt_ rpt_915

Both prefixed and raw numeric IDs work: GET /domains/ud_19 and GET /domains/19 return the same result. Prefer the prefixed form when storing IDs, so you can route them by type later.

Retention windows

Report data, daily stats, and source data are queryable for a fixed window per plan. Data outside the window is either filtered out of list responses, clamped on the server (for date parameters), or rejected with 403 retention_exceeded on detail endpoints.

Plan Retention Effective query window
Free 7 days 9 days (7 + 2-day grace)
Starter 90 days 92 days (90 + 2-day grace)
Pro 180 days 182 days (180 + 2-day grace)

The Free plan does not include API access (you'll get 403 subscription_required); the row is included for completeness because retention drives data lifecycle even when API access is blocked.

Error handling

All errors return a JSON object with a machine-readable code, a human-readable message, and a request_id you can give to support. Validation errors include the offending parameter; rate-limit errors include retry_after; domain_limit_reached includes the current numbers so you can show a useful message.

{ "error": { "code": "not_found", "message": "The requested resource could not be found", "request_id": "abc123-..." } }
HTTP status Code Meaning
400 bad_request Malformed request or missing required parameter
400 invalid_date A date parameter could not be parsed as YYYY-MM-DD
401 invalid_api_key API key is missing, malformed, revoked, or the account is suspended
403 subscription_required No active subscription. Free plan accounts cannot use the API.
403 retention_exceeded The resource exists but is older than your plan's retention window
404 not_found Resource does not exist or is not in your account
422 unprocessable_entity Validation failed (e.g., invalid domain format). Includes a param field naming the offending input.
422 domain_limit_reached Cannot activate another domain on this plan. Includes current_limit and active_domains.
429 rate_limit_exceeded Too many requests. Includes retry_after in seconds.

Include the request_id when contacting support. It lets us find your exact request in our logs.

Integration examples

Slack alert for DMARC failures

This Python script checks for authentication failures in the last 48 hours and posts a summary to Slack. Run it as a daily cron job.

import requests, json, os API_KEY = os.environ["DMARCTRUST_API_KEY"] SLACK_WEBHOOK = os.environ["SLACK_WEBHOOK_URL"] BASE = "https://www.dmarctrust.com/api/v1" headers = {"Authorization": f"Bearer {API_KEY}"} # Get all domains domains = requests.get(f"{BASE}/domains", headers=headers).json()["data"] alerts = [] for d in domains: resp = requests.get( f"{BASE}/domains/{d['id']}/sources", headers=headers, params={"range": "48h"} ).json()["data"] for src in resp["sources"]: rejected = src.get("rejected", 0) quarantined = src.get("quarantined", 0) if rejected + quarantined > 0: alerts.append( f"*{d['domain']}*: {src['sender_domain']} " f"({rejected} rejected, {quarantined} quarantined)" ) if alerts: requests.post(SLACK_WEBHOOK, json={ "text": f"DMARC failures (last 48h):\n" + "\n".join(alerts) })

Export daily stats to CSV

Pull 30 days of per-domain statistics and write them to a CSV file. Useful for compliance reports or feeding data into a spreadsheet.

import requests, csv, os API_KEY = os.environ["DMARCTRUST_API_KEY"] BASE = "https://www.dmarctrust.com/api/v1" headers = {"Authorization": f"Bearer {API_KEY}"} domains = requests.get(f"{BASE}/domains", headers=headers).json()["data"] with open("dmarc_stats.csv", "w", newline="") as f: writer = csv.writer(f) writer.writerow(["domain", "date", "total", "spf_pass", "spf_fail", "dkim_pass", "dkim_fail"]) for d in domains: stats = requests.get( f"{BASE}/domains/{d['id']}/stats", headers=headers ).json()["data"]["daily_stats"] for day in stats: writer.writerow([ d["domain"], day["date"], day["total_messages"], day["spf_pass"], day["spf_fail"], day["dkim_pass"], day["dkim_fail"] ])

DNS health check in a shell script

Check all domains and exit with a non-zero status if any DNS record is invalid. Plug this into a CI/CD pipeline or a monitoring cron.

#!/bin/bash set -e API_KEY="${DMARCTRUST_API_KEY}" BASE="https://www.dmarctrust.com/api/v1" ERRORS=0 DOMAINS=$(curl -s -H "Authorization: Bearer $API_KEY" "$BASE/domains" | jq -r '.data[] | .id') for ID in $DOMAINS; do DNS=$(curl -s -H "Authorization: Bearer $API_KEY" "$BASE/domains/$ID/dns") DOMAIN=$(echo "$DNS" | jq -r '.data.domain') DMARC_STATUS=$(echo "$DNS" | jq -r '.data.dmarc.status') SPF_STATUS=$(echo "$DNS" | jq -r '.data.spf.status') if [ "$DMARC_STATUS" != "valid" ] || [ "$SPF_STATUS" != "valid" ]; then echo "FAIL: $DOMAIN (DMARC=$DMARC_STATUS, SPF=$SPF_STATUS)" ERRORS=$((ERRORS + 1)) fi done if [ $ERRORS -gt 0 ]; then echo "$ERRORS domain(s) have DNS issues" exit 1 fi echo "All domains healthy"

Client libraries

There is no official SDK. The API uses standard REST conventions, so any HTTP client works. Here are starter examples in three languages.

Python

import requests, os API_KEY = os.environ["DMARCTRUST_API_KEY"] BASE_URL = "https://www.dmarctrust.com/api/v1" headers = {"Authorization": f"Bearer {API_KEY}"} # List domains domains = requests.get(f"{BASE_URL}/domains", headers=headers).json()["data"] for d in domains: print(f"{d['domain']}: {d['dns_status']}") # Get reports for a specific domain reports = requests.get( f"{BASE_URL}/reports", headers=headers, params={"domain_id": "ud_19", "limit": 50} ).json()["data"]

JavaScript (Node.js)

const API_KEY = process.env.DMARCTRUST_API_KEY; const BASE_URL = "https://www.dmarctrust.com/api/v1"; async function apiGet(path, params = {}) { const url = new URL(`${BASE_URL}${path}`); Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v)); const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } }); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } // List domains const { data: domains } = await apiGet("/domains"); // Get sources for a domain (last 48 hours) const { data: sources } = await apiGet(`/domains/${domains[0].id}/sources`, { range: "48h" });

Ruby

require "net/http" require "json" API_KEY = ENV.fetch("DMARCTRUST_API_KEY") BASE_URL = "https://www.dmarctrust.com/api/v1" def api_get(endpoint, params = {}) uri = URI("#{BASE_URL}#{endpoint}") uri.query = URI.encode_www_form(params) if params.any? req = Net::HTTP::Get.new(uri) req["Authorization"] = "Bearer #{API_KEY}" res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } JSON.parse(res.body) end # List domains domains = api_get("/domains") domains["data"].each { |d| puts "#{d['domain']}: #{d['dns_status']}" } # Get DNS records dns = api_get("/domains/ud_19/dns") puts "DMARC: #{dns.dig('data', 'dmarc', 'status')}"

Managing API keys

All your keys are listed in Settings > API keys. For each key you can see the name, the first 12 characters (so you can identify it), when it was created, and when it was last used.

To revoke a key, click the trash icon next to it. Revoked keys stop working immediately. Any application using that key will get 401 responses.

Security practices

  • Use one key per application so you can revoke them independently.
  • Store keys in environment variables, not in code. Never commit them to git.
  • Rotate keys periodically. Create the new key, update your application, then revoke the old one.
  • Check "last used" dates to find unused keys and clean them up.

Versioning and stability

The API is versioned in the URL path: today, that is /api/v1. Two rules govern how it changes:

  • Additive changes are not breaking. We may add new endpoints, new fields to existing responses, new optional query parameters, or new error codes without bumping the version. Write your client to ignore unknown fields and unknown error codes (treat unknowns the same as a 500: log and surface the message).
  • Breaking changes ship as a new version. If we ever need to remove a field, rename a parameter, or change a response shape, we will release /api/v2 in parallel and keep /api/v1 running for a deprecation window. We will not silently change /v1 behavior.

To future-proof your integration: pin the base URL, ignore unknown fields, treat the cursor as opaque, and check the subscription.status field rather than hard-coding plan names.

Need help? Include the request_id from error responses when contacting support. It lets us find your exact request in our logs.

Was this page helpful? Send us feedback

Last updated: June 2026

Need expert help with email deliverability?

Hire an email deliverability consultant who has shipped billions of emails. Free assessment, hands-on engagement, written quote before any work starts.