{"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, a trend sparkline and the latest APRF placement data of each reporting mailbox provider 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, forensic_state and aprf_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"]},"aprf":{"type":"object","properties":{"status":{"type":"string","description":"How the record stands: ok when exactly one APRF record is published, warning when more than one is (providers ignore all of them), and unknown when none was found or it could not be read. Never error: APRF is opt-in."},"details":{"type":"string","description":"A ready-to-show sentence explaining the status."}},"description":"The domain's APRF performance-reporting record.","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","aprf"]},"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."},"aprf_placement":{"type":"array","items":{"type":"object","properties":{"day":{"type":["string","null"],"description":"The UTC day the report covers, YYYY-MM-DD.","format":"date"},"source":{"type":"string","description":"The reporting mailbox provider, by the name it reports under."},"dkim_domain":{"type":"string","description":"The DKIM signing domain (d=) the report covers; a leading *. means the provider rolled its subdomains up into one report."},"dkim_selector":{"type":"string","description":"The DKIM selector (s=) the report covers; * when the provider rolled every selector up into one report."},"version":{"type":"string","description":"The report version the provider declared. A change means the provider's categories may count different actions than before, so do not compare counts across versions."},"sdi_used":{"type":"string","description":"Whether the provider applied your signer-defined identifier (SDI) segmentation, as it reported it: N/A when it did not use the one you declared, N/F when it found no valid one; empty when not reported."},"extra_info":{"type":"string","description":"The provider's own notes on its report, where it explains what its categories count; empty when it sent none."},"segment":{"type":["null","array"],"items":{"type":"string"},"description":"The signer-defined identifier values this row counts, in order; an empty list when the report is not segmented."},"classification":{"type":["null","array"],"items":{"type":"object","properties":{"category":{"type":"string","description":"The category, exactly as the provider named it."},"value":{"type":"integer","description":"The count as reported; a provider may round it up to a bucket, so read it as an upper bound."}},"required":["category","value"]},"description":"Message counts by placement (for example inbox or unwanted), as reported: a provider may round each up to a bucket, so read it as an upper bound."},"engagement":{"type":["null","array"],"items":{"type":"object","properties":{"category":{"type":"string","description":"The category, exactly as the provider named it."},"value":{"type":"integer","description":"The count as reported; a provider may round it up to a bucket, so read it as an upper bound."}},"required":["category","value"]},"description":"Engagement-action counts (for example positive, neutral or negative), as reported: a provider may round each up to a bucket, so read it as an upper bound."}},"required":["day","source","dkim_domain","dkim_selector","version","sdi_used","extra_info","segment","classification","engagement"]},"description":"APRF placement data mailbox providers sent for this domain: for each provider, DKIM domain and DKIM selector, the rows of its most recent report within the last 7 UTC days (today included), one row per segment, or an empty list when none. Rows are independent measurements: a *.domain roll-up row and a * selector row cover the same mail as the exact rows, and different providers count different mail, so never add rows together. Counts are as reported: providers may round them up to buckets, so read each as an upper bound."},"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","aprf_placement","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"]},"aprf_enabled":{"type":"boolean","description":"Whether APRF performance reporting is switched on for this domain."},"aprf_state":{"type":"string","description":"State of the domain's APRF 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","aprf_enabled","aprf_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."}}}