---
title: "Enable hosted DMARC"
method: POST
path: "/domains/{id}/hosted-dmarc"
tags: ["Domain"]
---

# Enable hosted DMARC

`POST /domains/{id}/hosted-dmarc`

## Request

- Base URL: `https://api.palisade.email`
- URL: `https://api.palisade.email/domains/{id}/hosted-dmarc`
- Auth: one of:
  - HTTP bearer
  - HTTP basic

Rebuilds the Palisade-hosted DMARC record from the domain's current policy, publishes it, and returns the CNAME to point _dmarc at. Call this before showing anyone the CNAME: the delegation target is otherwise whatever was written when the domain was created, so a domain whose owner tightened their own DMARC policy since then would be switched onto a weaker one. Nothing is written to the database unless the publish succeeds, so a failure is safe to retry, and calling it on a domain that is already hosted changes nothing and answers hosted_dmarc_active: true.

## Path parameters

- `id` string, uuid, required

## Headers

- `Palisade-Version` '2025-04-30' | 'unstable', required

## Response `200`

Success

- EnableHostedDmarcResponse
  - `hosted_dmarc_active` boolean, required: False until the hosted CNAME resolves in live DNS. Publish "record" and Palisade flips this to true on its own; true already on a domain that was hosted before the call.
  - `record` object, required: The CNAME to publish at _dmarc.<domain>, rebuilt from the domain's current DMARC policy. Delete any DMARC TXT record still sitting at the same host, which conflicts with it.
    - `purpose` 'dmarc' | 'spf' | 'dkim' | 'mta_sts' | 'mta_sts_policy' | 'tls_rpt' | 'bimi', required
    - `type` 'TXT' | 'CNAME' | 'NS', required
    - `host` string, required: Fully qualified record name.
    - `relative_host` string, required: Record name relative to the domain apex; "@" designates the apex itself.
    - `value` string, required
    - `ttl` integer, required: Recommended TTL in seconds.
    - `action` 'create' | 'replace' | 'delete', required: How to publish this record, derived from what is currently live in DNS. "create" means nothing serving this purpose exists at the host yet; "replace" means a record for this purpose already occupies the host and must be overwritten with this value; "delete" means the record must be removed. When already_published is true the record is already correct and "replace" would be a no-op, so use already_published to decide whether to act at all.
    - `required` boolean, required: False for records that improve an enabled feature but are not needed for it to function.
    - `status` 'pending' | 'verifying' | 'verified' | 'drifted', required
    - `already_published` boolean, nullable, required: True when live DNS already serves exactly this value, so the record can be skipped. False when it is missing or serving something else. Null when the live lookup could not be completed, in which case "action" falls back to a static, type-derived value and should be treated as a best-effort hint.
  - `domain` Domain, required
    - `id` string, uuid, required
    - `url` string, required
    - `dmarc_policy` string, required
    - `dmarc_options` object, required
      - `additional_emails` string[], required
      - `legacy_ruf_emails` string[]
      - `policy` 'none' | 'quarantine' | 'reject', required
      - `pct` number, required
      - `sp` 'none' | 'quarantine' | 'reject' | 'same', required
      - `strict_spf` boolean, required
      - `strict_dkim` boolean, required
    - `spf_options` object, required
      - `force_flattening` boolean, required
      - `enabled` boolean, required
      - `mechanism_descriptions` object
    - `managed_dns_status` object, required
      - `dmarc` 'unknown' | 'syncing' | 'ready' | 'error', required
      - `spf` 'unknown' | 'syncing' | 'ready' | 'error', required
      - `dkim` 'unknown' | 'syncing' | 'ready' | 'error', required
      - `bimi` 'unknown' | 'syncing' | 'ready' | 'error', required
      - `mta_sts` 'unknown' | 'syncing' | 'ready' | 'error', required
    - `managed_dns_status_since` object, required: When each managed record last entered the "syncing" status. For dmarc, falls back to dmarc_setup_at when the transition predates tracking; other records are null when they have never synced or the transition predates tracking. Useful for estimating how long a "syncing" record has been waiting on DNS propagation.
      - `dmarc` string, date-time, nullable, required
      - `spf` string, date-time, nullable, required
      - `dkim` string, date-time, nullable, required
      - `bimi` string, date-time, nullable, required
      - `mta_sts` string, date-time, nullable, required
    - `monitoring_status` 'drifted' | 'none' | 'pending' | 'setup' | 'failed', required
    - `monitoring_failure_reason` 'nameservers_unresolvable' | 'dmarc_record_missing' | 'dmarc_record_added_as_txt_not_cname' | 'dmarc_record_on_parent_only' | 'dmarc_record_missing_palisade_rua' | 'dmarc_record_invalid' | 'dmarc_multiple_records', nullable, required
    - `monitoring_failure_detail` DmarcSetupFailureDetail, nullable, required
      - `observed` string
      - `expected_rua` string
      - `from_hostname` string
      - `record_count` number
    - `monitoring_email` string, required
    - `open_tasks_count` number, required: Open tickets on this domain. Opportunity tickets such as set_up_bimi are not counted, and `expand[]=tasks` leaves them out to match.
    - `highest_open_task_priority` integer, nullable, required: Priority of the most urgent open task on this domain, on the same inverted scale as a task's own priority: 0 critical, 1 high, 2 medium, 3 low. Null when the domain has no open tasks. Sort on "severity" to order domains by it.
    - `score` number, required
    - `score_band` 'critical' | 'bad' | 'good' | 'great', required: Risk band the score falls in: critical, bad, good, or great. The same banding the dashboard shows, so clients do not have to reimplement the thresholds.
    - `is_parked` boolean, required
    - `is_dmarc_managed` boolean, required
    - `is_spf_managed` boolean, required
    - `is_dkim_managed` boolean, required
    - `is_bimi_managed` boolean, required
    - `is_mta_sts_managed` boolean, required
    - `bimi_options` object, required
      - `enabled` boolean, required
      - `logo` string, uri, nullable, required
      - `certificate` string, uri, nullable, required
    - `bimi_error_reason` 'setup_timeout' | 'drift' | 'certificate_invalid' | 'record_conflict' | 'delegation_missing' | 'publish_failed' | 'dkim_key_error' | 'check_failed', nullable, required: Why managed_dns_status.bimi is "error". "setup_timeout" means the customer CNAME was never detected within the verification window (setup was never completed); "drift" means a previously verified CNAME stopped pointing at Palisade; "record_conflict" means default._bimi holds conflicting records — see dns_conflicts. Null when there is no error or the error predates reason tracking.
    - `dmarc_error_reason` 'setup_timeout' | 'drift' | 'certificate_invalid' | 'record_conflict' | 'delegation_missing' | 'publish_failed' | 'dkim_key_error' | 'check_failed', nullable, required: Why managed_dns_status.dmarc is "error". "record_conflict" means _dmarc holds records that conflict with each other — a CNAME coexisting with other data, or more than one DMARC record — and the domain's DMARC policy is not reliably enforced. See dns_conflicts for the offending records. Null when there is no error or the error predates reason tracking.
    - `spf_error_reason` 'setup_timeout' | 'drift' | 'certificate_invalid' | 'record_conflict' | 'delegation_missing' | 'publish_failed' | 'dkim_key_error' | 'check_failed', nullable, required: Why managed_dns_status.spf is "error". See dmarc_error_reason for the meaning of each value.
    - `dkim_error_reason` 'setup_timeout' | 'drift' | 'certificate_invalid' | 'record_conflict' | 'delegation_missing' | 'publish_failed' | 'dkim_key_error' | 'check_failed', nullable, required: Why managed_dns_status.dkim is "error". See dmarc_error_reason for the meaning of each value.
    - `mta_sts_error_reason` 'setup_timeout' | 'drift' | 'certificate_invalid' | 'record_conflict' | 'delegation_missing' | 'publish_failed' | 'dkim_key_error' | 'check_failed', nullable, required: Why managed_dns_status.mta_sts is "error". See dmarc_error_reason for the meaning of each value.
    - `dns_conflicts` DnsConflict[], required: Unresolved record conflicts observed at labels Palisade manages or monitors for this domain. Empty for a healthy domain. Each entry quotes the offending records verbatim along with what every authoritative nameserver and public resolver returned, so it can be acted on without a second investigation.
      - `label` string, required: The exact DNS name the conflicting records sit at.
      - `protocol` 'dmarc' | 'spf' | 'bimi' | 'mta_sts' | 'dkim', required
      - `kind` 'cname_with_other_data' | 'multiple_dmarc_records' | 'multiple_spf_records' | 'multiple_bimi_records' | 'cname_target_unresolvable' | 'cname_chain_too_deep' | 'authoritative_disagreement' | 'resolver_divergence' | 'managed_delegation_missing', required
      - `summary` string, required
      - `rfc` string, required: The specification clause the zone violates, e.g. "RFC 1034 §3.6.2".
      - `consequence` string, required: What this actually does to mail for this domain, in plain language.
      - `remediation` string, required: Which record to delete and which to keep, stated concretely enough to act on.
      - `conflicting_records` string[], required: The offending record values, verbatim as published.
      - `authoritative_nameservers` object[], required: Every authoritative nameserver queried and exactly what it returned.
        - `nameserver` string, required
        - `ip` string, required
        - `cname` string[], required
        - `txt` string[], required
        - `answered` boolean, required
        - `timed_out` boolean, required
      - `public_resolvers` object[], required: Independent public resolvers queried and exactly what each returned.
        - `resolver` string, required
        - `ip` string, required
        - `cname` string[], required
        - `txt` string[], required
        - `answered` boolean, required
        - `timed_out` boolean, required
      - `occurrence_count` number, required: How many detection runs have observed this conflict.
      - `first_detected_at` string, date-time, required
      - `last_detected_at` string, date-time, required
    - `bimi_cname_target` string, required: The host a customer CNAMEs default._bimi.{domain} to for Palisade-served BIMI.
    - `bimi_txt_value` string, nullable, required: The fully-formed BIMI TXT value, populated whenever bimi_options.logo is set (via asset ingestion or a direct bimi_options update). The a= tag is included only when a certificate URL is also set. Customers who prefer not to delegate via CNAME can publish this value at default._bimi.{domain} themselves.
    - `target_spf` string, nullable, required
    - `last_14_days_volume` number, required
    - `last_14_days_dmarc_pass_pct` number, required
    - `last_14_days_spf_pass_pct` number, required
    - `last_14_days_dkim_pass_pct` number, required
    - `group_id` string, uuid, nullable, required
    - `registrar` string, nullable, required
    - `group` Group
      - `id` string, uuid, required
      - `name` string, required
      - `external_customer_id` string, nullable, required
      - `external_billing_entity_id` string, nullable, required
      - `domains` object
        - `count` number, required
        - `enforcement_pct` number, required
        - `last_14_days_volume` number, required
        - `last_14_days_dmarc_pass_pct` number, required
        - `average_score` number, required
      - `created_at` string, date-time, required
      - `updated_at` string, date-time, required
    - `tasks` Task[]: Returns only tasks with status "open"
      - `id` string, uuid, required
      - `domain_id` string, uuid, required
      - `domain` object
        - `id` string, uuid, required
        - `url` string, required
        - `dmarc_policy` string, required
        - `score` number, required: The domain audit score as it stands now.
      - `score_gain` integer: How much of the domain score completing this ticket wins back, by the same scoring the domain overview shows. 0 for ticket types the score does not measure. Present only with expand[]=domain, since it depends on the domain as it stands now.
      - `sending_domain` string, nullable, required
      - `source_id` string, uuid, nullable, required
      - `source` object
        - `id` string, uuid, required
        - `name` string, required
        - `homepage` string, uri, nullable, required
        - `documentation` object, required
          - `spf` string, nullable
          - `dkim` string, nullable
          - `combined` string, nullable
        - `category` 'SENDING_TOOL' | 'MAILBOX_PROVIDER' | 'VPS_PROVIDER' | 'SECURITY_PROVIDER' | 'EMAIL_RELAY', required
        - `forwarding_type` 'NOT_FORWARDER' | 'FORWARDER' | 'FORWARDER_ONLY', required
        - `spf_support` 'NONE' | 'RARE' | 'FULL', required
        - `spf_supported_methods` string[], required
        - `spf_support_options` object, required
          - `direct` object, nullable, required
            - `values` string[], required
            - `requires_advanced_config` boolean, nullable, required
          - `subdomain` object, nullable, required
            - `is_managed` boolean, required
            - `requires_user_selection` boolean, required
            - `path` string, nullable, required
            - `value` string, nullable, required
            - `dns_records` object[], required
              - …
        - `dkim_support` 'NONE' | 'RARE' | 'FULL', required
        - `dkim_discovery_selectors` string[], required
      - `instructions` object, nullable: BETA: Only expandable for GET /tasks/:id. Can contain instructions sometimes.
        - `steps` string[], required
        - `dns_records` object[], required
          - `type` 'TXT' | 'CNAME', required
          - `name` string
          - `value` string
      - `source_alias` string, nullable, required
      - `priority` number, required
      - `sequence` number, required: Where the task sits in the email-authentication rollout, lower first: 10 DMARC setup, 20 foundation records, 30 SPF/DKIM alignment, 40 cleanup, 50 enforcement, 60 hardening. Separate from priority, which is urgency; sequence orders tasks of equal priority, since an alignment task cannot be worked before DMARC reporting exists to show the failure.
      - `status` 'open' | 'snoozed' | 'canceled' | 'dismissed' | 'pending' | 'done', required
      - `type` 'cleanup_spf_record' | 'confirm_source' | 'lockdown_domain' | 'reduce_spf_lookups' | 'set_up_dmarc_record' | 'source_failing_dkim' | 'source_failing_spf' | 'set_up_mx_record' | 'set_up_spf_record' | 'update_dmarc_record' | 'update_ttl' | 'set_up_mta_sts' | 'set_up_tls_rpt' | 'resolve_dns_conflict' | 'review_unused_sender' | 'cleanup_sender_records' | 'fix_mx_certificate' | 'renew_vmc_certificate' | 'dkim_selector_not_imported' | 'set_up_bimi', required
      - `description` string, required
      - `snoozed_until` string, date-time, nullable, required
      - `marked_pending_at` string, date-time, nullable, required
      - `created_at` string, date-time, required
      - `updated_at` string, date-time, required
      - `resolved_at` string, date-time, nullable, required
      - `resolution_reason` 'verified' | 'manual', nullable, required: Why a done ticket closed. 'verified': Palisade's own check found the problem gone. 'manual': a person or an API caller marked it done and nothing was checked. Null on a ticket that is not done, and on one closed before the reason was first recorded. More values may be added, so treat an unknown one as unspecified.
    - `dmarc_setup_at` string, date-time, nullable, required
    - `dmarc_verified_at` string, date-time, nullable, required: When the domain first reached the "setup" monitoring status and DMARC report collection began.
    - `monitoring_since` string, nullable, required: The first day (yyyy-MM-dd) a DMARC aggregate report was actually received for this domain. The honest denominator for any activity claim: nothing before this day was observable, so a domain monitored for a week can never truthfully read as quiet for ninety days. Null until the first report arrives.
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required

---

[API](https://skmtc.dev/palisade/apis/palisade-api.md) · [All operations](https://skmtc.dev/palisade/apis/palisade-api/llms.txt) · [OpenAPI document](https://skmtc.dev/palisade/apis/palisade-api/revisions/37b4ae5a7766?raw)
