Encrypt and authenticate the connection between the edge and your origin. The edge verifies origin certificates issued by the OpenWAF Origin CA in full_strict mode, and can present its own client certificate (mTLS) to origins that require it. See SSL/TLS.
| Operation | Endpoint |
|---|---|
| List origin certificates | GET /v1/orgs/{org}/zones/{zone}/origin-certs |
| Issue origin certificate | POST /v1/orgs/{org}/zones/{zone}/origin-certs |
| Revoke origin certificate | DELETE /v1/orgs/{org}/zones/{zone}/origin-certs/{cert} |
| Get origin TLS status | GET /v1/orgs/{org}/zones/{zone}/origin-tls/status |
| Download the Origin CA | GET /v1/origin-tls/origin-ca.pem |
| Download the edge client CA | GET /v1/origin-tls/edge-client-ca.pem |
List origin certificates
GET /v1/orgs/{org}/zones/{zone}/origin-certsLists the origin certificates issued for the zone, newest first, with their hostnames, key type, serial, expiry and status (active, revoked, expired). Private keys are never returned.
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 OriginCert.
| Field | Type | Description |
|---|---|---|
items[].id | string (uuid) | Id. |
items[].zone_id | string (uuid) | Zone id. |
items[].hostnames | array of string | |
items[].key_type | string | csr: the public key of a certificate request was certified. One of ecdsa_p256, rsa2048, csr. |
items[].serial | string | Lowercase hexadecimal without leading zeros. |
items[].fingerprint_sha256 | string | SHA-256 fingerprint of the certificate. |
items[].not_before | string (date-time) | Start of validity. |
items[].not_after | string (date-time) | End of validity. |
items[].validity_days | integer | |
items[].status | string | revoked takes precedence over expired. One of active, revoked, expired. |
items[].created_by | string | Who created it (actor). |
items[].created_at | string (date-time) | Creation time. |
items[].revoked_at | string (date-time) or null | Revocation time (null if active). |
items[].revoked_by | string or null | Who revoked it. |
Errors
| Status | Description |
|---|---|
404 | Not found, or not visible to the token (not_found). |
503 | unavailable: the Origin CA is not available right now. |
Example
curl "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs" \
-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.listOriginCerts({
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_origin_certs(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"){
"items": [
{
"id": "a5614527-0ce6-43be-ad1d-d012b7394867",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"hostnames": [
"example.com",
"*.example.com"
],
"key_type": "ecdsa_p256",
"serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
"fingerprint_sha256": "example",
"not_before": "2026-10-27T09:30:00Z",
"not_after": "2026-10-27T09:30:00Z",
"validity_days": 0,
"status": "active",
"created_by": "token:11111111-2222-4333-8444-555555555555",
"created_at": "2026-10-27T09:30:00Z",
"revoked_at": "2026-10-27T09:30:00Z",
"revoked_by": null
}
]
}Issue origin certificate
POST /v1/orgs/{org}/zones/{zone}/origin-certsIssues a certificate signed by the OpenWAF Origin CA for names of the zone, to install on the origin server so that the edge can verify it in full_strict mode. The certificate has only DNS names (SAN), server authentication usage, a random 128-bit serial and the first name as CN.
OpenWAF generates the key (key_type) unless you send a csr. The private key is returned only in this answer (Cache-Control: no-store) and is never stored. With dry_run=1 the request is only validated.
Required role: tech or higher.
Change controls: dry_run · X-Change-Reason (optional)
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 | No | Reason of the change (1-500 UTF-8 characters, no control characters); defaults to the creation of the zone. 1 to 500 characters. |
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: OriginCertRequest.
| Field | Type | Required | Description |
|---|---|---|---|
hostnames | array of string | Yes | The zone, names below it or wildcards *.<zone name>; lowercased and deduplicated. 1 to 100 items. |
key_type | string | No | Key that OpenWAF generates (empty with csr). One of ecdsa_p256, rsa2048. Default ecdsa_p256. |
validity_days | integer | No | One of 7, 30, 90, 365, 730, 1095, 5475. Default 5475. |
csr | string | No | PEM certificate request (ECDSA P-256/P-384 or RSA 2048-8192): its public key is certified; its subject and extensions are ignored and no key is generated. |
Response
200: Simulation. Returns OriginCertIssued.201: Issued. Returns OriginCertIssued.
| Field | Type | Description |
|---|---|---|
dry_run | boolean | Only with ?dry_run=1: what would be issued; no PEM. |
certificate | OriginCert | |
certificate_pem | string | |
private_key_pem | string | PKCS#8. Only in this answer (never stored); left out with csr. |
ca_pem | string | The OpenWAF Origin CA that signs it. |
Errors
| Status | Description |
|---|---|
400 | Malformed request (bad_request). |
403 | The token's role or limits do not allow it (forbidden). |
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). |
422 | Validation error (invalid) or missing or invalid reason (reason_required). |
503 | unavailable: the Origin CA is not available right now. |
Example
curl -X POST "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Origin certificate for the web servers" \
-H "Content-Type: application/json" \
--data '{
"hostnames": [
"example.com",
"*.example.com"
],
"key_type": "ecdsa_p256",
"validity_days": 5475
}'import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.issueOriginCert({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body: {
hostnames: [
"example.com",
"*.example.com"
],
key_type: "ecdsa_p256",
validity_days: 5475
}
}, {
reason: "Origin certificate for the web servers"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.issue_origin_cert(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
body={
"hostnames": [
"example.com",
"*.example.com"
],
"key_type": "ecdsa_p256",
"validity_days": 5475
},
reason="Origin certificate for the web servers",
){
"dry_run": false,
"certificate": {
"id": "a5614527-0ce6-43be-ad1d-d012b7394867",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"hostnames": [
"example.com",
"*.example.com"
],
"key_type": "ecdsa_p256",
"serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
"fingerprint_sha256": "example",
"not_before": "2026-10-27T09:30:00Z",
"not_after": "2026-10-27T09:30:00Z",
"validity_days": 0,
"status": "active",
"created_by": "token:11111111-2222-4333-8444-555555555555",
"created_at": "2026-10-27T09:30:00Z",
"revoked_at": "2026-10-27T09:30:00Z",
"revoked_by": null
},
"certificate_pem": "example",
"private_key_pem": "example",
"ca_pem": "example"
}Revoke origin certificate
DELETE /v1/orgs/{org}/zones/{zone}/origin-certs/{cert}Revokes an origin certificate. The edge nodes reject it in full_strict mode from their next configuration, so an origin still serving it gets 526 errors. Revoking it again changes nothing. With dry_run=1 the certificate is only returned.
Required role: tech or higher.
Change controls: dry_run · X-Change-Reason (optional)
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). |
cert | path | string (uuid) | Yes | Certificate id. |
X-Change-Reason | header | string | No | Reason of the change (1-500 UTF-8 characters, no control characters); defaults to the creation of the zone. 1 to 500 characters. |
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: The (revoked) certificate. Returns OriginCert.
| Field | Type | Description |
|---|---|---|
id | string (uuid) | Id. |
zone_id | string (uuid) | Zone id. |
hostnames | array of string | |
key_type | string | csr: the public key of a certificate request was certified. One of ecdsa_p256, rsa2048, csr. |
serial | string | Lowercase hexadecimal without leading zeros. |
fingerprint_sha256 | string | SHA-256 fingerprint of the certificate. |
not_before | string (date-time) | Start of validity. |
not_after | string (date-time) | End of validity. |
validity_days | integer | |
status | string | revoked takes precedence over expired. One of active, revoked, expired. |
created_by | string | Who created it (actor). |
created_at | string (date-time) | Creation time. |
revoked_at | string (date-time) or null | Revocation time (null if active). |
revoked_by | string or null | Who revoked it. |
Errors
| Status | Description |
|---|---|
403 | The token's role or limits do not allow it (forbidden). |
404 | Not found, or not visible to the token (not_found). |
Example
curl -X DELETE "https://dash.openwaf.dev/v1/orgs/$OPENWAF_ORG/zones/6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d/origin-certs/9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e" \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "X-Change-Reason: Server decommissioned"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.revokeOriginCert({
zone: "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
cert: "9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e"
}, {
reason: "Server decommissioned"
});from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.revoke_origin_cert(
zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
cert="9e7c5a3b-1d2f-4e8a-b6c4-3a5d7f9b1c2e",
reason="Server decommissioned",
){
"id": "a5614527-0ce6-43be-ad1d-d012b7394867",
"zone_id": "6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d",
"hostnames": [
"example.com",
"*.example.com"
],
"key_type": "ecdsa_p256",
"serial": "5f3a9c0e7b21d4a68c13e9f02b7d4a61",
"fingerprint_sha256": "example",
"not_before": "2026-10-27T09:30:00Z",
"not_after": "2026-10-27T09:30:00Z",
"validity_days": 0,
"status": "active",
"created_by": "token:11111111-2222-4333-8444-555555555555",
"created_at": "2026-10-27T09:30:00Z",
"revoked_at": "2026-10-27T09:30:00Z",
"revoked_by": null
}Get origin TLS status
GET /v1/orgs/{org}/zones/{zone}/origin-tls/statusFor each proxied name, returns its effective origin TLS settings, what a TLS handshake with the origin shows (without an HTTP request; private, loopback and link-local addresses are skipped) and the 525/526 errors reported by the edge in the last 24 hours. The result is cached for 60 seconds per zone.
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 OriginTLSStatus.
| Field | Type | Description |
|---|---|---|
checked_at | string (date-time) | Last check. |
hosts | array of object | |
hosts[].host | string | |
hosts[].upstream_url | string | Without credentials. |
hosts[].tls_verify | string | One of full, full_strict. |
hosts[].server_name | string | Configured value (empty = the default). |
hosts[].client_cert | boolean | |
hosts[].effective_server_name | string | SNI and verified name: server_name, or the origin host if it is a name, or the site's name if the origin is an IP. |
hosts[].probe | object | |
hosts[].edge_errors_24h | integer | 525/526 events of the edge in the last 24 hours. |
hosts[].last_edge_error | object or null |
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/origin-tls/status" \
-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.getOriginTlsStatus({
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_origin_tls_status(zone="6f1d2c3b-8a4e-4b7d-9c21-5e3f7a8b9c0d"){
"checked_at": "2026-10-27T09:30:00Z",
"hosts": [
{
"host": "www.example.com",
"upstream_url": "https://203.0.113.10",
"tls_verify": "full",
"server_name": "",
"client_cert": false,
"effective_server_name": "example",
"probe": {
"state": "ok",
"error": "",
"issuer": "example",
"subject": "example",
"not_after": "2026-10-27T09:30:00Z",
"days_left": 0,
"name_match": false,
"trusted_by": "public",
"requests_client_cert": false
},
"edge_errors_24h": 0,
"last_edge_error": {
"ts": "2026-10-27T09:30:00Z",
"status": 525,
"msg": ""
}
}
]
}Download the Origin CA
GET /v1/origin-tls/origin-ca.pemReturns, in PEM, every non-expired OpenWAF Origin CA certificate (several during a rotation). In full_strict mode the edge trusts these certificates in addition to the public roots.
Required role: None: public, no token needed.
Response
200: Body of typetext/plain.
Errors
| Status | Description |
|---|---|
503 | unavailable: the Origin CA is not available right now. |
Example
curl "https://dash.openwaf.dev/v1/origin-tls/origin-ca.pem"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getOriginCa();from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.get_origin_ca()-----BEGIN CERTIFICATE-----
MIIB…
-----END CERTIFICATE-----Download the edge client CA
GET /v1/origin-tls/edge-client-ca.pemReturns, in PEM, the CA of the client certificates that the edge presents to origins (mTLS). Configure it on an origin that requires the edge certificate.
Required role: None: public, no token needed.
Response
200: Body of typetext/plain.
Errors
| Status | Description |
|---|---|
503 | unavailable: the Origin CA is not available right now. |
Example
curl "https://dash.openwaf.dev/v1/origin-tls/edge-client-ca.pem"import { OpenWAF } from "./openwaf";
const owf = new OpenWAF({ baseUrl: "https://dash.openwaf.dev" }); // token from OPENWAF_TOKEN
const result = await owf.getEdgeClientCa();from openwaf import OpenWAF
owf = OpenWAF(base_url="https://dash.openwaf.dev") # token from OPENWAF_TOKEN
result = owf.get_edge_client_ca()-----BEGIN CERTIFICATE-----
MIIB…
-----END CERTIFICATE-----