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
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.
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:
Response:
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:
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):
If you need a higher limit, contact us with the use case.
Conventions
Response envelope
Successful responses always have the same top-level shape:
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
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
Response: 200 OK. See the example in Getting started above.
Field reference
-
subscription.plan:free,starter, orpro. -
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:
-
Added — created via
POST /domains. DNS check runs in the background;active: false. -
DNS verified — DNS check confirms the DMARC
ruatag points to your reporting address.dns_status: "valid", stillactive: false. -
Activated — call
POST /domains/:id/activateto count it against your plan and start processing reports.active: true. -
Deactivated — call
POST /domains/:id/deactivateto stop processing without deleting the domain. Historical data is preserved subject to retention.
List 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
Response: 200 OK
Get a single domain
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
Response: 200 OK
Errors: 404 not_found if the ID is unknown or belongs to another account.
Add a domain
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
Response: 201 Created
Errors
-
422 unprocessable_entitywithparam: "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
Activate a domain so DMARCTrust starts processing the reports it receives. Activation counts the domain against your plan's total_allowed_domains.
cURL example
Response: 200 OK
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:
-
404 not_found— unknown ID.
Deactivate a domain
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
Response: 200 OK
Idempotent on already-inactive domains.
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
Response: 202 Accepted
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
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
Response: 200 OK
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
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
Response: 200 OK
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
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
Response: 200 OK
status on a source is "resolved" when we identified the provider, "unresolved" when we have IPs but no provider attribution yet.
Reports
List 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
Response: 200 OK
summary can be null for reports that contain no per-record entries (rare, but possible for empty submitter reports).
Get report details
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
Response: 200 OK
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:
To fetch the next page, pass next_cursor back as cursor:
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.
| 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.
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.
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.
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
JavaScript (Node.js)
Ruby
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/v2in parallel and keep/api/v1running for a deprecation window. We will not silently change/v1behavior.
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.