DNS records of a zone. A/AAAA/CNAME records can be proxied: the name then answers with the edge addresses and the record content becomes the origin. In an external zone only proxied A/AAAA/CNAME records are accepted (they are the sites served by the edge). Every write creates a new configuration version.
| Operation | Endpoint |
|---|---|
| List records | GET /v1/orgs/{org}/zones/{zone}/records |
| Create record | POST /v1/orgs/{org}/zones/{zone}/records |
| Create records in bulk | POST /v1/orgs/{org}/zones/{zone}/records/bulk |
| Batch records | POST /v1/orgs/{org}/zones/{zone}/records/batch |
| Get record | GET /v1/orgs/{org}/zones/{zone}/records/{record} |
| Update record | PATCH /v1/orgs/{org}/zones/{zone}/records/{record} |
| Delete record | DELETE /v1/orgs/{org}/zones/{zone}/records/{record} |
| Find existing records | GET /v1/orgs/{org}/zones/{zone}/import |
| Read a BIND zone file | POST /v1/orgs/{org}/zones/{zone}/import/bind |
| Get edge DNS answers | GET /v1/orgs/{org}/zones/{zone}/dns/answers |
List records
GET /v1/orgs/{org}/zones/{zone}/recordsLists the DNS records of a zone with their ids.
Required role: readonly or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
Response
200: Returnsitems, an array of Record.
| Field | Type | Description |
|---|---|---|
items[].id | string (uuid) | Id. |
items[].org_id | string (uuid) | Organization id. |
items[].zone_id | string (uuid) | Zone id. |
items[].type | string | |
items[].name | string | Full name without a trailing dot. |
items[].content | string | Origin content. |
items[].ttl | integer | TTL in seconds. |
items[].priority | integer | MX and SRV priority (null for other types). |
items[].proxied | boolean | Served through the OpenWAF edge. |
items[].comment | string | Comment (empty if none). |
items[].tags | array of string | Tags (empty list if none). |
items[].created_at | string (date-time) | Creation time. |
items[].updated_at | string (date-time) | Last change. |
Example
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/records" \
-H "Authorization: Bearer $OPENWAF_TOKEN"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.listRecords({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.list_records(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"){
"items": [
{
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"type": "A",
"name": "www.example.com",
"content": "203.0.113.10",
"ttl": 300,
"priority": null,
"proxied": true,
"comment": "Web servers",
"tags": [
"web"
],
"created_at": "2026-03-02T10:25:00Z",
"updated_at": "2026-10-27T09:00:00Z"
}
]
}Create record
POST /v1/orgs/{org}/zones/{zone}/recordsCreates a DNS record. In an external zone only proxied A/AAAA/CNAME records are accepted (they are the sites published to the edge); they are not published in DNS, which stays with the other provider.
Required role: tech or higher.
Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
X-Change-Reason | header | string | Yes | Reason 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-Match | header | string | No | Expected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict. |
dry_run | query | string | No | 1/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: RecordInput.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | One of A, AAAA, CNAME, MX, TXT, NS, SRV, CAA. |
name | string | No | @, relative (www) or full name; * for a wildcard. Default @. |
content | string | Yes | A/AAAA: address. CNAME/NS/MX: host name. TXT: text without quotes (split into 255-character strings). SRV: weight port target. CAA: flags tag "value". |
ttl | integer | No | TTL in seconds. Default 3600. From 60 to 86400. |
priority | integer | No | Required for MX and SRV; not allowed for other types. From 0 to 65535. |
proxied | boolean | No | A/AAAA/CNAME only. The name answers with the edge addresses (TTL 300) and the content is kept as the origin. All A/AAAA/CNAME records of a name share the value. An unproxied CNAME at the apex is published flattened (ALIAS). Default false. |
comment | string | No | Attribute of the record (not published in DNS; part of the zone's versions). No control characters. At most 100 characters. |
tags | array of string | No | Attribute of the record, like the comment. Lowercased and deduplicated. At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
Response
200: Withdry_run=1, the plan of the change (Applied); nothing is saved.201: Created and published. Returns Record.
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Id. |
org_id | string (uuid) | Organization id. |
zone_id | string (uuid) | Zone id. |
type | string | |
name | string | Full name without a trailing dot. |
content | string | Origin content. |
ttl | integer | TTL in seconds. |
priority | integer | MX and SRV priority (null for other types). |
proxied | boolean | Served through the OpenWAF edge. |
comment | string | Comment (empty if none). |
tags | array of string | Tags (empty list if none). |
created_at | string (date-time) | Creation time. |
updated_at | string (date-time) | Last change. |
Response headers: ETag, X-Config-Version.
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
409 | Disabled zone, or zone_merged. |
412 | version_conflict: If-Match does not match the current version. |
422 | Validation 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/records" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Add the API host" \
-H 'If-Match: "12"' \
-H "Content-Type: application/json" \
--data '{
"type": "A",
"name": "api",
"content": "203.0.113.20",
"proxied": true
}'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.createRecord({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body: {
type: "A",
name: "api",
content: "203.0.113.20",
proxied: true
}
}, {
reason: "Add the API host",
ifMatch: 12
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.create_record(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body={
"type": "A",
"name": "api",
"content": "203.0.113.20",
"proxied": True
},
reason="Add the API host",
if_match=12,
){
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"type": "A",
"name": "www.example.com",
"content": "203.0.113.10",
"ttl": 300,
"priority": null,
"proxied": true,
"comment": "Web servers",
"tags": [
"web"
],
"created_at": "2026-03-02T10:25:00Z",
"updated_at": "2026-10-27T09:00:00Z"
}Create records in bulk
POST /v1/orgs/{org}/zones/{zone}/records/bulkCreates up to 1000 records atomically, all or none, as one version (for example a reviewed import). A validation error names the record in field (records[i].field).
Required role: tech or higher.
Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
X-Change-Reason | header | string | Yes | Reason 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-Match | header | string | No | Expected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict. |
dry_run | query | string | No | 1/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.
| Field | Type | Required | Description |
|---|---|---|---|
records | array of RecordInput | Yes | At most 1000 items. |
records[].type | string | Yes | One of A, AAAA, CNAME, MX, TXT, NS, SRV, CAA. |
records[].name | string | No | @, relative (www) or full name; * for a wildcard. Default @. |
records[].content | string | Yes | A/AAAA: address. CNAME/NS/MX: host name. TXT: text without quotes (split into 255-character strings). SRV: weight port target. CAA: flags tag "value". |
records[].ttl | integer | No | TTL in seconds. Default 3600. From 60 to 86400. |
records[].priority | integer | No | Required for MX and SRV; not allowed for other types. From 0 to 65535. |
records[].proxied | boolean | No | A/AAAA/CNAME only. The name answers with the edge addresses (TTL 300) and the content is kept as the origin. All A/AAAA/CNAME records of a name share the value. An unproxied CNAME at the apex is published flattened (ALIAS). Default false. |
records[].comment | string | No | Attribute of the record (not published in DNS; part of the zone's versions). No control characters. At most 100 characters. |
records[].tags | array of string | No | Attribute of the record, like the comment. Lowercased and deduplicated. At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
Response
200: Withdry_run=1, the plan of the change (Applied); nothing is saved.201: Created (one version). Returnsitems, an array of Record.
| Field | Type | Description |
|---|---|---|
items[].id | string (uuid) | Id. |
items[].org_id | string (uuid) | Organization id. |
items[].zone_id | string (uuid) | Zone id. |
items[].type | string | |
items[].name | string | Full name without a trailing dot. |
items[].content | string | Origin content. |
items[].ttl | integer | TTL in seconds. |
items[].priority | integer | MX and SRV priority (null for other types). |
items[].proxied | boolean | Served through the OpenWAF edge. |
items[].comment | string | Comment (empty if none). |
items[].tags | array of string | Tags (empty list if none). |
items[].created_at | string (date-time) | Creation time. |
items[].updated_at | string (date-time) | Last change. |
Response headers: ETag, X-Config-Version.
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
409 | zone_merged: the zone is merged into another one (read-only until it is unmerged or purged). |
412 | version_conflict: If-Match does not match the current version. |
422 | Validation error (field names records[i].field) or reason_required. |
Example
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/records/bulk" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Import reviewed records" \
-H "Content-Type: application/json" \
--data '{
"records": [
{
"type": "A",
"name": "@",
"content": "203.0.113.10",
"proxied": true
},
{
"type": "MX",
"name": "@",
"content": "mail.example.com",
"priority": 10
}
]
}'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.bulkCreateRecords({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body: {
records: [
{
type: "A",
name: "@",
content: "203.0.113.10",
proxied: true
},
{
type: "MX",
name: "@",
content: "mail.example.com",
priority: 10
}
]
}
}, {
reason: "Import reviewed records"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.bulk_create_records(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body={
"records": [
{
"type": "A",
"name": "@",
"content": "203.0.113.10",
"proxied": True
},
{
"type": "MX",
"name": "@",
"content": "mail.example.com",
"priority": 10
}
]
},
reason="Import reviewed records",
){
"items": [
{
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"type": "A",
"name": "www.example.com",
"content": "203.0.113.10",
"ttl": 300,
"priority": null,
"proxied": true,
"comment": "Web servers",
"tags": [
"web"
],
"created_at": "2026-03-02T10:25:00Z",
"updated_at": "2026-10-27T09:00:00Z"
}
]
}Batch records
POST /v1/orgs/{org}/zones/{zone}/records/batchCreates, updates and deletes records as one change, all or nothing, with one version, one reason and one audit entry per record. Turning the proxy on or off for an A/AAAA/CNAME also changes the other A/AAAA/CNAME records of the same name that are not in the batch. The answer (200) is the plan of the change plus the resulting records of the zone.
Required role: tech or higher.
Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
X-Change-Reason | header | string | Yes | Reason 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-Match | header | string | No | Expected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict. |
dry_run | query | string | No | 1/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: RecordBatch.
Up to 500 operations in total. Each record is updated or deleted at most once. Deletes are applied first, then updates, then creates; coexistence rules are checked on the final state.
| Field | Type | Required | Description |
|---|---|---|---|
create | array of RecordInput | No | |
create[].type | string | Yes | One of A, AAAA, CNAME, MX, TXT, NS, SRV, CAA. |
create[].name | string | No | @, relative (www) or full name; * for a wildcard. Default @. |
create[].content | string | Yes | A/AAAA: address. CNAME/NS/MX: host name. TXT: text without quotes (split into 255-character strings). SRV: weight port target. CAA: flags tag "value". |
create[].ttl | integer | No | TTL in seconds. Default 3600. From 60 to 86400. |
create[].priority | integer | No | Required for MX and SRV; not allowed for other types. From 0 to 65535. |
create[].proxied | boolean | No | A/AAAA/CNAME only. The name answers with the edge addresses (TTL 300) and the content is kept as the origin. All A/AAAA/CNAME records of a name share the value. An unproxied CNAME at the apex is published flattened (ALIAS). Default false. |
create[].comment | string | No | Attribute of the record (not published in DNS; part of the zone's versions). No control characters. At most 100 characters. |
create[].tags | array of string | No | Attribute of the record, like the comment. Lowercased and deduplicated. At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
update | array of object | No | |
update[].id | string (uuid) | Yes | Id. |
update[].type | string | No | |
update[].name | string | No | Name. |
update[].content | string | No | |
update[].ttl | integer | No | TTL in seconds. |
update[].priority | integer or null | No | MX and SRV priority (null for other types). |
update[].proxied | boolean | No | Served through the OpenWAF edge. |
update[].comment | string | No | At most 100 characters. |
update[].tags | array of string | No | At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
delete | array of string (uuid) | No |
Response
200: Applied (or simulated withdry_run). Returns BatchResult.
| Field | Type | Description |
|---|---|---|
dry_run | boolean | true in a simulation. |
from_version | integer | Current version before the change. |
to_version | integer | Version the change creates (the same if nothing changes). |
diff | Diff | Changes. |
warnings | array of string | Warnings (the change is still valid). |
records | array of Record | Records of the zone after the change (simulated with dry_run). |
Response headers: ETag, X-Config-Version.
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
409 | Disabled zone, or zone_merged. |
412 | version_conflict: If-Match does not match the current version. |
422 | Validation error (field names the operation: create[i].field, update[i].field, delete[i]; empty if the batch is empty or has more than 500 operations) or reason_required. |
Example
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/records/batch?dry_run=1" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Move the site to the new origin" \
-H "Content-Type: application/json" \
--data '{
"create": [
{
"type": "CNAME",
"name": "shop",
"content": "shops.example.net",
"proxied": true
}
],
"update": [
{
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"content": "203.0.113.30"
}
],
"delete": [
"1b3d5f7a-9c2e-4a6b-8d0f-2a4c6e8b0d2f"
]
}'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.batchRecords({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body: {
create: [
{
type: "CNAME",
name: "shop",
content: "shops.example.net",
proxied: true
}
],
update: [
{
id: "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
content: "203.0.113.30"
}
],
delete: [
"1b3d5f7a-9c2e-4a6b-8d0f-2a4c6e8b0d2f"
]
}
}, {
reason: "Move the site to the new origin",
dryRun: true
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.batch_records(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body={
"create": [
{
"type": "CNAME",
"name": "shop",
"content": "shops.example.net",
"proxied": True
}
],
"update": [
{
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"content": "203.0.113.30"
}
],
"delete": [
"1b3d5f7a-9c2e-4a6b-8d0f-2a4c6e8b0d2f"
]
},
reason="Move the site to the new origin",
dry_run=True,
){
"dry_run": false,
"from_version": 12,
"to_version": 12,
"diff": {
"from": 0,
"to": 0,
"changes": [
{
"path": "rules[id=12]",
"op": "add",
"from": "example",
"to": "example"
}
]
},
"warnings": [],
"records": [
{
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"type": "A",
"name": "www.example.com",
"content": "203.0.113.10",
"ttl": 300,
"priority": null,
"proxied": true,
"comment": "Web servers",
"tags": [
"web"
],
"created_at": "2026-03-02T10:25:00Z",
"updated_at": "2026-10-27T09:00:00Z"
}
]
}Get record
GET /v1/orgs/{org}/zones/{zone}/records/{record}Returns one record. For a proxied record, content is the origin.
Required role: readonly or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
record | path | string (uuid) | Yes | Record id (see List records). |
Response
200: Returns Record.
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Id. |
org_id | string (uuid) | Organization id. |
zone_id | string (uuid) | Zone id. |
type | string | |
name | string | Full name without a trailing dot. |
content | string | Origin content. |
ttl | integer | TTL in seconds. |
priority | integer | MX and SRV priority (null for other types). |
proxied | boolean | Served through the OpenWAF edge. |
comment | string | Comment (empty if none). |
tags | array of string | Tags (empty list if none). |
created_at | string (date-time) | Creation time. |
updated_at | string (date-time) | Last change. |
Errors
| Status | Description |
|---|---|
404 | Not 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/records/0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d" \
-H "Authorization: Bearer $OPENWAF_TOKEN"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getRecord({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
record: "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.get_record(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
record="0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
){
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"type": "A",
"name": "www.example.com",
"content": "203.0.113.10",
"ttl": 300,
"priority": null,
"proxied": true,
"comment": "Web servers",
"tags": [
"web"
],
"created_at": "2026-03-02T10:25:00Z",
"updated_at": "2026-10-27T09:00:00Z"
}Update record
PATCH /v1/orgs/{org}/zones/{zone}/records/{record}Changes fields of a record; fields left out keep their value.
Required role: tech or higher.
Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
record | path | string (uuid) | Yes | Record id (see List records). |
X-Change-Reason | header | string | Yes | Reason 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-Match | header | string | No | Expected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict. |
dry_run | query | string | No | 1/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: RecordPatch.
Fields that change; the others keep their value.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | No | |
name | string | No | Name. |
content | string | No | |
ttl | integer | No | TTL in seconds. |
priority | integer or null | No | MX and SRV priority (null for other types). |
proxied | boolean | No | Served through the OpenWAF edge. |
comment | string | No | At most 100 characters. |
tags | array of string | No | At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
Response
200: Record changed (with X-Config-Version and ETag) or, withdry_run, the plan (Applied). Returns Record or Applied.
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Id. |
org_id | string (uuid) | Organization id. |
zone_id | string (uuid) | Zone id. |
type | string | |
name | string | Full name without a trailing dot. |
content | string | Origin content. |
ttl | integer | TTL in seconds. |
priority | integer | MX and SRV priority (null for other types). |
proxied | boolean | Served through the OpenWAF edge. |
comment | string | Comment (empty if none). |
tags | array of string | Tags (empty list if none). |
created_at | string (date-time) | Creation time. |
updated_at | string (date-time) | Last change. |
Response headers: ETag, X-Config-Version.
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
409 | zone_merged: the zone is merged into another one (read-only until it is unmerged or purged). |
412 | version_conflict: If-Match does not match the current version. |
422 | Validation error (invalid) or missing or invalid reason (reason_required). |
Example
curl -X PATCH "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/records/0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Point www at the new origin" \
-H 'If-Match: "12"' \
-H "Content-Type: application/json" \
--data '{
"content": "203.0.113.30"
}'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.updateRecord({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
record: "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
body: {
content: "203.0.113.30"
}
}, {
reason: "Point www at the new origin",
ifMatch: 12
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.update_record(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
record="0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
body={
"content": "203.0.113.30"
},
reason="Point www at the new origin",
if_match=12,
){
"id": "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
"org_id": "1e7c9a3b-5d2f-4b8a-9c6e-0f2a4b6c8d1e",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"type": "A",
"name": "www.example.com",
"content": "203.0.113.10",
"ttl": 300,
"priority": null,
"proxied": true,
"comment": "Web servers",
"tags": [
"web"
],
"created_at": "2026-03-02T10:25:00Z",
"updated_at": "2026-10-27T09:00:00Z"
}Delete record
DELETE /v1/orgs/{org}/zones/{zone}/records/{record}Deletes a record. Deleting a proxied record stops serving its site.
Required role: tech or higher.
Change controls: dry_run · If-Match (optional) · X-Change-Reason (required)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
record | path | string (uuid) | Yes | Record id (see List records). |
X-Change-Reason | header | string | Yes | Reason 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-Match | header | string | No | Expected configuration version, e.g. "12" (W/ accepted). If it is not the current one: 412 version_conflict. |
dry_run | query | string | No | 1/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: Withdry_run=1, the plan of the change (Applied); nothing is saved.204: Deleted. No body.
Response headers: ETag, X-Config-Version.
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
404 | Not found, or not visible to the token (not_found). |
409 | zone_merged: the zone is merged into another one (read-only until it is unmerged or purged). |
412 | version_conflict: If-Match does not match the current version. |
422 | Validation 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/records/0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Remove an unused host" \
-H 'If-Match: "12"'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.deleteRecord({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
record: "0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d"
}, {
reason: "Remove an unused host",
ifMatch: 12
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.delete_record(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
record="0c9a7e4d-2b1f-4e6a-8d3c-5f7b9a1c3e5d",
reason="Remove an unused host",
if_match=12,
)Find existing records
GET /v1/orgs/{org}/zones/{zone}/importQueries the current authoritative servers (or public resolvers) for the apex, common names, mail TXT records, SRV records and names seen in Certificate Transparency logs, and returns them for review. Nothing is applied; send the reviewed list to Create records in bulk or Batch records.
Required role: tech or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
Response
200: Returns ImportResult.
| Field | Type | Description |
|---|---|---|
domain | string | |
servers | array of string | |
wildcard | boolean | |
warnings | array of string | Warnings (the change is still valid). |
candidates | array of object | |
candidates[].type | string | One of A, AAAA, CNAME, MX, TXT, NS, SRV, CAA. |
candidates[].name | string | @, relative (www) or full name; * for a wildcard. Default @. |
candidates[].content | string | A/AAAA: address. CNAME/NS/MX: host name. TXT: text without quotes (split into 255-character strings). SRV: weight port target. CAA: flags tag "value". |
candidates[].ttl | integer | TTL in seconds. Default 3600. From 60 to 86400. |
candidates[].priority | integer | Required for MX and SRV; not allowed for other types. From 0 to 65535. |
candidates[].proxied | boolean | A/AAAA/CNAME only. The name answers with the edge addresses (TTL 300) and the content is kept as the origin. All A/AAAA/CNAME records of a name share the value. An unproxied CNAME at the apex is published flattened (ALIAS). Default false. |
candidates[].comment | string | Attribute of the record (not published in DNS; part of the zone's versions). No control characters. At most 100 characters. |
candidates[].tags | array of string | Attribute of the record, like the comment. Lowercased and deduplicated. At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
candidates[].source | string | One of dns, crtsh. |
Example
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/import" \
-H "Authorization: Bearer $OPENWAF_TOKEN"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.listImportCandidates({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.list_import_candidates(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"){
"domain": "example",
"servers": [
"example"
],
"wildcard": false,
"warnings": [],
"candidates": [
{
"type": "A",
"name": "example.com",
"content": "203.0.113.10",
"ttl": 3600,
"priority": 0,
"proxied": false,
"comment": "Web servers",
"tags": [
"web"
],
"source": "dns"
}
]
}Read a BIND zone file
POST /v1/orgs/{org}/zones/{zone}/import/bindReads a BIND zone file (up to 1 MiB) and returns the records it contains for review. Nothing is applied; apply the chosen candidates with Batch records. Not available in external zones.
It understands $ORIGIN, $TTL, @, relative names, inherited owners, parentheses, comments and multi-string TXT records. It leaves out, with the reason, the SOA and the apex NS records (OpenWAF sets them), other directives, unsupported types or classes, names outside the zone, invalid records, records that already exist in the zone and duplicates.
Required role: tech or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
proxied | query | boolean | No | Proxy the A/AAAA/CNAME records whose name can be a site (no wildcards or _). Default false. |
Request body
Content type text/plain.
At most 1048576 characters.
Response
200: Returns BINDImport.
| Field | Type | Description |
|---|---|---|
items | array of object | |
items[].type | string | One of A, AAAA, CNAME, MX, TXT, NS, SRV, CAA. |
items[].name | string | @, relative (www) or full name; * for a wildcard. Default @. |
items[].content | string | A/AAAA: address. CNAME/NS/MX: host name. TXT: text without quotes (split into 255-character strings). SRV: weight port target. CAA: flags tag "value". |
items[].ttl | integer | TTL in seconds. Default 3600. From 60 to 86400. |
items[].priority | integer | Required for MX and SRV; not allowed for other types. From 0 to 65535. |
items[].proxied | boolean | A/AAAA/CNAME only. The name answers with the edge addresses (TTL 300) and the content is kept as the origin. All A/AAAA/CNAME records of a name share the value. An unproxied CNAME at the apex is published flattened (ALIAS). Default false. |
items[].comment | string | Attribute of the record (not published in DNS; part of the zone's versions). No control characters. At most 100 characters. |
items[].tags | array of string | Attribute of the record, like the comment. Lowercased and deduplicated. At most 10 items. Items match ^[a-z0-9][a-z0-9:_-]{0,31}$. |
items[].source | string | Always file. |
skipped | array of object | At most 200 entries; skipped_total counts them all. |
skipped[].line | integer | Line where the entry starts. |
skipped[].text | string | Entry without comments (up to 200 bytes). |
skipped[].reason | string | Reason of the change. |
skipped_total | integer | Entries left out (all of them, even if skipped only lists the first 200). |
truncated | boolean | The file had more than 5000 entries: only the first 5000 were read. |
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
404 | Not found, or not visible to the token (not_found). |
409 | External zone. |
413 | too_large: the file is larger than 1 MiB. |
Example
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/import/bind" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "Content-Type: text/plain" \
--data-binary @example.com.zoneimport { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.parseBindZone({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body: ""
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.parse_bind_zone(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d", body=""){
"items": [
{
"type": "A",
"name": "example.com",
"content": "203.0.113.10",
"ttl": 3600,
"priority": 0,
"proxied": false,
"comment": "Web servers",
"tags": [
"web"
],
"source": "file"
}
],
"skipped": [
{
"line": 0,
"text": "example",
"reason": "Point www at the new origin"
}
],
"skipped_total": 128,
"truncated": false
}Get edge DNS answers
GET /v1/orgs/{org}/zones/{zone}/dns/answersFor a delegated zone, returns the addresses that OpenWAF DNS publishes for each proxied name (the edge addresses) and whether the published answer is up to date (in_sync; if not, it is corrected automatically).
Required role: readonly or higher.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
org | path | string (uuid) | Yes | Organization id. Every route checks that it is the token's organization (otherwise 404). |
zone | path | string (uuid) | Yes | Zone id (see List zones). |
Response
200: Returns ZoneAnswers.
| Field | Type | Description |
|---|---|---|
ipv4 | array of string | |
ipv6 | array of string | |
names | array of object | |
names[].name | string | Name. |
names[].in_sync | boolean | |
names[].rrsets | array of object |
Errors
| Status | Description |
|---|---|
404 | Not found, or not visible to the token (not_found). |
409 | conflict: the zone is external (another provider serves its DNS). |
503 | The DNS service is not available right now. |
Example
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/dns/answers" \
-H "Authorization: Bearer $OPENWAF_TOKEN"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getDnsAnswers({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.get_dns_answers(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"){
"ipv4": [
"example"
],
"ipv6": [
"example"
],
"names": [
{
"name": "example.com",
"in_sync": false,
"rrsets": [
{}
]
}
]
}