Zones

Add, list, check and delete zones (domains), in delegated or external DNS mode, and merge a zone into its parent.

On this page

A zone is a domain added to OpenWAF. In delegated mode OpenWAF serves its DNS once the registrar delegates it to the zone's name servers; in external mode the DNS stays where it is and each proxied name points at the edge with a CNAME to edge.openwaf.dev. A zone stays pending until its delegation, or the ownership TXT record of an external zone, is verified.

OperationEndpoint
List zonesGET /v1/orgs/{org}/zones
Create zonePOST /v1/orgs/{org}/zones
Create zones in bulkPOST /v1/orgs/{org}/zones/bulk
Get zoneGET /v1/orgs/{org}/zones/{zone}
Delete zoneDELETE /v1/orgs/{org}/zones/{zone}
Get delegation statusGET /v1/orgs/{org}/zones/{zone}/delegation
Check delegation nowPOST /v1/orgs/{org}/zones/{zone}/delegation/check
Check external zone namesGET /v1/orgs/{org}/zones/{zone}/hosts
Merge zone into its parentPOST /v1/orgs/{org}/zones/{zone}/merge
Unmerge zonePOST /v1/orgs/{org}/zones/{zone}/unmerge

List zones

GET /v1/orgs/{org}/zones

Lists the zones of the organization. Zones merged into another one are hidden unless include=merged.

Required role: readonly or higher. A token limited to a zone only sees that zone; one limited to a client only sees its client's zones.

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
includequerystringNomerged also lists the zones merged into another one (hidden by default). One of merged.

Response

  • 200: Returns items, an array of Zone.
FieldTypeDescription
items[].idstring (uuid)Id.
items[].org_idstring (uuid)Organization id.
items[].namestringName.
items[].statusstringOne of pending, active, moved, disabled.
items[].dns_modestringdelegated: OpenWAF name servers. external: the customer keeps their DNS and points their sites at the edge (CNAME/A). One of delegated, external. Default delegated.
items[].ns_pairarray of stringName servers the delegated zones use. Exactly 2 items.
items[].delegation_checked_atstring (date-time)Last delegation check.
items[].delegationCheckResult or VerifyResultLast check: CheckResult (delegated) or VerifyResult (external).
items[].activated_atstring (date-time)When the zone became active.
items[].moved_atstring (date-time)When the delegation stopped pointing to OpenWAF.
items[].disabled_atstring (date-time)When the zone was disabled.
items[].verificationZoneVerification
items[].cname_targetstringExternal zones only: CNAME target for each site.
items[].edge_addressesEdgeAddresses
items[].config_versionintegerCurrent configuration version (also in ETag).
items[].proxied_namesintegerNames with a proxied record.
items[].merged_intostring (uuid)Merged zones only (status disabled): the parent zone.
items[].merged_atstring (date-time)Merged zones only; they are purged after 14 days.
items[].created_atstring (date-time)Creation time.
items[].updated_atstring (date-time)Last change.
items[].client_idstring (uuid) or nullClient of the organization the zone belongs to; null = none.

Example

curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
200
{
  "items": [
    {
      "id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
      "org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
      "name": "example.com",
      "status": "active",
      "dns_mode": "delegated",
      "ns_pair": [
        "ns1.example-dns.net",
        "ns2.example-dns.net"
      ],
      "delegation_checked_at": "2026-10-27T09:00:00Z",
      "activated_at": "2026-03-02T11:40:00Z",
      "config_version": 12,
      "proxied_names": 3,
      "client_id": null,
      "created_at": "2026-03-02T10:20:00Z",
      "updated_at": "2026-10-27T09:00:00Z"
    }
  ]
}

Create zone

POST /v1/orgs/{org}/zones

Adds a zone and creates version 1 of its configuration (the change reason defaults to the creation of the zone).

  • delegated (default): OpenWAF serves the zone's DNS. The zone stays pending until the registrar delegates it to the zone's name servers (ns_pair). By default the records found on the public DNS are imported right away (import).
  • external: the DNS stays with its current provider. Point each proxied name at the edge with a CNAME to cname_target (or A/AAAA records to edge_addresses at the apex) and publish the TXT record in verification to prove ownership; once verified, the zone becomes active. External zones only accept proxied A/AAAA/CNAME records.

A domain that is a parent or a subdomain of a zone of another organization (or, for a client-limited credential, of another client) is refused. A zone that stays pending too long is deleted automatically.

Required role: tech or higher. Not available to tokens limited to a zone.

Change controls: dry_run · If-Match (optional) · X-Change-Reason (optional)

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
X-Change-ReasonheaderstringNoReason of the change (1-500 UTF-8 characters, no control characters); defaults to the creation of the zone. 1 to 500 characters.
If-MatchheaderstringNoExpected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict.
dry_runquerystringNo1/true simulates the change: answers 200 with the plan (Applied) and saves nothing. 0/false accepted; any other value: 400. One of 1, true, 0, false.

Request body

Content type application/json.

FieldTypeRequiredDescription
namestringYesName.
dns_modestringNodelegated: OpenWAF name servers. external: the customer keeps their DNS and points their sites at the edge (CNAME/A). One of delegated, external. Default delegated.
client_idstring (uuid)NoAssigns the zone to a client. With a client-limited credential, the zone always belongs to that client.
importbooleanNoDelegated zones: create the records found on the public DNS right after the zone. Default true.

Response

  • 200: With dry_run=1, the plan of the change (Applied); nothing is saved.
  • 201: Created. Returns CreatedZone.
FieldTypeDescription
idstring (uuid)Id.
org_idstring (uuid)Organization id.
namestringName.
statusstringOne of pending, active, moved, disabled.
dns_modestringdelegated: OpenWAF name servers. external: the customer keeps their DNS and points their sites at the edge (CNAME/A). One of delegated, external. Default delegated.
ns_pairarray of stringName servers the delegated zones use. Exactly 2 items.
delegation_checked_atstring (date-time)Last delegation check.
delegationCheckResult or VerifyResultLast check: CheckResult (delegated) or VerifyResult (external).
activated_atstring (date-time)When the zone became active.
moved_atstring (date-time)When the delegation stopped pointing to OpenWAF.
disabled_atstring (date-time)When the zone was disabled.
verificationZoneVerification
cname_targetstringExternal zones only: CNAME target for each site.
edge_addressesEdgeAddresses
config_versionintegerCurrent configuration version (also in ETag).
proxied_namesintegerNames with a proxied record.
merged_intostring (uuid)Merged zones only (status disabled): the parent zone.
merged_atstring (date-time)Merged zones only; they are purged after 14 days.
created_atstring (date-time)Creation time.
updated_atstring (date-time)Last change.
client_idstring (uuid) or nullClient of the organization the zone belongs to; null = none.
detected_recordsintegerRecords found on the public DNS.
imported_recordsintegerRecords created in the zone.
import_warningsarray of stringWarnings of the import.

Response headers: ETag, X-Config-Version.

Errors

StatusDescription
400Malformed request (bad_request).
403Not allowed (forbidden) or the organization reached its zone limit (zone_limit).
409The domain is already added (zone_exists) or is a parent or subdomain of a zone of another organization or another client (zone_overlaps).
412version_conflict: If-Match does not match the current version.
422Validation error (invalid) or missing or invalid reason (reason_required).
503Delegated zones are not available right now (unavailable): only external zones are accepted.

Example

curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Add example.com" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "example.com",
    "dns_mode": "delegated"
  }'
201
{
  "id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  "org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
  "name": "example.com",
  "status": "pending",
  "dns_mode": "delegated",
  "ns_pair": [
    "ns1.example-dns.net",
    "ns2.example-dns.net"
  ],
  "delegation_checked_at": "2026-10-27T09:00:00Z",
  "activated_at": null,
  "config_version": 1,
  "proxied_names": 2,
  "client_id": null,
  "created_at": "2026-03-02T10:20:00Z",
  "updated_at": "2026-10-27T09:00:00Z",
  "detected_records": 6,
  "imported_records": 6,
  "import_warnings": []
}

Create zones in bulk

POST /v1/orgs/{org}/zones/bulk

Adds up to 1000 zones from a CSV body: one domain per line in the first column and, optionally, a client name in the second (clients that do not exist are created). A header line domain or name is optional and # starts a comment.

Each line is independent and follows the rules of Create zone; the answer reports the result of every line. dry_run is not available. Public records are imported only for uploads of up to 50 domains.

Required role: tech or higher. Not available to tokens limited to a zone.

Change controls: X-Change-Reason (optional)

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
X-Change-ReasonheaderstringNoReason of the change (1-500 UTF-8 characters, no control characters); defaults to the creation of the zone. 1 to 500 characters.
importquerybooleanNoImport the public records of each delegated zone (only for uploads of up to 50 domains). Default true.

Request body

Content type text/csv.

Response

  • 200: Result per line. Returns an object with the fields below.
FieldTypeDescription
createdinteger
failedinteger
resultsarray of object
results[].lineinteger
results[].namestringName.
results[].zoneCreatedZone
results[].errorstring
results[].codestringError code of the line. One of invalid, zone_exists, zone_overlaps, zone_limit, forbidden, not_found.
results[].clientstringClient of the line (second column).
clients_createdarray of stringClients created from the second column.

Errors

StatusDescription
400Invalid CSV, or dry_run (not available for bulk creation).
422Validation error (invalid) or missing or invalid reason (reason_required).

Example

curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/bulk" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Onboard new domains" \
  -H "Content-Type: text/csv" \
  --data-binary $'domain,client\nexample.com,Acme Retail\nexample.org\n'
200
{
  "created": 0,
  "failed": 0,
  "results": [
    {
      "line": 0,
      "name": "example.com",
      "zone": {
        "id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
        "org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
        "name": "example.com",
        "status": "pending",
        "dns_mode": "delegated",
        "ns_pair": [
          "ns1.example-dns.net",
          "ns2.example-dns.net"
        ],
        "delegation_checked_at": "2026-10-27T09:00:00Z",
        "activated_at": null,
        "config_version": 1,
        "proxied_names": 2,
        "client_id": null,
        "created_at": "2026-03-02T10:20:00Z",
        "updated_at": "2026-10-27T09:00:00Z",
        "detected_records": 6,
        "imported_records": 6,
        "import_warnings": []
      },
      "error": "",
      "code": "invalid",
      "client": "example"
    }
  ],
  "clients_created": []
}

Get zone

GET /v1/orgs/{org}/zones/{zone}

Returns one zone. The ETag header carries its current configuration version.

Required role: readonly or higher.

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).

Response

FieldTypeDescription
idstring (uuid)Id.
org_idstring (uuid)Organization id.
namestringName.
statusstringOne of pending, active, moved, disabled.
dns_modestringdelegated: OpenWAF name servers. external: the customer keeps their DNS and points their sites at the edge (CNAME/A). One of delegated, external. Default delegated.
ns_pairarray of stringName servers the delegated zones use. Exactly 2 items.
delegation_checked_atstring (date-time)Last delegation check.
delegationCheckResult or VerifyResultLast check: CheckResult (delegated) or VerifyResult (external).
activated_atstring (date-time)When the zone became active.
moved_atstring (date-time)When the delegation stopped pointing to OpenWAF.
disabled_atstring (date-time)When the zone was disabled.
verificationZoneVerification
cname_targetstringExternal zones only: CNAME target for each site.
edge_addressesEdgeAddresses
config_versionintegerCurrent configuration version (also in ETag).
proxied_namesintegerNames with a proxied record.
merged_intostring (uuid)Merged zones only (status disabled): the parent zone.
merged_atstring (date-time)Merged zones only; they are purged after 14 days.
created_atstring (date-time)Creation time.
updated_atstring (date-time)Last change.
client_idstring (uuid) or nullClient of the organization the zone belongs to; null = none.

Response headers: ETag.

Errors

StatusDescription
404Not found, or not visible to the token (not_found).

Example

curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
200
{
  "id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  "org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
  "name": "example.com",
  "status": "active",
  "dns_mode": "delegated",
  "ns_pair": [
    "ns1.example-dns.net",
    "ns2.example-dns.net"
  ],
  "delegation_checked_at": "2026-10-27T09:00:00Z",
  "activated_at": "2026-03-02T11:40:00Z",
  "config_version": 12,
  "proxied_names": 3,
  "client_id": null,
  "created_at": "2026-03-02T10:20:00Z",
  "updated_at": "2026-10-27T09:00:00Z"
}

Delete zone

DELETE /v1/orgs/{org}/zones/{zone}

Deletes a zone with its records, edge settings and rules; OpenWAF stops serving its DNS and its sites. A last version is recorded first, so the history of the deleted zone stays readable through List versions.

Required role: tech or higher.

Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).
X-Change-ReasonheaderstringYesReason of the change: 1-500 UTF-8 characters after trimming spaces, no control characters. If it is missing or invalid: 422 reason_required. 1 to 500 characters.
If-MatchheaderstringNoExpected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict.
dry_runquerystringNo1/true simulates the change: answers 200 with the plan (Applied) and saves nothing. 0/false accepted; any other value: 400. One of 1, true, 0, false.

Response

  • 200: With dry_run=1, the plan of the change (Applied); nothing is saved.
  • 204: Deleted. No body.

Response headers: ETag, X-Config-Version.

Errors

StatusDescription
400Malformed request (bad_request).
404Not found, or not visible to the token (not_found).
412version_conflict: If-Match does not match the current version.
422Validation error (invalid) or missing or invalid reason (reason_required).

Example

curl -X DELETE "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Domain no longer used"

Get delegation status

GET /v1/orgs/{org}/zones/{zone}/delegation

Returns the delegation status of a delegated zone (the name servers to set at the registrar) or, for an external zone, the status of its ownership verification.

Required role: readonly or higher.

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).

Response

FieldTypeDescription
zone_idstring (uuid)Zone id.
namestringName.
statusstringOne of pending, active, moved, disabled.
dns_modestringdelegated: OpenWAF name servers. external: the customer keeps their DNS and points their sites at the edge (CNAME/A). One of delegated, external. Default delegated.
nameserversarray of stringThe name servers to set at the registrar.
checked_atstring (date-time)Last check.
activated_atstring (date-time)When the zone became active.
moved_atstring (date-time)When the delegation stopped pointing to OpenWAF.
verificationZoneVerification
observationCheckResult or VerifyResultCheckResult for delegated zones, VerifyResult for external ones.

Example

curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/delegation" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
200
{
  "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  "name": "example.com",
  "status": "pending",
  "dns_mode": "delegated",
  "nameservers": [
    "ns1.example-dns.net",
    "ns2.example-dns.net"
  ],
  "checked_at": "2026-10-27T09:30:00Z",
  "activated_at": "2026-10-27T09:30:00Z",
  "moved_at": "2026-10-27T09:30:00Z",
  "verification": {
    "name": "_openwaf.example.com",
    "type": "A",
    "value": "openwaf-verify=3f9c0a1e5b7d4c2a8e6f1b0d9c7a5e3f"
  },
  "observation": {
    "zone": "example.com",
    "expected": [
      "ns1.example-dns.net",
      "ns2.example-dns.net"
    ],
    "parent_zone": "com",
    "verdict": "delegated",
    "error": "",
    "checked_at": "2026-10-27T09:30:00Z",
    "sources": [
      {
        "kind": "parent",
        "server": "example",
        "host": "www.example.com",
        "ns": [
          "ns1.example-dns.net",
          "ns2.example-dns.net"
        ],
        "rcode": "NOERROR",
        "error": "",
        "match": false
      }
    ]
  }
}

Check delegation now

POST /v1/orgs/{org}/zones/{zone}/delegation/check

Checks now the delegation of a delegated zone (at the parent zone's servers and public resolvers), or the verification TXT record of an external zone, and activates the zone when it is verified. The observation is returned in observation.

Required role: tech or higher.

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).

Response

FieldTypeDescription
zone_idstring (uuid)Zone id.
namestringName.
statusstringOne of pending, active, moved, disabled.
dns_modestringdelegated: OpenWAF name servers. external: the customer keeps their DNS and points their sites at the edge (CNAME/A). One of delegated, external. Default delegated.
nameserversarray of stringThe name servers to set at the registrar.
checked_atstring (date-time)Last check.
activated_atstring (date-time)When the zone became active.
moved_atstring (date-time)When the delegation stopped pointing to OpenWAF.
verificationZoneVerification
observationCheckResult or VerifyResultCheckResult for delegated zones, VerifyResult for external ones.

Example

curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/delegation/check" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
200
{
  "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
  "name": "example.com",
  "status": "pending",
  "dns_mode": "delegated",
  "nameservers": [
    "ns1.example-dns.net",
    "ns2.example-dns.net"
  ],
  "checked_at": "2026-10-27T09:30:00Z",
  "activated_at": "2026-10-27T09:30:00Z",
  "moved_at": "2026-10-27T09:30:00Z",
  "verification": {
    "name": "_openwaf.example.com",
    "type": "A",
    "value": "openwaf-verify=3f9c0a1e5b7d4c2a8e6f1b0d9c7a5e3f"
  },
  "observation": {
    "zone": "example.com",
    "expected": [
      "ns1.example-dns.net",
      "ns2.example-dns.net"
    ],
    "parent_zone": "com",
    "verdict": "delegated",
    "error": "",
    "checked_at": "2026-10-27T09:30:00Z",
    "sources": [
      {
        "kind": "parent",
        "server": "example",
        "host": "www.example.com",
        "ns": [
          "ns1.example-dns.net",
          "ns2.example-dns.net"
        ],
        "rcode": "NOERROR",
        "error": "",
        "match": false
      }
    ]
  }
}

Check external zone names

GET /v1/orgs/{org}/zones/{zone}/hosts

For an external zone, checks on public resolvers whether each proxied name points at the OpenWAF edge:

  • ok: the CNAME chain reaches cname_target or every address belongs to the edge.
  • proxied_elsewhere: every address belongs to another proxy (for example Cloudflare); it works if that proxy's origin is the OpenWAF edge, but it cannot be confirmed.
  • missing: the name does not exist or has no data (error is set when no resolver answered).
  • wrong: anything else.

The result is informative: it does not change how a verified zone is served. It is cached for at least 30 seconds per zone.

Required role: readonly or higher.

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).

Response

FieldTypeDescription
checked_atstring (date-time)Last check.
itemsarray of object
items[].namestringName.
items[].statusstringOne of ok, proxied_elsewhere, wrong, missing.
items[].viastringOne of cname, a, "".
items[].answersarray of stringTargets of the CNAME chain, then the addresses.
items[].errorstring

Errors

StatusDescription
404Not found, or not visible to the token (not_found).
409zone_not_external: the zone is delegated (OpenWAF DNS serves its names).
503The check is not available right now.

Example

curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/hosts" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
200
{
  "checked_at": "2026-10-27T09:30:00Z",
  "items": [
    {
      "name": "example.com",
      "status": "ok",
      "via": "cname",
      "answers": [
        "example"
      ],
      "error": ""
    }
  ]
}

Merge zone into its parent

POST /v1/orgs/{org}/zones/{zone}/merge

Merges a zone (for example shop.example.com) into its parent zone (into, for example example.com): its records (same ids) and certificates move to the parent, settings that differ become per-name settings under hosts of the parent, and its rules move to the parent limited to the child's names. Both zones must be active and have the same DNS mode. The parent inherits the child's client if it has none; zones of different clients are not merged.

The merge is applied only if every name keeps being served exactly the same; otherwise it answers 409 merge_not_equivalent with the plan. The child zone is then disabled (merged_into) and purged after 14 days; the parent gets a new version. In delegated zones the names are published in the parent before the child zone is removed, so they never stop resolving. reason and dry_run in the body take precedence over the header and the query parameter.

Required role: admin or higher. Organization-wide.

Change controls: dry_run · X-Change-Reason (required)

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).
X-Change-ReasonheaderstringYesReason of the change: 1-500 UTF-8 characters after trimming spaces, no control characters. If it is missing or invalid: 422 reason_required. 1 to 500 characters.
dry_runquerystringNo1/true simulates the change: answers 200 with the plan (Applied) and saves nothing. 0/false accepted; any other value: 400. One of 1, true, 0, false.

Request body

Content type application/json. Schema: MergeInput.

FieldTypeRequiredDescription
intostringYesId or name of the parent zone.
reasonstringNoTakes precedence over X-Change-Reason.
dry_runbooleanNoTakes precedence over ?dry_run. Default false.
expected_versionsmap of integerNoZone id or name → expected version (like If-Match).

Response

  • 200: Plan (simulation, also when it cannot be applied) or merge applied (X-Config-Version and ETag = the parent's new version). Returns MergeResult.
FieldTypeDescription
dry_runbooleantrue in a simulation.
equivalentbooleanEvery name is served the same before and after.
childMergeZoneRef
parentMergeZoneRef
movedobject
moved.recordsarray of object
moved.certificatesarray of object
moved.rulesarray of ZoneRuleRules created in the parent (only when applied).
moved.hostsHostSettingsMap
moved.clientobject
hostsarray of objectEquivalence report per name.
hosts[].hoststring
hosts[].from_zonestringOne of child, parent.
hosts[].equivalentboolean
hosts[].nodesobject
hosts[].certificatesobject
problemsarray of stringReasons why it cannot be applied.
warningsarray of stringWarnings (the change is still valid).
diffDiffChanges.

Response headers: ETag, X-Config-Version.

Errors

StatusDescription
400Malformed request (bad_request).
403The token's role or limits do not allow it (forbidden).
404Not found, or not visible to the token (not_found).
409merge_not_equivalent: not applied; plan carries the report.
412version_conflict: If-Match does not match the current version.
422Validation error (invalid) or missing or invalid reason (reason_required).

Example

curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/merge" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Merge shop.example.com into example.com" \
  -H "Content-Type: application/json" \
  --data '{
    "into": "example.com",
    "dry_run": true
  }'
200
{
  "dry_run": false,
  "equivalent": false,
  "child": {
    "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    "name": "example.com",
    "from_version": 12,
    "to_version": 12
  },
  "parent": {
    "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    "name": "example.com",
    "from_version": 12,
    "to_version": 12
  },
  "moved": {
    "records": [
      {
        "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
        "type": "A",
        "name": "example.com",
        "content": "203.0.113.10",
        "proxied": false
      }
    ],
    "certificates": [
      {
        "id": "a5614527-0ce6-43be-ad1d-d012b7394867",
        "hostname": "www.example.com",
        "source": "example",
        "fingerprint_sha256": "example",
        "not_after": "2026-10-27T09:30:00Z",
        "moved": false
      }
    ],
    "rules": [
      {
        "id": 3,
        "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
        "kind": "ip",
        "ip": "203.0.113.0/24",
        "action": "block",
        "note": "Abusive scanner",
        "hosts": [],
        "created_at": "2026-10-27T09:30:00Z"
      }
    ],
    "hosts": {
      "www.example.com": {
        "mode": "active",
        "waf_level": "off",
        "rate_limit_rpm": 0,
        "timeout_ms": 0,
        "enable_feed": false,
        "stack": "generic",
        "block_page_mode": "nginx",
        "fake_server_header": "example",
        "block_page_html": "",
        "access_gate": {},
        "max_body_mb": 0,
        "redirect_allow_hosts": [
          "www.example.com"
        ],
        "cors_allow_origins": [
          "https://app.example.com"
        ],
        "hsts": {},
        "compression": false,
        "origin": {},
        "cache": {},
        "early_hints": {},
        "rum": {},
        "ai_agents": {},
        "rate_rules": []
      }
    },
    "client": {
      "from": "75857a45-8999-45be-ac4d-941e90b6b396",
      "to": "663ea1bf-ffe5-438f-af0c-f667f14c4257"
    }
  },
  "hosts": [
    {
      "host": "www.example.com",
      "from_zone": "child",
      "equivalent": false,
      "nodes": {
        "before": [],
        "after": [],
        "lost": [],
        "gained": []
      },
      "certificates": {
        "before": [],
        "after": []
      }
    }
  ],
  "problems": [],
  "warnings": [],
  "diff": {
    "from": 0,
    "to": 0,
    "changes": [
      {
        "path": "rules[id=12]",
        "op": "add",
        "from": "example",
        "to": "example"
      }
    ]
  }
}

Unmerge zone

POST /v1/orgs/{org}/zones/{zone}/unmerge

Undoes a merge: the parent goes back to its version before the merge and the child zone to its own (records and certificates return to the child, which becomes active again). If the parent changed after the merge, revert those changes first; otherwise the answer is 409 merge_not_reversible.

Required role: admin or higher. Organization-wide.

Change controls: dry_run · X-Change-Reason (required)

Parameters

NameInTypeRequiredDescription
orgpathstring (uuid)YesOrganization id. Every route checks that it is the token's organization (otherwise 404).
zonepathstring (uuid)YesZone id (see List zones).
X-Change-ReasonheaderstringYesReason of the change: 1-500 UTF-8 characters after trimming spaces, no control characters. If it is missing or invalid: 422 reason_required. 1 to 500 characters.
dry_runquerystringNo1/true simulates the change: answers 200 with the plan (Applied) and saves nothing. 0/false accepted; any other value: 400. One of 1, true, 0, false.

Request body

Content type application/json. Schema: UnmergeInput.

FieldTypeRequiredDescription
reasonstringNoReason of the change.
expected_versionintegerNoVersion of the parent left by the merge.
dry_runbooleanNotrue in a simulation. Default false.

Response

FieldTypeDescription
dry_runbooleantrue in a simulation.
childMergeZoneRef
parentMergeZoneRef
warningsarray of stringWarnings (the change is still valid).

Errors

StatusDescription
400Malformed request (bad_request).
403The token's role or limits do not allow it (forbidden).
404Not found, or not visible to the token (not_found).
409merge_not_reversible.
422Validation error (invalid) or missing or invalid reason (reason_required).

Example

curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/unmerge" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Undo the merge" \
  -H "Content-Type: application/json" \
  --data '{
    "expected_version": 14
  }'
200
{
  "dry_run": false,
  "child": {
    "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    "name": "example.com",
    "from_version": 12,
    "to_version": 12
  },
  "parent": {
    "zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
    "name": "example.com",
    "from_version": 12,
    "to_version": 12
  },
  "warnings": []
}

Last updated:

↑↓ to navigate↵ to openesc to close