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.