DMARC Shark

JSON API reference

The public HTTPS API is versioned under https://dmarcshark.com/api/v1. No API key or account is required. You can run a fresh check or read a saved result. Results are public: do not submit anything you expect to remain private.

POST /api/v1/checks

Runs fresh public DNS queries. This is not a cache lookup: each accepted request uses the fresh-check budget and may overwrite the saved snapshot for that domain. It returns 200 OK and a result object even if some or all DNS lookups fail; an all-unknown result has saved: false and does not overwrite a prior snapshot.

Send a JSON body with Content-Type: application/json:

Field Type Required Meaning
domain string Yes Public DNS domain, not a URL, email address, IP address, or reserved/internal name. Leading/trailing whitespace and a final dot are removed; IDNs are converted to lowercase ASCII (punycode).
selectors array of strings No Up to two known DKIM selectors, each a single label. The API also accepts a comma-separated string; an omitted or empty value checks no DKIM selector. Selectors are normalized to lowercase and deduplicated.

The normalized domain must have at least two valid labels, be at most 253 bytes, and have a recognized public suffix. A selector must be 1–63 characters, start and end with a letter or digit, and otherwise contain letters, digits, _ or -. An unknown selector is not proof that DKIM is absent from a domain.

curl -sS -X POST https://dmarcshark.com/api/v1/checks \
  -H 'Content-Type: application/json' \
  -d '{"domain":"poshtui.com","selectors":["fm1"]}'

The service queries SPF TXT and MX at the domain, DMARC TXT at _dmarc.<domain> (and possibly the organizational domain for a subdomain), plus TXT at each specified <selector>._domainkey.<domain>. A fresh check also follows literal SPF include/redirect policies and checks referenced A, AAAA and MX answers when applicable. SPF traversal is limited to 10 reference levels and 16 distinct SPF DNS queries; sender-dependent macros and PTR are not resolved. The total request lookup budget is approximately 9.5 seconds, with a maximum of 1.5 seconds per lookup. Budget exhaustion is inconclusive, not evidence of a missing policy. It queries public recursive resolvers; it does not connect to the domain's web or mail servers. The result timestamp is the start of this check, not a guarantee of when each DNS answer was published.

GET /api/v1/domains/:domain

Reads the last saved result, without DNS. The path parameter follows the same domain validation/normalization rules as the POST. No selector is needed: the snapshot includes only the selectors explicitly supplied in the previous successful fresh check. When a saved result exists, the response is 200 OK with source: "saved" and the original checked_at (not the time of the GET).

curl -sS https://dmarcshark.com/api/v1/domains/poshtui.com

If no unexpired snapshot exists, this returns 404. Snapshots older than 90 days are removed on a later saved read; an unanswered fresh check does not erase an earlier usable snapshot. To get current DNS data, use POST again.

Errors and limits

HTTP status error.code When What to do
400 Bad Request invalid_input Invalid domain or selectors. Correct the input; no DNS check is started.
404 Not Found not_found Saved read only: no unexpired snapshot. Request a fresh check explicitly if needed.
429 Too Many Requests rate_limited A per-client, global, or concurrency limit is reached. Respect Retry-After: 60 and retry later.

Example error body:

{"error":{"code":"not_found","message":"No unexpired saved check found."}}

Fresh checks: 6 per client per minute, 60 globally per minute, and up to 3 simultaneous checks per app process. Saved reads: 120 per client per minute, 1,200 globally per minute. These limits are separate. A 429 response carries a Retry-After: 60 header. Rate limits are based on the request's remote IP; do not rely on exact limits as a service-level guarantee. HTTP status 200 means the request completed, not that DNS succeeded or that email authentication passed. Check domain_status, findings[].dns_status, and saved.

See result fields and a complete response example or connect through MCP.