{"openapi":"3.1.0","info":{"title":"DMARCguard API","version":"v1","description":"Read your organization's DMARC data programmatically. Authenticate with an API key, page with limit and offset, and watch the rate-limit headers."},"servers":[{"url":"https://api.dmarcguard.io/api/v1"}],"tags":[{"name":"Reports","description":"Aggregate (RUA) reports as received, parsed and classified."},{"name":"Analytics","description":"Organization-wide and per-domain rollups computed by DMARCguard."},{"name":"Domains","description":"The domains you monitor and their health."},{"name":"Sources","description":"Who sends mail as your domains, and whether they should."},{"name":"Events","description":"Conditions that needed attention and whether they were acknowledged."},{"name":"API keys","description":"How much of the hourly budget the organization's keys have used."},{"name":"Vocabulary","description":"The enumerations other responses use, keyed by category: verification states, severities, grades, policies and reputation."}],"paths":{"/api-keys/usage":{"get":{"operationId":"getAPIKeyUsage","tags":["API keys"],"summary":"API usage","description":"The organization's current hourly window, a verdict on how close it is to the quota, a request history (hourly points for a window of two days or less, daily beyond) and the share attributed to each key. Poll this instead of parsing headers when you want the trend.","parameters":[{"name":"days","in":"query","description":"History window in days, 1 to 90.","schema":{"type":"integer","default":30}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"400":{"description":"days is outside 1 to 90.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"The organization's plan could not be resolved; retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"integration:read","x-scopes":["read","full"],"x-heavy":"","x-order":14}},"/compliance-score":{"get":{"operationId":"getComplianceScore","tags":["Analytics"],"summary":"Compliance score","description":"The composite score and letter grade, with the per-domain breakdown behind it. The figures may be up to a few minutes old.","parameters":[{"name":"domain","in":"query","description":"Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization.","schema":{"type":"string"},"example":"example.com"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceScore"}}}},"400":{"description":"The domain query parameter is not a valid domain name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":5}},"/compliance-score/history":{"get":{"operationId":"getComplianceScoreHistory","tags":["Analytics"],"summary":"Compliance score history","description":"Daily score snapshots for the organization over the requested window.","parameters":[{"name":"days","in":"query","description":"Window length in days, 1 to 365. Out-of-range values fall back to the default.","schema":{"type":"integer","default":30}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoreHistoryResponse"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":6}},"/dashboard/overview":{"get":{"operationId":"getDashboardOverview","tags":["Analytics"],"summary":"Organization overview","description":"The dashboard hero in one call: organization health, one card per domain and the pending action-item counts. The figures may be up to a few minutes old.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardOverviewResponse"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"Too many expensive requests in flight for this organization; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"concurrency-capped","x-order":3}},"/domains":{"get":{"operationId":"listDomains","tags":["Domains"],"summary":"List domains","description":"Every domain the organization monitors, added manually or discovered from reports, with its verification state per protocol.","parameters":[{"name":"limit","in":"query","description":"Page size. Values above the maximum are clamped; zero, negative or non-numeric values are ignored and the default applies.","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","description":"Number of rows to skip; combine with limit to page. A negative or non-numeric value is treated as 0.","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainListResponse"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"domain:read","x-scopes":["read","full"],"x-heavy":"","x-order":9}},"/domains/{domain}/health":{"get":{"operationId":"getDomainHealth","tags":["Domains"],"summary":"Domain health","description":"Score, grade, DNS record status, compliance, top senders, open action items and a trend sparkline for one domain. The figures may be up to an hour old.","parameters":[{"name":"domain","in":"path","description":"A domain monitored by the organization.","required":true,"schema":{"type":"string"},"example":"example.com"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainHealthResponse"}}}},"400":{"description":"The path segment is not a valid domain name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"The domain is not monitored by this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"domain:read","x-scopes":["read","full"],"x-heavy":"heavy","x-order":10}},"/enums":{"get":{"operationId":"listEnums","tags":["Vocabulary"],"summary":"List enumerations","description":"Every enumeration the documented responses use, keyed by category. Read it once at start-up instead of hardcoding a code list, and fall back to the raw code when a category grows a value your copy does not know. Which field reads which category: dmarc_state, tlsrpt_state and forensic_state are verification_state; events[].type is alert_type and events[].severity is alert_severity; action_items[].severity is action_item_severity, a different scale; abuse_status is reputation_status; policy fields are dmarc_policy; health_state and grade are the categories of the same name.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnumsResponse"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":15}},"/events":{"get":{"operationId":"listEvents","tags":["Events"],"summary":"List events","description":"The attention feed, newest first: every condition that needed attention, with its deep link and whether it was acknowledged.","parameters":[{"name":"from","in":"query","description":"Earliest date to include, YYYY-MM-DD (UTC).","schema":{"type":"string"},"example":"2026-01-01"},{"name":"to","in":"query","description":"Latest date to include, YYYY-MM-DD (UTC), inclusive.","schema":{"type":"string"},"example":"2026-01-31"},{"name":"acknowledged","in":"query","description":"true returns acknowledged events only, false open ones only. Omit for both.","schema":{"type":"boolean"}},{"name":"limit","in":"query","description":"Page size. Values above the maximum are clamped; zero, negative or non-numeric values are ignored and the default applies.","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","description":"Number of rows to skip; combine with limit to page. A negative or non-numeric value is treated as 0.","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponse"}}}},"400":{"description":"from or to is not YYYY-MM-DD.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"alert:read","x-scopes":["read","full"],"x-heavy":"","x-order":13}},"/reports":{"get":{"operationId":"listReports","tags":["Reports"],"summary":"List aggregate reports","description":"Aggregate (RUA) reports received for the organization, newest first by default. Set format=csv or format=json to download the current page as an attachment instead of the paged envelope.","parameters":[{"name":"domain","in":"query","description":"Keep only reports whose domain contains this text; a substring match, unlike the exact match elsewhere.","schema":{"type":"string"},"example":"example.com"},{"name":"org","in":"query","description":"Reporting organization name as printed in the report; a substring match, e.g. google.com.","schema":{"type":"string"},"example":"google.com"},{"name":"result","in":"query","description":"pass keeps reports in which every message passed DMARC; fail keeps reports in which at least one message failed. A report with no messages is in neither.","schema":{"type":"string","enum":["pass","fail"]}},{"name":"source_ip","in":"query","description":"Keep only reports that contain exactly this sending IP.","schema":{"type":"string"},"example":"203.0.113.10"},{"name":"from","in":"query","description":"Earliest date to include, YYYY-MM-DD (UTC). A report is kept when its reporting window starts on or after this day.","schema":{"type":"string"},"example":"2026-01-01"},{"name":"to","in":"query","description":"Latest date to include, YYYY-MM-DD (UTC), inclusive. A report is kept when its reporting window ends on or before this day, so a window that straddles the boundary needs a range covering both days.","schema":{"type":"string"},"example":"2026-01-31"},{"name":"search","in":"query","description":"Fuzzy match against reporter names, domains and report IDs. Longer terms are truncated.","schema":{"type":"string"}},{"name":"sort_by","in":"query","description":"Sort column. An unknown value falls back to the default.","schema":{"type":"string","default":"date_end","enum":["date_end","received","org_name","domain","total_messages","report_id","compliance"]}},{"name":"sort_dir","in":"query","description":"Sort direction.","schema":{"type":"string","default":"desc","enum":["asc","desc"]}},{"name":"limit","in":"query","description":"Page size. Values above the maximum are clamped; zero, negative or non-numeric values are ignored and the default applies.","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","description":"Number of rows to skip; combine with limit to page. A negative or non-numeric value is treated as 0.","schema":{"type":"integer","default":0}},{"name":"format","in":"query","description":"Download the current page (up to the page limit) as an attachment instead of the JSON envelope. The CSV is a subset of the JSON row: it omits received_at and prints the reporting window as YYYY-MM-DD.","schema":{"type":"string","enum":["csv","json"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportListResponse"}}}},"400":{"description":"from or to is not YYYY-MM-DD.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"report:read","x-scopes":["read","full"],"x-heavy":"","x-order":0}},"/reports/date-range":{"get":{"operationId":"getReportDateRange","tags":["Reports"],"summary":"Report date range","description":"The earliest and latest report dates the organization holds, for building date pickers and backfill loops.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDateRangeResponse"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"report:read","x-scopes":["read","full"],"x-heavy":"","x-order":1}},"/reports/{id}":{"get":{"operationId":"getReport","tags":["Reports"],"summary":"Get one aggregate report","description":"The parsed report as the reporter sent it, plus DMARCguard's per-record classification and identifier-alignment verdicts. Field names inside the report object are the report's own elements, re-cased (ReportMetadata, PolicyPublished, Records); take the names from the table below rather than transforming the XML yourself. Its date range is the reporter's epoch seconds.","parameters":[{"name":"id","in":"path","description":"Report ID as returned by List aggregate reports (Crockford Base32).","required":true,"schema":{"type":"string"},"example":"0J1Z2K3M4N5P6Q7R"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetailResponse"}}}},"400":{"description":"The id is not a valid identifier.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No report with that ID in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"report:read","x-scopes":["read","full"],"x-heavy":"","x-order":2}},"/sources":{"get":{"operationId":"listSources","tags":["Sources"],"summary":"List sending sources","description":"Sending IPs seen in reports, enriched with sender identification, reputation and DMARCguard's authorized-or-not verdict.","parameters":[{"name":"domain","in":"query","description":"Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization.","schema":{"type":"string"},"example":"example.com"},{"name":"search","in":"query","description":"Substring match on IP, domain or sender name. Longer terms are truncated.","schema":{"type":"string"}},{"name":"reputation","in":"query","description":"Keep only sources with this abuse status. An unrecognised value is ignored rather than rejected.","schema":{"type":"string","enum":["clean","suspicious","malicious","unknown"]}},{"name":"has_failures","in":"query","description":"true keeps only sources with SPF or DKIM failures.","schema":{"type":"boolean"}},{"name":"sort_by","in":"query","description":"Sort column.","schema":{"type":"string","default":"total_messages","enum":["total_messages","source_ip","first_seen","last_seen","compliance","auth_failures","domains"]}},{"name":"sort_dir","in":"query","description":"Sort direction.","schema":{"type":"string","default":"desc","enum":["asc","desc"]}},{"name":"limit","in":"query","description":"Page size. Values above the maximum are clamped; zero, negative or non-numeric values are ignored and the default applies.","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","description":"Number of rows to skip; combine with limit to page. A negative or non-numeric value is treated as 0.","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourcesListResponse"}}}},"400":{"description":"The domain query parameter is not a valid domain name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":11}},"/sources/{ip}":{"get":{"operationId":"getSource","tags":["Sources"],"summary":"Get one sending source","description":"Everything known about one sending IP: per-domain records, timeline, geo, blocklist verdict and classification.","parameters":[{"name":"ip","in":"path","description":"IPv4 or IPv6 address exactly as reported.","required":true,"schema":{"type":"string"},"example":"203.0.113.10"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SourceDetailResponse"}}}},"400":{"description":"The path segment is not a valid IP address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No report in this organization mentions that IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":12}},"/statistics":{"get":{"operationId":"getStatistics","tags":["Analytics"],"summary":"Message statistics","description":"Totals across every report held: messages, compliant messages, distinct sources and domains.","parameters":[{"name":"domain","in":"query","description":"Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization.","schema":{"type":"string"},"example":"example.com"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Statistics"}}}},"400":{"description":"The domain query parameter is not a valid domain name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":4}},"/top-sources":{"get":{"operationId":"listTopSources","tags":["Analytics"],"summary":"Top sending IPs","description":"The highest-volume sending IPs with pass, fail and per-mechanism failure counts.","parameters":[{"name":"domain","in":"query","description":"Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization.","schema":{"type":"string"},"example":"example.com"},{"name":"limit","in":"query","description":"Number of IPs to return. Values above the maximum are clamped.","schema":{"type":"integer","default":10}},{"name":"offset","in":"query","description":"Number of rows to skip; combine with limit to page. A negative or non-numeric value is treated as 0.","schema":{"type":"integer","default":0}},{"name":"format","in":"query","description":"Download the rows as a CSV attachment instead of JSON. The CSV carries the IP and its total, pass and fail counts only.","schema":{"type":"string","enum":["csv"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopSourcesResponse"}}}},"400":{"description":"The domain query parameter is not a valid domain name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":8}},"/trends":{"get":{"operationId":"getTrends","tags":["Analytics"],"summary":"Daily message trends","description":"Per-day message, pass and fail counts over the requested window.","parameters":[{"name":"domain","in":"query","description":"Restrict to one monitored domain, matched exactly; a subdomain is a separate domain. Omit for the whole organization.","schema":{"type":"string"},"example":"example.com"},{"name":"days","in":"query","description":"Window length in days, 1 to 365. Out-of-range values fall back to the default.","schema":{"type":"integer","default":30}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrendResponse"}}}},"400":{"description":"The domain query parameter is not a valid domain name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"Missing, malformed, revoked, expired or not-entitled API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The key's scope or its owner's role does not allow this operation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Unexpected server error; retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"A transient failure resolving the organization or reading storage; retry after a short pause.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"x-permission":"analytics:read","x-scopes":["read","full"],"x-heavy":"","x-order":7}}},"components":{"schemas":{"ComplianceScore":{"type":"object","properties":{"overall_score":{"type":"number","description":"Composite health score, 0 to 100. Compliance weighs heaviest, then SPF, DKIM and policy strength equally."},"grade":{"type":"string","description":"Letter grade for overall_score: A+, A, A-, B+, B, B-, C+, C, C-, D or F, or N/A before the first report has been received."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of messages that passed DMARC."},"spf_pass_rate":{"type":"number","description":"Percentage from 0 to 100 of messages with an aligned SPF pass."},"dkim_pass_rate":{"type":"number","description":"Percentage from 0 to 100 of messages with an aligned DKIM pass."},"policy_strength":{"type":"number","description":"A 0 to 100 rating of how far the published policies have moved towards enforcement, averaged across the domains."},"domain_scores":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","description":"The domain these figures are for."},"score":{"type":"number","description":"Composite health score for the domain, 0 to 100."},"grade":{"type":"string","description":"Letter grade for score, on the same scale as the overall grade."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of this domain's messages that passed DMARC."},"spf_pass_rate":{"type":"number","description":"Percentage from 0 to 100 of this domain's messages with an aligned SPF pass."},"dkim_pass_rate":{"type":"number","description":"Percentage from 0 to 100 of this domain's messages with an aligned DKIM pass."},"policy":{"type":"string","description":"The DMARC policy the reports saw published for this domain: none, quarantine or reject."},"total_messages":{"type":"integer","description":"Messages reported for this domain."}},"required":["domain","score","grade","compliance_rate","spf_pass_rate","dkim_pass_rate","policy","total_messages"]},"description":"The same figures for each domain behind the overall score."},"total_messages":{"type":"integer","description":"Messages the score was computed over."},"total_reports":{"type":"integer","description":"Reports the score was computed over."}},"required":["overall_score","grade","compliance_rate","spf_pass_rate","dkim_pass_rate","policy_strength","domain_scores","total_messages","total_reports"]},"DashboardOverviewResponse":{"type":"object","properties":{"org_health":{"type":"object","properties":{"overall_score":{"type":"integer","description":"Composite health score for the organization, 0 to 100."},"grade":{"type":"string","description":"Letter grade for overall_score: A+, A, A-, B+, B, B-, C+, C, C-, D or F, or N/A before the first report has been received."},"verdict":{"type":"string","description":"A ready-to-show sentence summarising the organization's posture and how many domains need attention. Display it; its wording is not a value to branch on."},"health_state":{"type":"string","description":"The coarse band behind the verdict, for colouring: nodata before any report arrives, then secure, warning or critical as the score falls."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of reported messages that passed DMARC."},"total_messages":{"type":"integer","description":"Messages reported across every domain."},"total_domains":{"type":"integer","description":"Domains the organization's reports cover."},"domains_needing_attention":{"type":"integer","description":"Domains scoring below the healthy band or carrying at least one open action item."},"unique_source_ips":{"type":"integer","description":"Distinct sending IPs seen across every report."},"has_data":{"type":"boolean","description":"False until the first report arrives; the scores above mean nothing while it is false."},"forwarded_messages":{"type":"integer","description":"DMARC-failing messages judged to be ordinary forwarding rather than spoofing. Already inside the failures; it does not change compliance_rate."}},"description":"Organization-wide posture: score, grade and the headline counts.","required":["overall_score","grade","verdict","health_state","compliance_rate","total_messages","total_domains","domains_needing_attention","unique_source_ips","has_data","forwarded_messages"]},"domain_cards":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","description":"The domain this card is about."},"score":{"type":"integer","description":"Composite health score for the domain, 0 to 100."},"grade":{"type":"string","description":"Letter grade for score, on the same scale as org_health.grade."},"verdict":{"type":"string","description":"A ready-to-show sentence for the card. Display it; its wording is not a value to branch on."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of this domain's reported messages that passed DMARC."},"total_messages":{"type":"integer","description":"Messages reported for this domain."},"policy":{"type":"string","description":"The DMARC policy in force for the domain: none, quarantine or reject. Empty when no policy could be read."},"top_issue":{"type":"string","description":"Title of the most urgent open action item for this domain; empty when there is none."},"top_issue_severity":{"type":"string","description":"Priority of top_issue: high, medium or low. Empty when there is no open item."},"action_count":{"type":"integer","description":"Open action items for this domain."},"inherited":{"type":"boolean","description":"True when the policy shown comes from the organizational parent rather than the domain's own record."},"inherited_from":{"type":"string","description":"The parent domain the policy was inherited from."},"forwarded_messages":{"type":"integer","description":"This domain's DMARC-failing messages judged to be ordinary forwarding rather than spoofing."}},"required":["domain","score","grade","verdict","compliance_rate","total_messages","policy","top_issue","top_issue_severity","action_count","forwarded_messages"]},"description":"One card per domain, worst score first."},"action_items_summary":{"type":"object","properties":{"total_pending":{"type":"integer","description":"Open action items across the organization; the sum of high, medium and low."},"high":{"type":"integer","description":"Open action items at high priority."},"medium":{"type":"integer","description":"Open action items at medium priority."},"low":{"type":"integer","description":"Open action items at low priority."}},"description":"How many action items are open, bucketed by priority.","required":["total_pending","high","medium","low"]}},"required":["org_health","domain_cards","action_items_summary"]},"DomainHealthResponse":{"type":"object","properties":{"domain":{"type":"string","description":"The domain this report is about."},"overall_score":{"type":"integer","description":"Composite health score for the domain, 0 to 100."},"grade":{"type":"string","description":"Letter grade for overall_score: A+, A, A-, B+, B, B-, C+, C, C-, D or F, or N/A before the first report has been received."},"verdict":{"type":"string","description":"A ready-to-show sentence summarising the domain's posture. Display it; its wording is not a value to branch on."},"last_report_date":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"},"total_messages":{"type":"integer","description":"Messages reported for this domain in the scoring window."},"dns":{"type":"object","properties":{"dmarc":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"The domain's DMARC record.","required":["status","details"]},"spf":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"The domain's SPF record.","required":["status","details"]},"dkim":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"The domain's DKIM signing keys.","required":["status","details"]},"mta_sts":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"The domain's MTA-STS policy.","required":["status","details"]},"tls_rpt":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"The domain's TLS reporting record.","required":["status","details"]},"bimi":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"The domain's BIMI record.","required":["status","details"]},"dane":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok, warning when it exists but is not doing its job, error when it is missing or malformed, and unknown when nothing could be determined."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."},"inherited_from":{"type":"string","description":"The parent domain the record was found on, when this domain has none of its own."},"hosted_cta":{"type":["null","object"],"properties":{"label":{"type":"string","description":"Short label for the link, ready to show."},"url":{"type":"string","description":"SPA path of the Hosted DNS page with this domain selected."}},"description":"Link to host this protocol on DMARCguard, present when the record is missing or failing, or a hosted setup is still in progress.","required":["label","url"]}},"description":"DANE (TLSA) records for the domain's mail servers.","required":["status","details"]}},"description":"What a live DNS lookup found for each email-authentication record.","required":["dmarc","spf","dkim","mta_sts","tls_rpt","bimi","dane"]},"compliance":{"type":"object","properties":{"rate":{"type":"number","description":"Percentage from 0 to 100 of this domain's messages that passed DMARC."},"spf_pass":{"type":"number","description":"Percentage from 0 to 100 of this domain's messages with an aligned SPF pass."},"dkim_pass":{"type":"number","description":"Percentage from 0 to 100 of this domain's messages with an aligned DKIM pass."},"policy":{"type":"string","description":"The DMARC policy in force for the domain: none, quarantine or reject."}},"description":"How this domain's mail authenticates.","required":["rate","spf_pass","dkim_pass","policy"]},"top_senders":{"type":"array","items":{"type":"object","properties":{"ip":{"type":"string","description":"The sending IP."},"count":{"type":"integer","description":"Messages it sent as this domain."},"spf_result":{"type":"string","description":"Whether SPF produced an aligned pass for its mail: pass or fail."},"dkim_result":{"type":"string","description":"Whether DKIM produced an aligned pass for its mail: pass or fail."},"service_name":{"type":"string","description":"The sending service the IP was identified as, when it is recognised."},"service_icon":{"type":"string","description":"Icon slug for the identified service."},"service_slug":{"type":"string","description":"Stable identifier of the identified service."}},"required":["ip","count","spf_result","dkim_result"]},"description":"The highest-volume senders for the domain, busiest first."},"alert_count":{"type":"integer","description":"Open attention items for this domain."},"action_items":{"type":["null","array"],"items":{"type":"object","properties":{"type":{"type":"string","description":"What kind of problem this is. A gap the live DNS check found carries a dns_ prefix (dns_dmarc, dns_spf, dns_dkim, dns_mta_sts, dns_bimi); anything DMARCguard flagged carries the same type the events feed uses."},"severity":{"type":"string","description":"How urgent the item is: high, medium or low."},"title":{"type":"string","description":"One-line statement of the problem."},"suggested_action":{"type":"string","description":"What to do about it, ready to show."},"cta_url":{"type":"string","description":"Link to the screen where the item is resolved."},"cta_label":{"type":"string","description":"Label for that link."}},"required":["severity","title","suggested_action"]},"description":"What to do next for this domain, most urgent first. It is a to-do list, not an inventory, so it is capped at a handful."},"trend_sparkline":{"type":"array","items":{"type":"object","properties":{"date":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of the day's messages that passed DMARC."},"total_messages":{"type":"integer","description":"Messages reported for the day."}},"required":["date","compliance_rate","total_messages"]},"description":"One point per day for the trend line, oldest first."},"empty_state":{"type":["null","object"],"properties":{"healthy":{"type":"boolean","description":"True only when there is genuinely nothing wrong. False means the list is empty for some other reason, which the message gives."},"message":{"type":"string","description":"A ready-to-show sentence explaining why there is nothing to do."},"cta_url":{"type":"string","description":"Where to send the reader next, when there is somewhere useful."},"cta_label":{"type":"string","description":"Label for that link."}},"description":"What an empty action_items list means. Present only when the list is empty.","required":["healthy","message"]},"computed_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"required":["domain","overall_score","grade","verdict","last_report_date","total_messages","dns","compliance","top_senders","alert_count","action_items","trend_sparkline","computed_at"]},"DomainListResponse":{"type":"object","properties":{"domains":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Identifier, Crockford Base32. Opaque; never parse it as a number."},"domain":{"type":"string","description":"The monitored domain."},"verified":{"type":"boolean","description":"Legacy boolean; prefer dmarc_state, which distinguishes no record from a record that reports elsewhere and from an inconclusive check."},"dmarc_state":{"type":"string","description":"State of the domain's DMARC record: pending before the first check, verified when its own record reports to you, verified_inherited when the organizational parent's record does, not_published when the authoritative nameservers say there is none, published_not_ours when a record exists but reports elsewhere, multiple_records when the name serves more than one (receivers discard them all), and unconfirmed when the check could not reach authoritative DNS. unconfirmed is not a negative.","enum":["pending","verified","verified_inherited","not_published","published_not_ours","unconfirmed","multiple_records"]},"tlsrpt_enabled":{"type":"boolean","description":"Whether TLS reporting is switched on for this domain."},"tlsrpt_state":{"type":"string","description":"State of the domain's TLS-RPT record, on the same scale as dmarc_state.","enum":["pending","verified","verified_inherited","not_published","published_not_ours","unconfirmed","multiple_records"]},"forensic_enabled":{"type":"boolean","description":"Whether failure (forensic) reporting is switched on for this domain."},"forensic_state":{"type":"string","description":"State of the failure-report address in the domain's DMARC record, on the same scale as dmarc_state.","enum":["pending","verified","verified_inherited","not_published","published_not_ours","unconfirmed","multiple_records"]},"source":{"type":"string","description":"How the domain got here: manual if you added it, auto if a report introduced it, both when both are true."},"verified_source":{"type":"string","description":"Why the domain counts as verified: self for its own record, inherited for the organizational parent's. Empty when it is not verified."},"inherited_from":{"type":"string","description":"The organizational parent whose record covers this domain; present only when verified_source is inherited."},"report_count":{"type":"integer","description":"Aggregate reports held for this domain."},"verified_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"required":["domain","verified","dmarc_state","tlsrpt_enabled","tlsrpt_state","forensic_enabled","forensic_state","source","report_count"]},"description":"The requested page. Never null; an empty page is []."},"total":{"type":"integer","description":"Domains the organization monitors, before paging."},"limit":{"type":"integer","description":"The page size actually applied."},"offset":{"type":"integer","description":"The offset actually applied."}},"required":["domains","total","limit","offset"]},"EnumsResponse":{"type":"object","additionalProperties":{"type":["null","array"],"items":{"type":"object","properties":{"code":{"type":"string","description":"The value that appears on the wire in this category's fields. Match on this, never on the label."},"label":{"type":"string","description":"How DMARCguard writes the code for a reader. Safe to display; it may be reworded between releases."},"severity":{"type":"string","description":"Suggested tone for the entry, where the category defines one: neutral, success, info, warning or critical."},"deliverable":{"type":"boolean","description":"True on an alert type that can be pushed to email, a webhook or a chat channel; the rest surface in the app only."}},"required":["code","label"]}}},"ErrorBody":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable, title-cased reason."},"code":{"type":"string","description":"Optional machine-readable discriminator, e.g. quota_exceeded."},"required":{"type":"string","description":"The permission the request lacked; present only on a permission denial."}},"required":["error"]},"EventListResponse":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Identifier, Crockford Base32. Opaque; never parse it as a number."},"type":{"type":"string","description":"What was detected. Read the type vocabulary from the enumerations endpoint instead of hardcoding it, and fall back to the raw value for a type you do not recognise."},"type_label":{"type":"string","description":"How DMARCguard writes the type for a reader."},"severity":{"type":"string","description":"How urgent the event is: info, warning or critical. Action items call the same three levels low, medium and high."},"title":{"type":"string","description":"One-line headline for the event."},"message":{"type":"string","description":"The detail behind the title, ready to show."},"domain":{"type":"string","description":"The domain the event is about; empty for an organization-wide event."},"link":{"type":"string","description":"Link to the screen where the event is resolved."},"acknowledged":{"type":"boolean","description":"True once the underlying item was marked handled. Acknowledging happens in the app, not through this endpoint."},"created_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"required":["id","type","type_label","severity","title","message","domain","acknowledged","created_at"]},"description":"The requested page, newest first. Never null; an empty page is []."},"total":{"type":"integer","description":"Events matching the filter, before paging."},"limit":{"type":"integer","description":"The page size actually applied."},"offset":{"type":"integer","description":"The offset actually applied."}},"required":["events","total","limit","offset"]},"ReportDateRangeResponse":{"type":"object","properties":{"earliest":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"},"latest":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"}},"required":["earliest","latest"]},"ReportDetailResponse":{"type":"object","properties":{"Version":{"type":"string","description":"Report format version, 1.0 for every conformant report."},"ReportMetadata":{"type":"object","properties":{"OrgName":{"type":"string","description":"Name of the organization that generated the report."},"Email":{"type":"string","description":"Address to contact that organization about the report."},"ExtraContactInfo":{"type":"string","description":"Further contact details the reporter chose to include."},"ReportID":{"type":"string","description":"The reporter's unique identifier for this report. A resent report reuses it."},"DateRange":{"type":"object","properties":{"Begin":{"type":"integer","description":"Start of the reporting period, in seconds since the Unix epoch (UTC). This object is the reporter's own data, so it keeps the encoding the standard mandates."},"End":{"type":"integer","description":"End of the reporting period, in seconds since the Unix epoch (UTC)."}},"description":"The reporting period this report covers, typically one UTC day.","required":["Begin","End"]},"Errors":{"type":["null","array"],"items":{"type":"string"},"description":"Problems the reporter hit while reading your DMARC record. Worth reading when a report looks wrong."},"Generator":{"type":"string","description":"Name and version of the software that produced the report."}},"description":"Who sent the report, when, and how to reach them.","required":["OrgName","Email","ExtraContactInfo","ReportID","DateRange","Errors","Generator"]},"PolicyPublished":{"type":"object","properties":{"Domain":{"type":"string","description":"The domain whose DMARC record the reporter applied."},"ADKIM":{"type":"string","description":"DKIM alignment mode published by the domain: r (relaxed) or s (strict)."},"ASPF":{"type":"string","description":"SPF alignment mode published by the domain: r (relaxed) or s (strict)."},"P":{"type":"string","description":"Policy the domain asked receivers to apply: none, quarantine or reject."},"SP":{"type":"string","description":"Policy published for subdomains, when the domain publishes one separately."},"NP":{"type":"string","description":"Policy published for subdomains that do not exist, when the domain publishes one."},"PCT":{"type":"integer","description":"Sampling percentage from the older DMARC specification, 0 to 100. Current reports omit it; the tag was removed from the standard."},"FO":{"type":"string","description":"Failure-reporting options the domain published, deciding when a receiver sends a failure report."},"T":{"type":"string","description":"Testing flag the domain published, y or n: y asks receivers to report without enforcing the policy."},"PSD":{"type":"string","description":"Flag, y or n, marking the record as published by a public suffix domain rather than an ordinary one. Only reporters that send the element supply it."},"DiscoveryMethod":{"type":"string","description":"How the reporter found the record: treewalk for the current algorithm, psl for the older public-suffix-list one. Diagnostic only."}},"description":"The DMARC policy the reporter read from your DNS while it handled this mail.","required":["Domain","ADKIM","ASPF","P","SP","NP","PCT","FO","T","PSD","DiscoveryMethod"]},"Records":{"type":["null","array"],"items":{"type":"object","properties":{"Row":{"type":"object","properties":{"SourceIP":{"type":"string","description":"IP address the messages were delivered from."},"Count":{"type":"integer","description":"Messages this entry accounts for."},"PolicyEvaluated":{"type":"object","properties":{"Disposition":{"type":"string","description":"What the receiver actually did with the messages: none, quarantine or reject."},"DKIM":{"type":"string","description":"Whether DKIM produced an aligned pass for DMARC: pass or fail. Not the raw DKIM result."},"SPF":{"type":"string","description":"Whether SPF produced an aligned pass for DMARC: pass or fail. Not the raw SPF result."},"Reason":{"type":["null","array"],"items":{"type":"object","properties":{"Type":{"type":"string","description":"Why the receiver departed from your policy: local_policy, mailing_list, trusted_forwarder, policy_test_mode, or other."},"Comment":{"type":"string","description":"Free text the receiver added about the override."}},"required":["Type","Comment"]},"description":"Why the disposition differs from the published policy, when it does."}},"description":"What DMARC concluded for these messages.","required":["Disposition","DKIM","SPF","Reason"]}},"description":"The sending IP, how many messages it sent, and what the receiver did with them.","required":["SourceIP","Count","PolicyEvaluated"]},"Identifiers":{"type":"object","properties":{"EnvelopeTo":{"type":"string","description":"Recipient domain the messages were addressed to."},"EnvelopeFrom":{"type":"string","description":"Domain of the envelope sender SPF checked. May be present but empty when the message had no return path."},"HeaderFrom":{"type":"string","description":"The From domain the recipient saw. DMARC aligns everything against this one."}},"description":"The domains the policy was matched against.","required":["EnvelopeTo","EnvelopeFrom","HeaderFrom"]},"AuthResults":{"type":"object","properties":{"DKIM":{"type":["null","array"],"items":{"type":"object","properties":{"Domain":{"type":"string","description":"Signing domain from the signature's d= tag."},"Selector":{"type":"string","description":"Selector from the signature's s= tag, naming the key in DNS."},"Result":{"type":"string","description":"Raw DKIM verification result: none, pass, fail, policy, neutral, temperror or permerror. Alignment is decided separately."},"HumanResult":{"type":"string","description":"The reporter's own explanation of a verification failure."}},"required":["Domain","Selector","Result","HumanResult"]},"description":"One entry per DKIM signature the receiver verified."},"SPF":{"type":["null","array"],"items":{"type":"object","properties":{"Domain":{"type":"string","description":"Domain the SPF check was run against."},"Scope":{"type":"string","description":"Which identity was checked: mfrom, the envelope sender, is the only one DMARC uses; older reporters also send helo."},"Result":{"type":"string","description":"Raw SPF verification result: none, neutral, pass, fail, softfail, temperror or permerror. Alignment is decided separately."},"HumanResult":{"type":"string","description":"The reporter's own explanation of a verification failure."}},"required":["Domain","Scope","Result","HumanResult"]},"description":"The SPF check the receiver ran."}},"description":"The raw SPF and DKIM results, before DMARC alignment was applied to them.","required":["DKIM","SPF"]}},"required":["Row","Identifiers","AuthResults"]},"description":"One entry per sending IP and authentication outcome. A report always carries at least one."},"parse_warnings":{"type":["null","array"],"items":{"type":"string"},"description":"Ways this report departs from the standard. DMARCguard keeps such a report and flags it rather than dropping it; not part of the report itself."},"record_classifications":{"type":"array","items":{"type":"string"},"description":"DMARCguard's verdict on each record, in the same order as Records: authenticated, likely_forwarded, suspicious or needs_review."},"record_alignment":{"type":"array","items":{"type":"object","properties":{"spf":{"type":["null","array"],"items":{"type":"boolean"},"description":"One flag per SPF result on the record, in order, saying whether it aligned with the From domain under the published alignment mode."},"dkim":{"type":["null","array"],"items":{"type":"boolean"},"description":"One flag per DKIM result on the record, in order, saying whether it aligned with the From domain under the published alignment mode."}},"required":["spf","dkim"]},"description":"Which of each record's raw SPF and DKIM results aligned with the From domain, in the same order as Records."},"failure_summary":{"type":"object","properties":{"spf_only":{"type":"integer","description":"Records where only SPF failed to produce an aligned pass."},"dkim_only":{"type":"integer","description":"Records where only DKIM failed to produce an aligned pass."},"both":{"type":"integer","description":"Records where both failed, which is what makes a record fail DMARC. The three buckets are mutually exclusive."},"total":{"type":"integer","description":"Records in the report, failing or not."}},"description":"How the report's records split by which mechanism failed.","required":["spf_only","dkim_only","both","total"]}},"required":["Version","ReportMetadata","PolicyPublished","Records","record_classifications","record_alignment","failure_summary"]},"ReportListResponse":{"type":"object","properties":{"reports":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Identifier, Crockford Base32. Opaque; never parse it as a number."},"report_id":{"type":"string","description":"The reporter's own identifier for the report, as printed inside it. Not the id you fetch the report by."},"org_name":{"type":"string","description":"The organization that sent the report."},"domain":{"type":"string","description":"The domain the report is about."},"total_messages":{"type":"integer","description":"Messages the report accounts for."},"compliant_messages":{"type":"integer","description":"Of those, how many passed DMARC."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of this report's messages that passed DMARC."},"policy_p":{"type":"string","description":"The DMARC policy the reporter saw published: none, quarantine or reject."},"date_begin":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"date_end":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"received_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"required":["id","report_id","org_name","domain","total_messages","compliant_messages","compliance_rate","policy_p","date_begin","date_end","received_at"]},"description":"The requested page, newest first by default. Never null; an empty page is []."},"total":{"type":"integer","description":"Reports matching the filter, before paging."},"limit":{"type":"integer","description":"The page size actually applied."},"offset":{"type":"integer","description":"The offset actually applied."},"has_any":{"type":"boolean","description":"True when the organization holds any report at all, ignoring filters; use it for empty states."}},"required":["reports","total","limit","offset","has_any"]},"ScoreHistoryResponse":{"type":"object","properties":{"days":{"type":"integer","description":"Length of the window in days, as applied."},"points":{"type":"array","items":{"type":"object","properties":{"score":{"type":"number","description":"Composite health score on the snapshot day, 0 to 100."},"grade":{"type":"string","description":"Letter grade for score, on the same scale as the current grade."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of messages that passed DMARC on the snapshot day."},"spf_rate":{"type":"number","description":"Percentage from 0 to 100 of messages with an aligned SPF pass on the snapshot day."},"dkim_rate":{"type":"number","description":"Percentage from 0 to 100 of messages with an aligned DKIM pass on the snapshot day."},"total_messages":{"type":"integer","description":"Messages the snapshot was computed over."},"snapshot_date":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"}},"required":["score","grade","compliance_rate","spf_rate","dkim_rate","total_messages","snapshot_date"]},"description":"One snapshot per day, oldest first."}},"required":["days","points"]},"SourceDetailResponse":{"type":"object","properties":{"source_ip":{"type":"string","description":"The sending IP, exactly as the reports carry it."},"total_messages":{"type":"integer","description":"Messages this IP sent as your domains."},"pass_count":{"type":"integer","description":"Of those, how many passed DMARC."},"fail_count":{"type":"integer","description":"Of those, how many failed DMARC."},"spf_fail_count":{"type":"integer","description":"Messages whose SPF did not produce an aligned pass. A source can pass DMARC on DKIM alone, so this can be non-zero while fail_count is 0."},"dkim_fail_count":{"type":"integer","description":"Messages whose DKIM did not produce an aligned pass. A source can pass DMARC on SPF alone, so this can be non-zero while fail_count is 0."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of this source's messages that passed DMARC."},"first_seen":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"last_seen":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"domains":{"type":"array","items":{"type":"string"},"description":"The domains this IP sent as. The list endpoint returns the same names as one comma-separated string."},"records":{"type":"array","items":{"type":"object","properties":{"report_id":{"type":"string","description":"The report this row came from; fetch it to see the row in context."},"domain":{"type":"string","description":"The domain the mail claimed to be from."},"count":{"type":"integer","description":"Messages this row accounts for."},"disposition":{"type":"string","description":"What the receiver did with them: none, quarantine or reject."},"dkim_result":{"type":"string","description":"Whether DKIM produced an aligned pass for DMARC: pass or fail."},"spf_result":{"type":"string","description":"Whether SPF produced an aligned pass for DMARC: pass or fail."},"header_from":{"type":"string","description":"The From domain the recipient saw."},"org_name":{"type":"string","description":"The organization that sent the report this row came from."},"date_begin":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"date_end":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"required":["report_id","domain","count","disposition","dkim_result","spf_result","header_from","org_name","date_begin","date_end"]},"description":"The most recent report rows this IP appears in, one per reporting period and domain."},"records_total":{"type":"integer","description":"All-time report rows for this IP; larger than the records array, which carries only the recent ones."},"timeline":{"type":"array","items":{"type":"object","properties":{"total_messages":{"type":"integer","description":"Messages reported for the day."},"compliant_messages":{"type":"integer","description":"Of those, how many passed DMARC."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of the day's messages that passed DMARC."},"failed_messages":{"type":"integer","description":"Of the day's messages, how many failed DMARC."},"reports":{"type":"integer","description":"Reports covering the day."},"date":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"}},"required":["total_messages","compliant_messages","compliance_rate","failed_messages","reports","date"]},"description":"One bucket per calendar day, oldest first."},"service_name":{"type":"string","description":"The sending service the IP was identified as, when it is recognised."},"service_icon":{"type":"string","description":"Icon slug for the identified service."},"service_slug":{"type":"string","description":"Stable identifier of the identified service."},"hostname":{"type":"string","description":"Reverse-DNS name of the IP, when it has one."},"geo":{"type":["null","object"],"properties":{"country":{"type":"string","description":"Two-letter country code for the IP."},"city":{"type":"string","description":"City the IP geolocates to."},"latitude":{"type":"number","description":"Latitude of the geolocated IP."},"longitude":{"type":"number","description":"Longitude of the geolocated IP."}},"description":"Where the IP geolocates to; absent when it cannot be placed.","required":["country","city","latitude","longitude"]},"enrichment":{"type":["null","object"],"properties":{"ip":{"type":"string","description":"The IP this record is about."},"country":{"type":"string","description":"Two-letter country code the IP geolocates to."},"city":{"type":"string","description":"City the IP geolocates to."},"latitude":{"type":"number","description":"Latitude of the geolocated IP."},"longitude":{"type":"number","description":"Longitude of the geolocated IP."},"isp":{"type":"string","description":"Network operator the IP belongs to."},"usage_type":{"type":"string","description":"What the address range is used for, such as a data centre or a residential network."},"country_code":{"type":"string","description":"Two-letter country code as the reputation lookup reported it."},"abuse_score":{"type":"integer","description":"Abuse confidence for the IP, 0 to 100; higher is worse. Negative when the IP was never scored."},"abuse_reports":{"type":"integer","description":"How many abuse reports the IP has attracted."},"abuse_categories":{"type":["null","array"],"items":{"type":"string"},"description":"What the IP was reported for, such as port scanning or spam."},"abuse_status":{"type":"string","description":"Abuse standing of the IP: clean, suspicious or malicious, and unknown when this record exists but the IP was never scored. The enrichment block itself is absent for an IP with no record at all."},"sender_name":{"type":"string","description":"The sending service the IP was identified as, when it is recognised."},"sender_slug":{"type":"string","description":"Stable identifier of the identified service."},"sender_icon":{"type":"string","description":"Icon slug for the identified service."},"hostname":{"type":"string","description":"Reverse-DNS name of the IP, when it has one."},"blocklist_listed":{"type":"integer","description":"Lists holding the IP. Read blocklist_verdict instead; that is where the standing is interpreted."},"blocklist_total":{"type":"integer","description":"Lists consulted. Read blocklist_verdict instead; it separates the lists that answered from those that did not."},"blocklist_results":{"type":["null","array"],"items":{"type":"object","properties":{"name":{"type":"string","description":"The blocklist this result came from."},"listed":{"type":"boolean","description":"Whether the list holds the IP. Prefer status, which also distinguishes a list that could not be reached."},"status":{"type":"string","description":"What this list said: listed, clean, or error when it could not be reached."},"reason":{"type":"string","description":"Why the list holds the IP, in its own words."},"category":{"type":"string","description":"What kind of abuse the list tracks, such as spam or malware."},"details":{"type":"string","description":"The evidence behind the listing, such as the covering address range or the list's response code."},"tier":{"type":"integer","description":"How much this list counts, as a rank: 0 unrecognised, 1 advisory and not about mail, 2 an ordinary mail blocklist, 3 one mailbox providers widely enforce, 4 hijacked address space or malware infrastructure."},"delist_url":{"type":"string","description":"The list operator's self-service delisting page, present only when the list is holding the IP and publishes one."}},"required":["name","listed"]},"description":"One result per blocklist consulted, each with a delisting link when there is one."},"abuse_last_reported":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"enriched_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"updated_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"description":"Everything the reputation lookup knows about the IP; absent until it has been looked up.","required":["ip","abuse_score","abuse_reports","abuse_status","blocklist_listed","blocklist_total","enriched_at","updated_at"]},"blocklist_verdict":{"type":"object","properties":{"severity":{"type":"string","description":"Blocklist standing, listed best to worst: unchecked and clean need nothing, degraded means the evidence was too thin to call it clean, advisory covers non-mail lists, and warning or critical is a live deliverability problem.","enum":["unchecked","clean","degraded","advisory","warning","critical"]},"listed":{"type":"integer","description":"Blocklists that answered and reported the IP as listed."},"clean":{"type":"integer","description":"Blocklists that answered and did not list the IP."},"errored":{"type":"integer","description":"Blocklists that refused or failed to answer, so they prove nothing either way."},"checked":{"type":"integer","description":"Blocklists that actually answered, listed plus clean. This is the denominator to quote, not total."},"total":{"type":"integer","description":"Blocklists consulted, including any that did not answer."},"score":{"type":"integer","description":"Weighted seriousness of the listings, 0 to 100. A list mailbox providers enforce counts for far more than an advisory one."},"names":{"type":["null","array"],"items":{"type":"string"},"description":"The lists the IP is on, most consequential first."},"top_list":{"type":"string","description":"The most consequential list the IP is on."},"top_reason":{"type":"string","description":"Why that list holds the IP."},"top_tier":{"type":"integer","description":"How much that list counts, as a rank: 0 unrecognised, 1 advisory and not about mail, 2 an ordinary mail blocklist, 3 one mailbox providers widely enforce, 4 hijacked address space or malware infrastructure."},"top_errored":{"type":"string","description":"The most consequential list that did not answer. It names which evidence is missing when the standing is degraded."},"has_malware_c2":{"type":"boolean","description":"True when a listing is for malware infrastructure rather than spam, which points at an active compromise."},"urgency":{"type":"string","description":"How soon to act: none, monitor, soon or now."},"summary":{"type":"string","description":"A ready-to-show sentence stating the blocklist standing."},"scope":{"type":"string","description":"Set to ingest when this verdict consulted the smaller set of lists checked as reports arrive, so its counts cover fewer lists. Absent for a full check."}},"description":"The blocklist standing of this IP, already interpreted: severity, weighted score and a sentence you can show.","required":["severity","listed","clean","errored","checked","total","score","has_malware_c2","urgency","summary"]},"spam_signals":{"type":["null","array"],"items":{"type":"object","properties":{"name":{"type":"string","description":"Identifier of the spam-filter rule that fired for this IP."},"score":{"type":"number","description":"How much the rule moved the spam score; a positive value counts against the sender."},"description":{"type":"string","description":"What the rule checks, in words."}},"required":["name","score"]},"description":"Spam-filter signals observed for this IP, when any were recorded."},"classification":{"type":"string","description":"Whether this source's mail should worry you: authenticated when SPF or DKIM passed, likely_forwarded when a failure looks like ordinary relaying, suspicious when a failing source also carries reputation red flags, and needs_review when the signals decide neither."},"classification_description":{"type":"string","description":"A ready-to-show sentence explaining the classification. Absent for an authenticated source, which needs none."}},"required":["source_ip","total_messages","pass_count","fail_count","spf_fail_count","dkim_fail_count","compliance_rate","first_seen","last_seen","domains","records","records_total","timeline","blocklist_verdict","classification"]},"SourcesListResponse":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"source_ip":{"type":"string","description":"The sending IP, exactly as the reports carry it."},"total_messages":{"type":"integer","description":"Messages this IP sent as your domains."},"pass_count":{"type":"integer","description":"Of those, how many passed DMARC."},"fail_count":{"type":"integer","description":"Of those, how many failed DMARC."},"spf_fail_count":{"type":"integer","description":"Messages whose SPF did not produce an aligned pass. A source can pass DMARC on DKIM alone, so this can be non-zero while fail_count is 0."},"dkim_fail_count":{"type":"integer","description":"Messages whose DKIM did not produce an aligned pass. A source can pass DMARC on SPF alone, so this can be non-zero while fail_count is 0."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of this source's messages that passed DMARC."},"first_seen":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"last_seen":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"domains":{"type":"string","description":"The domains this IP sent as, comma-separated. The single-source endpoint returns the same list as an array."},"service_name":{"type":"string","description":"The sending service the IP was identified as, when it is recognised."},"service_icon":{"type":"string","description":"Icon slug for the identified service."},"service_slug":{"type":"string","description":"Stable identifier of the identified service."},"abuse_status":{"type":"string","description":"Abuse standing of the IP: clean, suspicious or malicious. Absent when the IP has no reputation record at all, and unknown when it has one but was never scored."},"abuse_score":{"type":"integer","description":"Abuse confidence for the IP, 0 to 100; higher is worse. Negative when the IP has a reputation record but was never scored. Omitted when it is zero, which is a clean IP."},"reputation_reason":{"type":"string","description":"A ready-to-show sentence explaining abuse_status."},"blocklist_severity":{"type":"string","description":"Blocklist standing: unchecked, clean, degraded when the evidence was too thin to call it clean, advisory for non-mail lists, and warning or critical for a live deliverability problem.","enum":["unchecked","clean","degraded","advisory","warning","critical"]},"blocklist_summary":{"type":"string","description":"A ready-to-show sentence stating the blocklist standing."},"isp":{"type":"string","description":"Network operator the IP belongs to, when known."},"country":{"type":"string","description":"Two-letter country code for the IP, when known."},"hostname":{"type":"string","description":"Reverse-DNS name of the IP, when it has one."},"classification":{"type":"string","description":"Whether this source's mail should worry you: authenticated when SPF or DKIM passed, likely_forwarded when a failure looks like ordinary relaying, suspicious when a failing source also carries reputation red flags, and needs_review when the signals decide neither."}},"required":["source_ip","total_messages","pass_count","fail_count","spf_fail_count","dkim_fail_count","compliance_rate","first_seen","last_seen","domains","blocklist_severity","classification"]},"description":"The requested page. Never null; an empty page is []."},"total":{"type":"integer","description":"Sources matching the filter, before paging."},"limit":{"type":"integer","description":"The page size actually applied."},"offset":{"type":"integer","description":"The offset actually applied."},"has_any":{"type":"boolean","description":"True when the organization holds any report at all, ignoring filters; use it for empty states."}},"required":["sources","total","limit","offset","has_any"]},"Statistics":{"type":"object","properties":{"total_reports":{"type":"integer","description":"Aggregate reports held."},"total_messages":{"type":"integer","description":"Messages those reports account for."},"compliant_messages":{"type":"integer","description":"Of those, how many passed DMARC."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of messages that passed DMARC."},"unique_source_ips":{"type":"integer","description":"Distinct sending IPs seen."},"unique_domains":{"type":"integer","description":"Distinct domains the reports cover."},"has_data":{"type":"boolean","description":"False until the first report arrives."}},"required":["total_reports","total_messages","compliant_messages","compliance_rate","unique_source_ips","unique_domains","has_data"]},"TopSourcesResponse":{"type":"object","properties":{"sources":{"type":"array","items":{"type":"object","properties":{"source_ip":{"type":"string","description":"The sending IP, exactly as the reports carry it."},"count":{"type":"integer","description":"Messages this IP sent as your domains."},"pass":{"type":"integer","description":"Of those, how many passed DMARC."},"fail":{"type":"integer","description":"Of those, how many failed DMARC."},"spf_fail":{"type":"integer","description":"Messages whose SPF did not produce an aligned pass. A source can pass DMARC on DKIM alone, so this can be non-zero while fail is 0."},"dkim_fail":{"type":"integer","description":"Messages whose DKIM did not produce an aligned pass. A source can pass DMARC on SPF alone, so this can be non-zero while fail is 0."},"service_name":{"type":"string","description":"The sending service the IP was identified as, when it is recognised."},"service_icon":{"type":"string","description":"Icon slug for the identified service."},"service_slug":{"type":"string","description":"Stable identifier of the identified service."}},"required":["source_ip","count","pass","fail","spf_fail","dkim_fail"]},"description":"The requested page, highest volume first. Never null; an empty page is []."},"total":{"type":"integer","description":"Sending IPs matching the filter, before paging."},"limit":{"type":"integer","description":"The page size actually applied."},"offset":{"type":"integer","description":"The offset actually applied."}},"required":["sources","total","limit","offset"]},"TrendResponse":{"type":"object","properties":{"days":{"type":"integer","description":"Length of the window in days, as applied."},"points":{"type":"array","items":{"type":"object","properties":{"total_messages":{"type":"integer","description":"Messages reported for the day."},"compliant_messages":{"type":"integer","description":"Of those, how many passed DMARC."},"compliance_rate":{"type":"number","description":"Percentage from 0 to 100 of the day's messages that passed DMARC."},"failed_messages":{"type":"integer","description":"Of the day's messages, how many failed DMARC."},"reports":{"type":"integer","description":"Reports covering the day."},"date":{"type":["string","null"],"description":"Calendar day, YYYY-MM-DD (UTC); null when absent.","format":"date"}},"required":["total_messages","compliant_messages","compliance_rate","failed_messages","reports","date"]},"description":"One point per day, oldest first."}},"required":["days","points"]},"UsageResponse":{"type":"object","properties":{"days":{"type":"integer","description":"Length of the charted window in days, as applied."},"bucket":{"type":"string","description":"How wide each history point is: hour for a short window, day for a longer one."},"window":{"type":"object","properties":{"used":{"type":"integer","description":"Requests counted so far in the current hour."},"remaining":{"type":"integer","description":"Requests left in the current hour, or -1 when the organization has no cap."},"limit":{"type":"integer","description":"Requests allowed per hour, or 0 when the organization has no cap."},"reset_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"description":"The hour being metered right now.","required":["used","remaining","limit","reset_at"]},"verdict":{"type":"object","properties":{"state":{"type":"string","description":"no_traffic before the first request, capped when requests were refused anywhere in the charted window, near_cap when the hour is nearly spent, unlimited when the organization has no cap, ok otherwise."},"summary":{"type":"string","description":"A ready-to-show sentence for the state."}},"description":"How close the organization is to its quota, already decided.","required":["state","summary"]},"points":{"type":["null","array"],"items":{"type":"object","properties":{"ts":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"},"allowed":{"type":"integer","description":"Requests served in this bucket."},"rejected":{"type":"integer","description":"Requests refused in this bucket because a limit was already spent."}},"required":["ts","allowed","rejected"]},"description":"Request history over the window, oldest first, one point per bucket."},"keys":{"type":["null","array"],"items":{"type":"object","properties":{"id":{"type":"string","description":"Identifier, Crockford Base32. Opaque; never parse it as a number."},"name":{"type":"string","description":"The name the key was created with."},"prefix":{"type":"string","description":"The key's leading characters, enough to recognise it without revealing it."},"allowed":{"type":"integer","description":"Requests this key had served over the window."},"rejected":{"type":"integer","description":"Requests this key had refused over the window."},"revoked":{"type":"boolean","description":"True once the key was revoked; its past usage still shows here."},"last_used_at":{"type":["string","null"],"description":"RFC 3339 UTC instant with whole seconds, e.g. 2026-09-15T16:03:43Z; null when absent.","format":"date-time"}},"required":["id","name","prefix","allowed","rejected","revoked","last_used_at"]},"description":"Each key's share of the window's requests."},"api_base_url":{"type":"string","description":"The base URL this deployment serves the API on."}},"required":["days","bucket","window","verdict","points","keys","api_base_url"]}},"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key (prefix \"dg_\") sent as `Authorization: Bearer <key>`."}}},"security":[{"apiKey":[]}],"x-dmarcguard":{"facts":{"pro_requests_per_hour":1000,"heavy_requests_per_hour":120,"pre_auth_requests_per_minute":300,"window_seconds":3600,"max_page_limit":200,"key_prefix":"dg_","rate_limit_headers":["X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset","RateLimit-Policy","RateLimit","Retry-After"],"read_scope":"read","full_scope":"full","rate_limited_code":"rate_limited","quota_exceeded_code":"quota_exceeded","error_schema":"ErrorBody"},"common_errors":{"401":"Missing, malformed, revoked, expired or not-entitled API key.","403":"The key's scope or its owner's role does not allow this operation.","429":"Hourly quota or an abuse limit exceeded; the body's code says which. Honour Retry-After.","500":"Unexpected server error; retry with backoff.","503":"A transient failure resolving the organization or reading storage; retry after a short pause."}}}