API reference · v2026-09-01

Polfence REST API

Everything in the dashboard, scriptable: hostnames, DNS zones and records, Secure Connect networks and nodes, Zero Trust access, the audit trail, monitors and Shield. Predictable JSON, cursor pagination, an OpenAPI 3.1 document, and a kill switch you can wire into your incident runbook.

Authentication & scopes

Create a key under Dashboard → Developers and send it as a bearer token. Keys act as the user who created them — an org owner's key sees the whole organization, a member's key sees what that member sees.

curl -H "Authorization: Bearer pf_live_4Kq9zB2m…" https://www.polfence.com/api/v1/me
ScopeAllows
readEvery GET: hostnames, zones, records, networks, nodes, applications, grants, audit, monitors, profiles.
writeCreating, changing and deleting hostnames, records, nodes, applications and monitors — plus reading secrets (update tokens, node configs, DoH URLs) via ?include=. Includes read.
adminAccess grants and the zone kill switch. Includes write.
Keys can carry an expiry and are revocable instantly. Every mutation made with a key is written to the audit trail with meta.via = "api" and the key id, so you can always tell automation from humans.

Responses & errors

Every response is JSON with one of two shapes:

success
{ "data": { … } | [ … ], "meta": { … } }
failure
{ "error": { "code": "validation_failed", "message": "Request body failed validation.",
           "details": [ { "path": "ipv4", "message": "Enter a valid IPv4 address.", "code": "custom" } ] } }
Status · codeMeaning
200 / 201Success. The body is { data, meta? }. 201 for creates.
400 bad_request · invalid_json · invalid_queryMalformed JSON, or a query parameter (cursor, limit, since…) we can't parse.
401 unauthorizedNo key, or an unknown / revoked / expired one.
403 forbidden · insufficient_scopeThe key exists but lacks the scope, or asked for a secret without `write`.
404 not_foundThe resource doesn't exist — or isn't yours. The API never confirms other tenants' ids.
409 conflict · not_readyName already taken, duplicate record, CNAME clash, or a config that can't be built yet.
422 validation_failed · plan_limitField-level problems in `details: [{ path, message, code }]`, or a plan allowance hit.
429 rate_limitedMore than 600 requests in the current minute. Wait for Retry-After.
500 internalOur fault. details.requestId matches the X-Request-Id header — quote it to support.

Timestamps are ISO-8601 UTC. Ids are opaque strings. Unknown body fields are rejected (422) rather than ignored, so typos never silently do nothing. Send Content-Type: application/json.

Pagination

List endpoints that can grow (hostnames, records, nodes, audit, monitors) take ?limit= (default 50, max 200 — audit allows 500) and return meta.nextCursor when more exists. Pass it back as ?cursor=. Cursors are opaque and stable across inserts.

GET https://www.polfence.com/api/v1/audit?since=2026-09-01T00:00:00Z&limit=500
→ { "data": [ … 500 events … ], "meta": { "limit": 500, "count": 500, "hasMore": true, "nextCursor": "Y2x4N2F1…" } }

GET https://www.polfence.com/api/v1/audit?since=2026-09-01T00:00:00Z&limit=500&cursor=Y2x4N2F1…

Rate limits

600 requests per minute per key, in fixed one-minute windows. Every response carries the current state; on 429, sleep for Retry-After seconds and retry — nothing was applied.

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetUnix time (seconds) when the window resets.
Retry-AfterOn 429 only: seconds to wait.
X-Request-IdUnique id for this request — include it in support tickets.
X-Polfence-VersionAPI version date the response was produced by.
2 endpoints

Account

Identity, limits and this document.

GET/mescope: read

Who am I

The user and organization behind the key, the key's own scopes/expiry, and plan limits. Use it to verify a key works.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/me"
response 200
{
  "data": {
    "user": {
      "id": "clx7us0k9a8b7c6d5e4f3g2",
      "email": "admin@example.com",
      "name": "Admin",
      "plan": "business",
      "createdAt": "2026-05-01T09:00:00.000Z"
    },
    "organization": {
      "id": "clx7or0k9a8b7c6d5e4f3g2",
      "name": "Example AB",
      "slug": "example-ab-k3f9a",
      "plan": "business",
      "role": "owner"
    },
    "key": {
      "id": "clx7ak0k9a8b7c6d5e4f3g2",
      "name": "Terraform",
      "prefix": "pf_live_4Kq9zB2m",
      "scopes": [
        "read",
        "write"
      ],
      "expiresAt": "2026-12-03T00:00:00.000Z",
      "createdAt": "2026-09-04T07:00:00.000Z"
    },
    "limits": {
      "hostnames": {
        "used": 4,
        "allowed": 250
      },
      "managedZones": {
        "used": 2
      },
      "rateLimit": {
        "perMinute": 600
      }
    }
  }
}

Errors: 401 · 429

GET/openapi.jsonpublic

This document

The OpenAPI 3.1 description of the API. Public — no key needed. Feed it to Swagger UI, Postman, openapi-generator or a Terraform provider generator.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/openapi.json"
response 200
{
  "openapi": "3.1.0",
  "info": {
    "title": "Polfence API",
    "version": "2026-09-01"
  }
}
6 endpoints

Hostnames

Dynamic DNS hostnames under the shared Polfence zones.

GET/hostnamesscope: read

List hostnames

Your dynamic DNS hostnames, newest first. Filter with `?zone=polfence.net`.

Parameters

NameIn · typeDescription
zonequery · stringOnly hostnames under this zone apex.
limitquery · integerPage size (1–200, default 50).
cursorquery · stringOpaque cursor from the previous page's `meta.nextCursor`.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/hostnames"
response 200
{
  "data": [
    {
      "id": "clx7ho0k9a8b7c6d5e4f3g2",
      "fqdn": "office.polfence.net",
      "label": "office",
      "zone": {
        "id": "clx7zo0k9a8b7c6d5e4f3g2",
        "name": "polfence.net"
      },
      "ipv4": "203.0.113.10",
      "ipv6": null,
      "ttl": 60,
      "wildcard": false,
      "paused": false,
      "monitoring": {
        "enabled": true,
        "checkPort": 443,
        "online": true,
        "lastCheckAt": "2026-09-04T08:12:03.000Z",
        "lastLatencyMs": 18,
        "alertsEnabled": true
      },
      "lastIp": "203.0.113.10",
      "lastUpdate": "2026-09-04T07:58:41.000Z",
      "lastSeenAt": "2026-09-04T07:58:41.000Z",
      "createdAt": "2026-06-11T10:02:00.000Z"
    }
  ],
  "meta": {
    "limit": 50,
    "count": 1,
    "hasMore": false,
    "nextCursor": null
  }
}

Errors: 401 · 429

POST/hostnamesscope: write

Create a hostname

Creates `<label>.<zone>`. The response is the only place the update token is returned in full — store it; routers and ddclient use it as the dyndns2 password. Counts against your plan's hostname allowance.

Request body

FieldTypeDescription
label*stringSubdomain label: 2–63 chars, a–z 0–9 and hyphens.
zone*stringShared zone apex to create under (see GET /zones, kind=shared).
ipv4string | null (ipv4)Initial A record.
ipv6string | null (ipv6)Initial AAAA record.
ttlintegerTTL in seconds.default 60
wildcardbooleanAlso answer *.<fqdn>.
checkPortinteger | nullTCP port to probe for uptime; omit for ICMP.
example request
{
  "label": "office",
  "zone": "polfence.net",
  "ipv4": "203.0.113.10",
  "ttl": 60,
  "checkPort": 443
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"office","zone":"polfence.net","ipv4":"203.0.113.10","ttl":60,"checkPort":443}' \
  "https://www.polfence.com/api/v1/hostnames"
response 201 · Created
{
  "data": {
    "id": "clx7ho0k9a8b7c6d5e4f3g2",
    "fqdn": "office.polfence.net",
    "label": "office",
    "zone": {
      "id": "clx7zo0k9a8b7c6d5e4f3g2",
      "name": "polfence.net"
    },
    "ipv4": "203.0.113.10",
    "ipv6": null,
    "ttl": 60,
    "wildcard": false,
    "paused": false,
    "monitoring": {
      "enabled": true,
      "checkPort": 443,
      "online": true,
      "lastCheckAt": "2026-09-04T08:12:03.000Z",
      "lastLatencyMs": 18,
      "alertsEnabled": true
    },
    "lastIp": "203.0.113.10",
    "lastUpdate": "2026-09-04T07:58:41.000Z",
    "lastSeenAt": "2026-09-04T07:58:41.000Z",
    "createdAt": "2026-06-11T10:02:00.000Z",
    "token": "9fZk3Qx2LmN8pRtVwYb4cD",
    "updateUrl": "https://update.polfence.net/nic/update?hostname=office.polfence.net&token=9fZk3Qx2LmN8pRtVwYb4cD"
  }
}

Errors: 401 · 403 · 409 · 422 · 429

GET/hostnames/{id}scope: read

Get a hostname

One hostname with live monitoring state. Add `?include=token` (needs `write`) to get the update token and URL.

Parameters

NameIn · typeDescription
id*path · stringHostname id.
includequery · token`token` adds the update token + URL (requires the write scope).

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/hostnames/{id}"
response 200
{
  "data": {
    "id": "clx7ho0k9a8b7c6d5e4f3g2",
    "fqdn": "office.polfence.net",
    "label": "office",
    "zone": {
      "id": "clx7zo0k9a8b7c6d5e4f3g2",
      "name": "polfence.net"
    },
    "ipv4": "203.0.113.10",
    "ipv6": null,
    "ttl": 60,
    "wildcard": false,
    "paused": false,
    "monitoring": {
      "enabled": true,
      "checkPort": 443,
      "online": true,
      "lastCheckAt": "2026-09-04T08:12:03.000Z",
      "lastLatencyMs": 18,
      "alertsEnabled": true
    },
    "lastIp": "203.0.113.10",
    "lastUpdate": "2026-09-04T07:58:41.000Z",
    "lastSeenAt": "2026-09-04T07:58:41.000Z",
    "createdAt": "2026-06-11T10:02:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 429

PATCH/hostnames/{id}scope: write

Update a hostname

Partial update — only the fields you send change. Setting `ipv4`/`ipv6` records a manual update in the host's activity log. Changing the probe method resets the cached status until the next monitor cycle.

Parameters

NameIn · typeDescription
id*path · stringHostname id.

Request body

FieldTypeDescription
ipv4string | null (ipv4)Set (or null to clear) the A record.
ipv6string | null (ipv6)Set (or null to clear) the AAAA record.
ttlintegerTTL in seconds.
pausedbooleanPause / resume resolution.
wildcardbooleanToggle wildcard.
checkPortinteger | nullTCP probe port; null = ICMP.
monitoringEnabledbooleanfalse opts the host out of probing.
alertsEnabledbooleanDowntime emails.
example request
{
  "ipv4": "203.0.113.11",
  "paused": false,
  "checkPort": 443
}

Example

curl
curl -X PATCH -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ipv4":"203.0.113.11","paused":false,"checkPort":443}' \
  "https://www.polfence.com/api/v1/hostnames/{id}"
response 200
{
  "data": {
    "id": "clx7ho0k9a8b7c6d5e4f3g2",
    "fqdn": "office.polfence.net",
    "label": "office",
    "zone": {
      "id": "clx7zo0k9a8b7c6d5e4f3g2",
      "name": "polfence.net"
    },
    "ipv4": "203.0.113.11",
    "ipv6": null,
    "ttl": 60,
    "wildcard": false,
    "paused": false,
    "monitoring": {
      "enabled": true,
      "checkPort": 443,
      "online": true,
      "lastCheckAt": "2026-09-04T08:12:03.000Z",
      "lastLatencyMs": 18,
      "alertsEnabled": true
    },
    "lastIp": "203.0.113.11",
    "lastUpdate": "2026-09-04T07:58:41.000Z",
    "lastSeenAt": "2026-09-04T07:58:41.000Z",
    "createdAt": "2026-06-11T10:02:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 422 · 429

DELETE/hostnames/{id}scope: write

Delete a hostname

Removes the hostname and its TXT/MX records and history. It stops resolving immediately (subject to caller TTL caches).

Parameters

NameIn · typeDescription
id*path · stringHostname id.

Example

curl
curl -X DELETE -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  "https://www.polfence.com/api/v1/hostnames/{id}"
response 200
{
  "data": {
    "id": "clx7ho0k9a8b7c6d5e4f3g2",
    "deleted": true
  }
}

Errors: 401 · 403 · 404 · 429

POST/hostnames/{id}/rotate-tokenscope: write

Rotate the update token

Issues a new dyndns2 token and invalidates the old one instantly. Update every client that used it. Logged as a warning-level audit event.

Parameters

NameIn · typeDescription
id*path · stringHostname id.

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  "https://www.polfence.com/api/v1/hostnames/{id}/rotate-token"
response 200
{
  "data": {
    "id": "clx7ho0k9a8b7c6d5e4f3g2",
    "fqdn": "office.polfence.net",
    "label": "office",
    "zone": {
      "id": "clx7zo0k9a8b7c6d5e4f3g2",
      "name": "polfence.net"
    },
    "ipv4": "203.0.113.10",
    "ipv6": null,
    "ttl": 60,
    "wildcard": false,
    "paused": false,
    "monitoring": {
      "enabled": true,
      "checkPort": 443,
      "online": true,
      "lastCheckAt": "2026-09-04T08:12:03.000Z",
      "lastLatencyMs": 18,
      "alertsEnabled": true
    },
    "lastIp": "203.0.113.10",
    "lastUpdate": "2026-09-04T07:58:41.000Z",
    "lastSeenAt": "2026-09-04T07:58:41.000Z",
    "createdAt": "2026-06-11T10:02:00.000Z",
    "token": "Xr7wQp2LzK9mB4nV8cT1dF",
    "updateUrl": "https://update.polfence.net/nic/update?hostname=office.polfence.net&token=Xr7wQp2LzK9mB4nV8cT1dF"
  }
}

Errors: 401 · 403 · 404 · 429

7 endpoints

Zones & records

Bring-your-own domains: full record management and the kill switch.

GET/zonesscope: read

List zones

Your managed domains (`kind: managed`) plus the shared Polfence DDNS zones you can create hostnames under (`kind: shared`). Filter with `?kind=`.

Parameters

NameIn · typeDescription
kindquery · managed | sharedmanaged or shared.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/zones"
response 200
{
  "data": [
    {
      "id": "clx7zm0k9a8b7c6d5e4f3g2",
      "name": "example.com",
      "kind": "managed",
      "active": true,
      "verified": true,
      "verifiedAt": "2026-08-25T14:00:00.000Z",
      "registrar": "one.com",
      "locked": false,
      "lockedAt": null,
      "lockReason": null,
      "serial": 42,
      "defaultTtl": 300,
      "nameservers": [
        "ns1.polfence.net",
        "ns2.polfence.net"
      ],
      "recordCount": 12,
      "hostnameCount": null,
      "createdAt": "2026-08-25T13:40:00.000Z"
    },
    {
      "id": "clx7zo0k9a8b7c6d5e4f3g2",
      "name": "polfence.net",
      "kind": "shared",
      "active": true,
      "verified": true,
      "verifiedAt": null,
      "registrar": null,
      "locked": false,
      "lockedAt": null,
      "lockReason": null,
      "serial": 42,
      "defaultTtl": 300,
      "nameservers": [
        "ns1.polfence.net",
        "ns2.polfence.net"
      ],
      "recordCount": null,
      "hostnameCount": 4,
      "createdAt": "2026-08-25T13:40:00.000Z"
    }
  ],
  "meta": {
    "count": 2
  }
}

Errors: 401 · 429

GET/zones/{id}/recordsscope: read

List records

Records in a managed zone, sorted by name then type. Filter with `?type=A&name=www.example.com`.

Parameters

NameIn · typeDescription
id*path · stringZone id (kind=managed).
typequery · A | AAAA | CNAME | MX | TXT | NS | SRV | CAARecord type.
namequery · stringExact full record name.
limitquery · integerPage size (1–200, default 50).
cursorquery · stringOpaque cursor from the previous page's `meta.nextCursor`.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/zones/{id}/records"
response 200
{
  "data": [
    {
      "id": "clx7re0k9a8b7c6d5e4f3g2",
      "zoneId": "clx7zm0k9a8b7c6d5e4f3g2",
      "name": "www.example.com",
      "type": "A",
      "value": "203.0.113.20",
      "ttl": 300,
      "priority": null,
      "weight": null,
      "port": null,
      "createdAt": "2026-08-25T14:05:00.000Z",
      "updatedAt": "2026-08-25T14:05:00.000Z"
    }
  ],
  "meta": {
    "limit": 50,
    "count": 1,
    "hasMore": false,
    "nextCursor": null,
    "zone": "example.com",
    "serial": 42
  }
}

Errors: 401 · 403 · 404 · 429

POST/zones/{id}/recordsscope: write

Create a record

Adds a record and bumps the zone serial — live on the authoritative servers within seconds. A CNAME can't coexist with other records at the same name; identical duplicates are rejected with 409.

Parameters

NameIn · typeDescription
id*path · stringZone id (kind=managed).

Request body

FieldTypeDescription
type*A | AAAA | CNAME | MX | TXT | NS | SRV | CAARecord type.
namestring`@` for the apex, a label like `www`, or a full name inside the zone.default "@"
value*stringIPv4 for A, IPv6 for AAAA, hostname for CNAME/NS/MX/SRV, text for TXT, `issue letsencrypt.org` for CAA.
ttlintegerTTL in seconds.default 300
priorityintegerMX (default 10) / SRV / CAA flags (default 0).
weightintegerSRV weight.
portintegerSRV port.
example request
{
  "type": "A",
  "name": "www",
  "value": "203.0.113.20",
  "ttl": 300
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"A","name":"www","value":"203.0.113.20","ttl":300}' \
  "https://www.polfence.com/api/v1/zones/{id}/records"
response 201 · Created
{
  "data": {
    "id": "clx7re0k9a8b7c6d5e4f3g2",
    "zoneId": "clx7zm0k9a8b7c6d5e4f3g2",
    "name": "www.example.com",
    "type": "A",
    "value": "203.0.113.20",
    "ttl": 300,
    "priority": null,
    "weight": null,
    "port": null,
    "createdAt": "2026-08-25T14:05:00.000Z",
    "updatedAt": "2026-08-25T14:05:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 409 · 422 · 429

GET/zones/{id}/records/{rid}scope: read

Get a record

One record.

Parameters

NameIn · typeDescription
id*path · stringZone id.
rid*path · stringRecord id.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/zones/{id}/records/{rid}"
response 200
{
  "data": {
    "id": "clx7re0k9a8b7c6d5e4f3g2",
    "zoneId": "clx7zm0k9a8b7c6d5e4f3g2",
    "name": "www.example.com",
    "type": "A",
    "value": "203.0.113.20",
    "ttl": 300,
    "priority": null,
    "weight": null,
    "port": null,
    "createdAt": "2026-08-25T14:05:00.000Z",
    "updatedAt": "2026-08-25T14:05:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 429

PATCH/zones/{id}/records/{rid}scope: write

Update a record

Partial update. Fields you omit keep their value; the result is re-validated as a whole and the serial bumps.

Parameters

NameIn · typeDescription
id*path · stringZone id.
rid*path · stringRecord id.

Request body

FieldTypeDescription
typeA | AAAA | CNAME | MX | TXT | NS | SRV | CAARecord type.
namestringNew name.
valuestringNew value.
ttlintegerTTL.
priorityintegerPriority.
weightintegerWeight.
portintegerPort.
example request
{
  "value": "203.0.113.21",
  "ttl": 60
}

Example

curl
curl -X PATCH -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value":"203.0.113.21","ttl":60}' \
  "https://www.polfence.com/api/v1/zones/{id}/records/{rid}"
response 200
{
  "data": {
    "id": "clx7re0k9a8b7c6d5e4f3g2",
    "zoneId": "clx7zm0k9a8b7c6d5e4f3g2",
    "name": "www.example.com",
    "type": "A",
    "value": "203.0.113.21",
    "ttl": 60,
    "priority": null,
    "weight": null,
    "port": null,
    "createdAt": "2026-08-25T14:05:00.000Z",
    "updatedAt": "2026-08-25T14:05:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 422 · 429

DELETE/zones/{id}/records/{rid}scope: write

Delete a record

Removes the record and bumps the serial.

Parameters

NameIn · typeDescription
id*path · stringZone id.
rid*path · stringRecord id.

Example

curl
curl -X DELETE -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  "https://www.polfence.com/api/v1/zones/{id}/records/{rid}"
response 200
{
  "data": {
    "id": "clx7re0k9a8b7c6d5e4f3g2",
    "deleted": true
  }
}

Errors: 401 · 403 · 404 · 429

POST/zones/{id}/lockscope: admin

Emergency kill switch

`locked: true` makes the zone answer nothing but SOA/NS — every name under it goes dark within ~10 seconds (the DNS servers poll lock state). `locked: false` restores it. Idempotent; `meta.changed` tells you whether anything happened. Logged as a critical audit event.

Parameters

NameIn · typeDescription
id*path · stringZone id (kind=managed).

Request body

FieldTypeDescription
locked*booleantrue engages the kill switch, false releases it.
reasonstringShown in the dashboard and the audit trail.
example request
{
  "locked": true,
  "reason": "Phishing kit found on www — SOC ticket 4821"
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"locked":true,"reason":"Phishing kit found on www — SOC ticket 4821"}' \
  "https://www.polfence.com/api/v1/zones/{id}/lock"
response 200
{
  "data": {
    "id": "clx7zm0k9a8b7c6d5e4f3g2",
    "name": "example.com",
    "kind": "managed",
    "active": true,
    "verified": true,
    "verifiedAt": "2026-08-25T14:00:00.000Z",
    "registrar": "one.com",
    "locked": true,
    "lockedAt": "2026-09-04T08:20:00.000Z",
    "lockReason": "Phishing kit found on www — SOC ticket 4821",
    "serial": 43,
    "defaultTtl": 300,
    "nameservers": [
      "ns1.polfence.net",
      "ns2.polfence.net"
    ],
    "recordCount": 12,
    "hostnameCount": null,
    "createdAt": "2026-08-25T13:40:00.000Z"
  },
  "meta": {
    "changed": true
  }
}

Errors: 401 · 403 · 404 · 422 · 429

5 endpoints

Networks & nodes

Secure Connect overlay networks, devices, site routers and servers.

GET/networksscope: read

List networks

Overlay networks in your organization. Empty when the account has no organization.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/networks"
response 200
{
  "data": [
    {
      "id": "clx7ne0k9a8b7c6d5e4f3g2",
      "orgId": "clx7or0k9a8b7c6d5e4f3g2",
      "name": "HQ",
      "cidr": "100.96.4.0/24",
      "topology": "hub",
      "mode": "plain",
      "relay": {
        "id": "clx7lo0k9a8b7c6d5e4f3g2",
        "code": "nl-ams",
        "city": "Amsterdam",
        "country": "NL",
        "endpoint": "nl-ams.relay.polfence.net:51820"
      },
      "dns": "10.0.1.53",
      "nodeCount": 7,
      "applicationCount": 3,
      "createdAt": "2026-07-02T09:00:00.000Z"
    }
  ],
  "meta": {
    "count": 1,
    "organization": {
      "id": "clx7or0k9a8b7c6d5e4f3g2",
      "name": "Example AB"
    }
  }
}

Errors: 401 · 429

GET/networks/{id}/nodesscope: read

List nodes

Devices, site routers and servers in a network with live tunnel status. Filter with `?kind=device|site|server`.

Parameters

NameIn · typeDescription
id*path · stringNetwork id.
kindquery · device | site | serverNode kind.
limitquery · integerPage size (1–200, default 50).
cursorquery · stringOpaque cursor from the previous page's `meta.nextCursor`.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/networks/{id}/nodes"
response 200
{
  "data": [
    {
      "id": "clx7no0k9a8b7c6d5e4f3g2",
      "networkId": "clx7ne0k9a8b7c6d5e4f3g2",
      "name": "HQ router",
      "kind": "site",
      "address": "100.96.4.2",
      "publicKey": "mB7xQ0…=",
      "endpoint": "hq.example.com",
      "port": 51820,
      "enabled": true,
      "lanNat": true,
      "ownerId": "clx7us0k9a8b7c6d5e4f3g2",
      "routes": [
        {
          "id": "clx7ro0k9a8b7c6d5e4f3g2",
          "cidr": "10.0.1.0/24",
          "enabled": true
        }
      ],
      "status": {
        "online": true,
        "lastHandshake": "2026-09-04T08:11:50.000Z",
        "lastIp": "198.51.100.8",
        "rxBytes": 48211233,
        "txBytes": 91733120
      },
      "createdAt": "2026-07-02T09:05:00.000Z"
    }
  ],
  "meta": {
    "limit": 50,
    "count": 1,
    "hasMore": false,
    "nextCursor": null,
    "network": {
      "id": "clx7ne0k9a8b7c6d5e4f3g2",
      "name": "HQ"
    }
  }
}

Errors: 401 · 403 · 404 · 429

POST/networks/{id}/nodesscope: write

Add a node

Polfence assigns the overlay address and generates the key pair. Advertised `routes` are checked for conflicts with the overlay, other networks on the same relay and sibling nodes. Fetch the installable config afterwards with `GET /networks/{id}/nodes/{nodeId}?include=config`.

Parameters

NameIn · typeDescription
id*path · stringNetwork id.

Request body

FieldTypeDescription
name*stringName, e.g. "HQ router" or "Alice — MacBook".
kinddevice | site | serverdevice (a person's machine) · site (router that advertises a LAN) · server.default "device"
routesstring[]
endpointstring | nullPublic hostname/IP of a site/server (no port).
portintegerListen port.default 51820
ownerIdstring | nullDevice nodes: id of the org member the device belongs to (default: you).
example request
{
  "name": "HQ router",
  "kind": "site",
  "routes": [
    "10.0.1.0/24"
  ],
  "endpoint": "hq.example.com"
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"HQ router","kind":"site","routes":["10.0.1.0/24"],"endpoint":"hq.example.com"}' \
  "https://www.polfence.com/api/v1/networks/{id}/nodes"
response 201 · Created
{
  "data": {
    "id": "clx7no0k9a8b7c6d5e4f3g2",
    "networkId": "clx7ne0k9a8b7c6d5e4f3g2",
    "name": "HQ router",
    "kind": "site",
    "address": "100.96.4.2",
    "publicKey": "mB7xQ0…=",
    "endpoint": "hq.example.com",
    "port": 51820,
    "enabled": true,
    "lanNat": true,
    "ownerId": "clx7us0k9a8b7c6d5e4f3g2",
    "routes": [
      {
        "id": "clx7ro0k9a8b7c6d5e4f3g2",
        "cidr": "10.0.1.0/24",
        "enabled": true
      }
    ],
    "status": {
      "online": false,
      "lastHandshake": null,
      "lastIp": null,
      "rxBytes": 0,
      "txBytes": 0
    },
    "createdAt": "2026-07-02T09:05:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 422 · 429

GET/networks/{id}/nodes/{nodeId}scope: read

Get a node

One node. `?include=config` (needs `write`) adds the WireGuard / Perimeter `.conf` to install on it — it contains the private key, treat the response as a secret. Returns 409 `not_ready` while the network has no relay.

Parameters

NameIn · typeDescription
id*path · stringNetwork id.
nodeId*path · stringNode id.
includequery · config`config` adds the installable configuration (requires the write scope).

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/networks/{id}/nodes/{nodeId}"
response 200
{
  "data": {
    "id": "clx7no0k9a8b7c6d5e4f3g2",
    "networkId": "clx7ne0k9a8b7c6d5e4f3g2",
    "name": "HQ router",
    "kind": "site",
    "address": "100.96.4.2",
    "publicKey": "mB7xQ0…=",
    "endpoint": "hq.example.com",
    "port": 51820,
    "enabled": true,
    "lanNat": true,
    "ownerId": "clx7us0k9a8b7c6d5e4f3g2",
    "routes": [
      {
        "id": "clx7ro0k9a8b7c6d5e4f3g2",
        "cidr": "10.0.1.0/24",
        "enabled": true
      }
    ],
    "status": {
      "online": true,
      "lastHandshake": "2026-09-04T08:11:50.000Z",
      "lastIp": "198.51.100.8",
      "rxBytes": 48211233,
      "txBytes": 91733120
    },
    "createdAt": "2026-07-02T09:05:00.000Z",
    "config": "[Interface]\nPrivateKey = …\nAddress = 100.96.4.2/24\n\n[Peer]\nPublicKey = …\nEndpoint = nl-ams.relay.polfence.net:51820\nAllowedIPs = 100.96.4.0/24\nPersistentKeepalive = 25\n"
  }
}

Errors: 401 · 403 · 404 · 409 · 429

DELETE/networks/{id}/nodes/{nodeId}scope: write

Remove a node

Deletes the node and its routes. The relay drops the peer on its next sync (seconds).

Parameters

NameIn · typeDescription
id*path · stringNetwork id.
nodeId*path · stringNode id.

Example

curl
curl -X DELETE -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  "https://www.polfence.com/api/v1/networks/{id}/nodes/{nodeId}"
response 200
{
  "data": {
    "id": "clx7no0k9a8b7c6d5e4f3g2",
    "deleted": true
  }
}

Errors: 401 · 403 · 404 · 429

5 endpoints

Zero Trust access

Applications and per-user access grants (ZTNA).

GET/networks/{id}/applicationsscope: read

List applications

Applications published in a network with grant counts.

Parameters

NameIn · typeDescription
id*path · stringNetwork id.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/networks/{id}/applications"
response 200
{
  "data": [
    {
      "id": "clx7ap0k9a8b7c6d5e4f3g2",
      "networkId": "clx7ne0k9a8b7c6d5e4f3g2",
      "name": "GitLab",
      "address": "10.0.1.20",
      "port": 443,
      "scheme": "https",
      "description": "Source control",
      "grantCount": 4,
      "groupGrantCount": 1,
      "createdAt": "2026-07-03T11:00:00.000Z"
    }
  ],
  "meta": {
    "count": 1,
    "network": {
      "id": "clx7ne0k9a8b7c6d5e4f3g2",
      "name": "HQ"
    }
  }
}

Errors: 401 · 403 · 404 · 429

POST/networks/{id}/applicationsscope: write

Publish an application

Registers an internal resource. Once a network has applications, device nodes only get routes to the apps their owner is granted — least privilege enforced at the tunnel.

Parameters

NameIn · typeDescription
id*path · stringNetwork id.

Request body

FieldTypeDescription
name*stringName.
address*string (ipv4)Internal IPv4, e.g. 10.0.1.20.
portinteger | nullService port.
schemehttps | http | tcp | ssh | rdpProtocol hint.default "https"
descriptionstring | nullDescription.
example request
{
  "name": "GitLab",
  "address": "10.0.1.20",
  "port": 443,
  "scheme": "https",
  "description": "Source control"
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"GitLab","address":"10.0.1.20","port":443,"scheme":"https","description":"Source control"}' \
  "https://www.polfence.com/api/v1/networks/{id}/applications"
response 201 · Created
{
  "data": {
    "id": "clx7ap0k9a8b7c6d5e4f3g2",
    "networkId": "clx7ne0k9a8b7c6d5e4f3g2",
    "name": "GitLab",
    "address": "10.0.1.20",
    "port": 443,
    "scheme": "https",
    "description": "Source control",
    "grantCount": 0,
    "groupGrantCount": 0,
    "createdAt": "2026-07-03T11:00:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 422 · 429

GET/applications/{id}/grantsscope: read

List grants

Direct per-user grants on an application (group grants are managed in the dashboard).

Parameters

NameIn · typeDescription
id*path · stringApplication id.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/applications/{id}/grants"
response 200
{
  "data": [
    {
      "id": "clx7gr0k9a8b7c6d5e4f3g2",
      "applicationId": "clx7ap0k9a8b7c6d5e4f3g2",
      "userId": "clx7us0k9a8b7c6d5e4f3g2",
      "user": {
        "id": "clx7us0k9a8b7c6d5e4f3g2",
        "email": "alice@example.com",
        "name": "Alice Nowak"
      },
      "expiresAt": "2026-09-04T18:00:00.000Z",
      "grantedVia": "manual",
      "grantedBy": "clx7ad0k9a8b7c6d5e4f3g2",
      "createdAt": "2026-09-04T08:00:00.000Z"
    }
  ],
  "meta": {
    "count": 1,
    "application": {
      "id": "clx7ap0k9a8b7c6d5e4f3g2",
      "name": "GitLab"
    }
  }
}

Errors: 401 · 403 · 404 · 429

POST/applications/{id}/grantsscope: admin

Grant access

Grant a user (by id or email — they must already be an org member) access to the application, optionally time-boxed. Re-granting updates the expiry and returns 200 with `meta.updated: true`.

Parameters

NameIn · typeDescription
id*path · stringApplication id.

Request body

FieldTypeDescription
userIdstringGrantee user id (or use `email`).
emailstring (email)Grantee email — must already be an org member.
expiresInMinutesintegerJust-in-time access: revoke automatically after N minutes (5 – 129600). Omit for permanent.
example request
{
  "email": "alice@example.com",
  "expiresInMinutes": 480
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","expiresInMinutes":480}' \
  "https://www.polfence.com/api/v1/applications/{id}/grants"
response 201 · Created (200 when an existing grant was updated)
{
  "data": {
    "id": "clx7gr0k9a8b7c6d5e4f3g2",
    "applicationId": "clx7ap0k9a8b7c6d5e4f3g2",
    "userId": "clx7us0k9a8b7c6d5e4f3g2",
    "user": {
      "id": "clx7us0k9a8b7c6d5e4f3g2",
      "email": "alice@example.com",
      "name": "Alice Nowak"
    },
    "expiresAt": "2026-09-04T18:00:00.000Z",
    "grantedVia": "manual",
    "grantedBy": "clx7ad0k9a8b7c6d5e4f3g2",
    "createdAt": "2026-09-04T08:00:00.000Z"
  }
}

Errors: 401 · 403 · 404 · 422 · 429

DELETE/applications/{id}/grantsscope: admin

Revoke access

Removes a user's direct grant: `DELETE /applications/{id}/grants?userId=…` or `?email=…`. Their tunnel loses the route on the next config refresh.

Parameters

NameIn · typeDescription
id*path · stringApplication id.
userIdquery · stringGrantee user id.
emailquery · string (email)Grantee email (alternative to userId).

Example

curl
curl -X DELETE -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  "https://www.polfence.com/api/v1/applications/{id}/grants"
response 200
{
  "data": {
    "id": "clx7ap0k9a8b7c6d5e4f3g2:clx7us0k9a8b7c6d5e4f3g2",
    "deleted": true
  }
}

Errors: 401 · 403 · 404 · 429

1 endpoint

Audit

The org-wide security audit trail, for SIEM pulls.

GET/auditscope: read

Query the audit trail

Security events newest first — logins, grants, node changes, zone locks, API key use. Org owners/admins get the whole organization (`meta.scope: org`); other users their own events. Built for SIEM pulls: page with `cursor`, bound with `since`/`until`, and persist the last `id` you ingested.

Parameters

NameIn · typeDescription
actionquery · stringExact key (`auth.login`) or prefix wildcard (`grant.*`).
sincequery · string (date-time)ISO-8601 lower bound (inclusive).
untilquery · string (date-time)ISO-8601 upper bound (inclusive).
severityquery · info | warning | criticalinfo | warning | critical
targetTypequery · stringapplication | node | zone | host | user | …
actorquery · stringActor email.
scopequery · org | personal`personal` restricts an org admin to their own events.
limitquery · integerPage size (1–500, default 100).
cursorquery · stringOpaque cursor from the previous page's `meta.nextCursor`.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/audit?action=grant.*"
response 200
{
  "data": [
    {
      "id": "clx7au0k9a8b7c6d5e4f3g2",
      "action": "grant.created",
      "severity": "info",
      "summary": "Granted alice@example.com access to \"GitLab\" via API until 2026-09-04T18:00:00.000Z",
      "actor": {
        "userId": "clx7ad0k9a8b7c6d5e4f3g2",
        "email": "admin@example.com"
      },
      "orgId": "clx7or0k9a8b7c6d5e4f3g2",
      "target": {
        "type": "application",
        "id": "clx7ap0k9a8b7c6d5e4f3g2"
      },
      "ip": "198.51.100.8",
      "country": "SE",
      "userAgent": "terraform-provider-restapi/1.19",
      "meta": {
        "via": "api",
        "keyId": "clx7ak0k9a8b7c6d5e4f3g2",
        "keyName": "Terraform",
        "granteeEmail": "alice@example.com"
      },
      "at": "2026-09-04T08:00:00.000Z"
    }
  ],
  "meta": {
    "limit": 100,
    "count": 1,
    "hasMore": false,
    "nextCursor": null,
    "scope": "org",
    "organization": {
      "id": "clx7or0k9a8b7c6d5e4f3g2",
      "name": "Example AB"
    }
  }
}

Errors: 401 · 429

2 endpoints

Monitoring

Uptime monitors.

GET/monitorsscope: read

List monitors

Uptime monitors with their current state. Filter with `?status=up|down|unknown|paused`.

Parameters

NameIn · typeDescription
statusquery · up | down | unknown | pausedCurrent state.
limitquery · integerPage size (1–200, default 50).
cursorquery · stringOpaque cursor from the previous page's `meta.nextCursor`.

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/monitors"
response 200
{
  "data": [
    {
      "id": "clx7mo0k9a8b7c6d5e4f3g2",
      "name": "Public website",
      "type": "http",
      "target": "https://www.example.com/",
      "intervalSeconds": 300,
      "timeoutMs": 8000,
      "expectedStatus": 200,
      "keyword": null,
      "dnsExpect": null,
      "enabled": true,
      "alertAfterFails": 2,
      "status": {
        "state": "up",
        "lastCheckAt": "2026-09-04T08:10:00.000Z",
        "lastLatencyMs": 143,
        "lastError": null,
        "consecutiveFails": 0,
        "downSince": null
      },
      "createdAt": "2026-08-01T12:00:00.000Z"
    }
  ],
  "meta": {
    "limit": 50,
    "count": 1,
    "hasMore": false,
    "nextCursor": null
  }
}

Errors: 401 · 429

POST/monitorsscope: write

Create a monitor

HTTP, TCP, ping, DNS or keyword probe. Probing starts on the next scheduler cycle; alerts fire after `alertAfterFails` consecutive failures.

Request body

FieldTypeDescription
name*stringName.
typehttp | tcp | ping | dns | keywordProbe type.default "http"
target*stringhttp/keyword: URL · tcp: host:port · ping/dns: hostname or IP.
intervalSecondsintegerInterval.default 300
timeoutMsintegerTimeout.default 8000
expectedStatusinteger | nullhttp: exact status to expect.
keywordstring | nullkeyword: required text.
dnsExpectstring | nulldns: expected answer.
alertAfterFailsintegerFailures before alerting.default 2
enabledbooleanStart enabled.
example request
{
  "name": "Public website",
  "type": "http",
  "target": "https://www.example.com/",
  "intervalSeconds": 300,
  "expectedStatus": 200
}

Example

curl
curl -X POST -H "Authorization: Bearer $POLFENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Public website","type":"http","target":"https://www.example.com/","intervalSeconds":300,"expectedStatus":200}' \
  "https://www.polfence.com/api/v1/monitors"
response 201 · Created
{
  "data": {
    "id": "clx7mo0k9a8b7c6d5e4f3g2",
    "name": "Public website",
    "type": "http",
    "target": "https://www.example.com/",
    "intervalSeconds": 300,
    "timeoutMs": 8000,
    "expectedStatus": 200,
    "keyword": null,
    "dnsExpect": null,
    "enabled": true,
    "alertAfterFails": 2,
    "status": {
      "state": "unknown",
      "lastCheckAt": null,
      "lastLatencyMs": null,
      "lastError": null,
      "consecutiveFails": 0,
      "downSince": null
    },
    "createdAt": "2026-08-01T12:00:00.000Z"
  }
}

Errors: 401 · 403 · 422 · 429

1 endpoint

Shield (DNS filtering)

DNS-over-HTTPS filtering profiles.

GET/shield/profilesscope: read

List filtering profiles

Your DNS filtering profiles plus your organization's. `?include=doh` (needs `write`) adds each profile's DNS-over-HTTPS URL — the secret devices are pointed at.

Parameters

NameIn · typeDescription
includequery · doh`doh` adds the DoH URL (requires the write scope).

Example

curl
curl -H "Authorization: Bearer $POLFENCE_API_KEY" \
  "https://www.polfence.com/api/v1/shield/profiles"
response 200
{
  "data": [
    {
      "id": "clx7sp0k9a8b7c6d5e4f3g2",
      "orgId": "clx7or0k9a8b7c6d5e4f3g2",
      "name": "Office laptops",
      "enabled": true,
      "categories": [
        "malware",
        "phishing",
        "ads"
      ],
      "safeSearch": true,
      "blockNewDomains": false,
      "logQueries": true,
      "logRetentionDays": 30,
      "entryCount": 14,
      "createdAt": "2026-09-01T09:00:00.000Z",
      "updatedAt": "2026-09-03T16:20:00.000Z"
    }
  ],
  "meta": {
    "count": 1
  }
}

Errors: 401 · 403 · 429

Changelog

v2026-09-01 · 2026-09-01

  • Initial public release of /api/v1.
  • Hostnames: list, create, get, update, delete, rotate token.
  • Zones: list managed + shared zones; records CRUD; emergency lock.
  • Networks: list; nodes list/create/get (with config)/delete; applications list/create; per-user grants.
  • Audit trail with cursor pagination and time-window filters.
  • Monitors list/create; Shield profiles list.
  • OpenAPI 3.1 document at /api/v1/openapi.json.

Versioning: additive changes (new fields, endpoints, enum values) ship without notice — write clients that ignore unknown fields. Breaking changes get a new version date announced 90 days ahead via the status page and email.

Ready to automate?

Create a key, run the quick start, and drop the Terraform sample into your repo.

Open Developers
API reference · Polfence