# DNS Zones and records at your DNS providers, templates and dynamic IP sources. Record changes are recorded in the zone history and can be undone. **Common tasks** - [List the records in a zone](#get-organizations-organization-dns-zones-dnszone-dns-records) - [Create a record](#post-organizations-organization-dns-zones-dnszone-dns-records) - [Undo a change](#post-organizations-organization-dns-changes-undo) **Only in the app:** Adding or deleting zones and moving a zone to another provider. **How it works in the app:** [help article](https://infranest.io/docs/dns-records). **Scopes:** Read with `dns.read` or `monitoring.read`, change with `dns.write`. See [Tokens and scopes](../tokens-and-scopes.md#scopes).
[DNS](#dns) 4 · [DNS changes](#dns-changes) 1 · [DNS templates](#dns-templates) 3 · [DNS zones](#dns-zones) 26 · [Dynamic IPs](#dynamic-ips) 4
Every endpoint below takes a token in the `Authorization: Bearer` header and answers JSON. Errors use the shared format in [Errors](../errors.md). Base address: `https://api.infranest.io/api`. ## DNS ### Plan moving records to another server {#get-organizations-organization-dns-address-move}
`GET` `/organizations/{organization}/dns/address-move` **Scope:** `dns.read` · **Changes data:** no Shows what moving A/AAAA records from one server to another would change: each record with its new value, the records left out and why, and the monitors that would follow. Send `from_server_id` or `record_ids`, and optionally `to_server_id`; changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `from_server_id`
query | integer | The server being left. Send this or `record_ids`. | | `record_ids`
query | string | Comma-separated A/AAAA record ids, as the cross-zone search returns them. | | `to_server_id`
query | integer | The server to move to; without it the records are listed with no new values. | | `from_server_id`
body | integer, nullable | This field is required when record_ids is not present. | | `record_ids`
body | string, nullable | This field is required when from_server_id is not present. Must match the regex /^\d+(,\d+)*$/. Must not be greater than 5000 characters. | | `to_server_id`
body | integer, nullable | | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns/address-move" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "from": { "server": { "id": 1, "name": "web-01" }, "addresses": [ "198.51.100.10", "2001:db8:1c1e:5a10::/64" ] }, "to": null, "targets": [ { "id": 13, "name": "analytics-01", "ipv4": "192.0.2.80", "ipv6": null, "ipv6_basis": null }, { "id": 3, "name": "api-01", "ipv4": "198.51.100.20", "ipv6": "2001:db8:1c1e:5a20::1", "ipv6_basis": "default" } ], "records": [ { "record_id": 26, "zone_id": 5, "zone_name": "northwind-docs.com", "zone_provider": "cloudflare", "name": "@", "fqdn": "northwind-docs.com", "type": "A", "value": "198.51.100.10", "ttl": 300, "proxied": true, "family": 4, "new_value": null, "excluded": "read_only", "aliases": [ { "zone_id": 5, "record_id": 28, "fqdn": "api.northwind-docs.com" } ] }, { "record_id": 27, "zone_id": 5, "zone_name": "northwind-docs.com", "zone_provider": "cloudflare", "name": "www", "fqdn": "www.northwind-docs.com", "type": "A", "value": "198.51.100.10", "ttl": 300, "proxied": true, "family": 4, "new_value": null, "excluded": "read_only", "aliases": [] } ], "monitors": { "follow": [], "by_name": [] }, "ttl": { "long": 0, "longest": null, "threshold": 3600, "lowered": 300 } } ```
--- ### List DNS host moves {#get-organizations-organization-dns-moves}
`GET` `/organizations/{organization}/dns/moves` **Scope:** `dns.read` · **Changes data:** no The 200 most recent moves of a domain’s DNS to another DNS host, newest first, each with its domain, target connection, current `state` and timestamps. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns/moves" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "data": [] } ```
--- ### Get the DNS overview {#get-organizations-organization-dns-overview}
`GET` `/organizations/{organization}/dns/overview` **Scope:** `dns.read` · **Changes data:** no A summary across all your DNS zones: what needs your attention, grouped by cause, the recent changes, where your DNS is hosted and what is worth tidying up. Changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns/overview" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "summary": { "state": "needs_you", "critical": false, "zones": 13, "needing_you": 2, "fine": 11, "live_match": 12, "live_checked": 12, "records": 88, "hosts": 6, "last_synced_at": "2026-01-15T11:53:00+00:00" }, "needs": [ { "key": "advisor:6:dnssec", "cause": "advisor", "critical": false, "subject": "northwind.de", "zones": [ { "id": 6, "name": "northwind.de" } ], "params": [], "since": null, "due_at": null, "route": "/dns/6", "finding": { "id": "dnssec", "status": "error", "variant": "error" }, "halt": null, "attention_keys": [ "dnssec_broken:9" ] }, { "key": "advisor:3:cf_ssl_mode", "cause": "advisor", "critical": false, "subject": "northwind-app.dev", "zones": [ { "id": 3, "name": "northwind-app.dev" } ], "params": [], "since": null, "due_at": null, "route": "/dns/3", "finding": { "id": "cf_ssl_mode", "status": "error", "value": "flexible" }, "halt": null, "attention_keys": [ "dns_zone_finding:3:cf_ssl_mode" ] } ], "decided": [], "changes": { "from": "2026-01-08T12:00:00+00:00", "entries": [ { "key": "audit:2", "kind": "audit", "action": "dns_record.created", "at": "2026-01-15T07:00:00+00:00", "zones": [ { "id": null, "name": "" } ], "actor": { "type": "user", "name": "Sam Okoye" }, "outside": false, "count": 1, "params": { "name": "@", "type": "" }, "batch": null, "writable": false, "provider": null }, { "key": "audit:8", "kind": "audit", "action": "dns_record.updated", "at": "2026-01-12T11:00:00+00:00", "zones": [ { "id": null, "name": "" } ], "actor": { "type": "user", "name": "Tom Bakker" }, "outside": false, "count": 1, "params": { "name": "@", "type": "" }, "batch": null, "writable": false, "provider": null } ], "truncated": false }, "hosts": [ { "key": "cred:2", "name": "Cloudflare DNS", "provider": "cloudflare", "credential_id": 2, "zones": 5, "connected": true, "halt": null }, { "key": "cred:1", "name": "Hetzner — Production", "provider": "hetzner", "credential_id": 1, "zones": 2, "connected": true, "halt": null } ], "domains_without_zone": 5, "recommendations": [] } ```
--- ### Search records across all zones {#get-organizations-organization-dns-search}
`GET` `/organizations/{organization}/dns/search` **Scope:** `dns.read` · **Changes data:** no Finds DNS records in any of your zones that match the `q` query parameter, by name, value or address, grouped by zone with the reason each one matched. A query shorter than two characters returns nothing with `too_short` set; at most 100 records are returned, with `truncated` set when there were more. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns/search" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "query": "", "too_short": true, "min_length": 2, "total": 0, "truncated": false, "zones": [], "address_record_ids": [] } ```
## DNS changes ### Undo a DNS change {#post-organizations-organization-dns-changes-undo}
`POST` `/organizations/{organization}/dns-changes/undo` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Undoes one recorded change (`change_id`) or every change in a batch (`batch_id`), even across zones, pushing each one to the DNS provider. A change whose record has changed since, or that a dynamic IP keeps up to date, is skipped with its reason; answers 404 when nothing matches. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `change_id`
body | integer | The change to undo. Send this or `batch_id`. | | `batch_id`
body | string | Every change in this batch, undone as one. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`idempotency_in_progress`](../errors.md#idempotency_in_progress), [`validation_failed`](../errors.md#validation_failed), [`idempotency_key_reused`](../errors.md#idempotency_key_reused), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X POST "https://api.infranest.io/api/organizations/42/dns-changes/undo" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"change_id":42,"batch_id":"9b2c6a1e-3f7d-4a8e-9c1b-2d3e4f5a6b7c"}' ```
## DNS templates ### List DNS templates {#get-organizations-organization-dns-templates}
`GET` `/organizations/{organization}/dns-templates` **Scope:** `dns.read` · **Changes data:** no The built-in DNS templates followed by your organization’s custom templates, each with its records, plus the TTL values a template can use. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-templates" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "builtin": [ { "id": null, "slug": "google-workspace", "name": "Google Workspace", "description": "MX records, SPF, and DMARC starter for Google Workspace email.", "is_builtin": true, "records": [ { "type": "MX", "name": "@", "value": "aspmx.l.google.com.", "ttl": 3600, "priority": 1 }, { "type": "MX", "name": "@", "value": "alt1.aspmx.l.google.com.", "ttl": 3600, "priority": 5 } ] }, { "id": null, "slug": "microsoft-365", "name": "Microsoft 365", "description": "MX record, SPF, Autodiscover CNAME, and DMARC starter for Microsoft 365. The MX is filled in from the domain.", "is_builtin": true, "records": [ { "type": "MX", "name": "@", "value": "{zone_dashed}.mail.protection.outlook.com.", "ttl": 3600, "priority": 0 }, { "type": "TXT", "name": "@", "value": "v=spf1 include:spf.protection.outlook.com -all", "ttl": 3600 } ] } ], "custom": [ { "id": 1, "organization_id": 1, "name": "Google Workspace mail", "description": "MX, SPF and DKIM for a domain that sends mail through Google Workspace.", "slug": "google-workspace-mail", "is_builtin": false, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "deleted_at": null, "purge_after": null, "purge_reminder_sent_at": null, "deleted_by_id": null, "deleted_by_label": null, "deletion_reason": null, "records": [ { "id": 1, "dns_template_id": 1, "type": "MX", "name": "@", "value": "aspmx.l.google.com", "ttl": 3600, "priority": 1, "proxied": false, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z" }, { "id": 2, "dns_template_id": 1, "type": "MX", "name": "@", "value": "alt1.aspmx.l.google.com", "ttl": 3600, "priority": 5, "proxied": false, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z" } ] } ], "ttl_options": [ 1, 30 ] } ```
--- ### Apply a template to a zone {#post-organizations-organization-dns-templates-templatekey-apply-zone}
`POST` `/organizations/{organization}/dns-templates/{templateKey}/apply/{zone}` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Creates or updates the template’s records in the zone and pushes each change to the DNS provider; with `overwrite=false`, records that already exist with the same type and name are left alone. `dry_run=1` returns the changes without making any. A CAA record that would block a certificate’s renewal is refused with 409 unless `confirm_caa_impact` is sent. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `templateKey`
path · required | string | The templateKey. | | `zone_id`
path · required | integer | The zone's ID. | | `dry_run`
query | boolean | Answer with the changes this would make, and make none. | | `overwrite`
body | boolean, nullable | | | `confirm_caa_impact`
body | boolean, nullable | Apply even though a template CAA record blocks a certificate's renewing CA — after a 409. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`idempotency_in_progress`](../errors.md#idempotency_in_progress), [`validation_failed`](../errors.md#validation_failed), [`idempotency_key_reused`](../errors.md#idempotency_key_reused), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X POST "https://api.infranest.io/api/organizations/42/dns-templates/1/apply/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"overwrite":true,"confirm_caa_impact":true}' ``` **Response** · 200 ```json { "added": 1, "overwritten": 0, "failed": 0, "error": null, "skipped": 2, "skipped_reason": "The A record \"@\" needs a value the template cannot know ({server_ipv4}). Add it yourself. The AAAA record \"@\" needs a value the template cannot know ({server_ipv6}). Add it yourself.", "message": "Applied Static Site: 1 records added, 0 updated. 2 skipped: The A record \"@\" needs a value the template cannot know ({server_ipv4}). Add it yourself. The AAAA record \"@\" needs a value the template cannot know ({server_ipv6}). Add it yourself." } ```
--- ### Preview applying a template {#get-organizations-organization-dns-templates-templatekey-preview-zone}
`GET` `/organizations/{organization}/dns-templates/{templateKey}/preview/{zone}` **Scope:** `dns.read` · **Changes data:** no Shows each of the template’s records for the zone with its status (such as `add`, `overwrite` or `unchanged`), the existing value it would replace and the TTL the zone will actually get. `templateKey` is a built-in template’s slug or a custom template’s id; changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `templateKey`
path · required | string | The templateKey. | | `zone_id`
path · required | integer | The zone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-templates/1/preview/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json [ { "type": "A", "name": "@", "value": "{server_ipv4}", "ttl": 3600, "priority": null, "proxied": false, "unfilled": "{server_ipv4}", "status": "skipped", "existing_value": null, "reason": "The A record \"@\" needs a value the template cannot know ({server_ipv4}). Add it yourself." }, { "type": "AAAA", "name": "@", "value": "{server_ipv6}", "ttl": 3600, "priority": null, "proxied": false, "unfilled": "{server_ipv6}", "status": "skipped", "existing_value": null, "reason": "The AAAA record \"@\" needs a value the template cannot know ({server_ipv6}). Add it yourself." } ] ```
## DNS zones ### List DNS zones {#get-organizations-organization-dns-zones}
`GET` `/organizations/{organization}/dns-zones` **Scope:** `dns.read` · **Changes data:** no Your DNS zones, paginated with `page` and `per_page` (up to 200), each with its provider, record count, tags and delegation state. Filter with `search`, `credential`, `tag` or `status` (`active`, `stale`, `drift` for records changed at the provider, or `security`) and sort with `sort` (`zone`, `provider`, `records`, `synced`) and `dir`; `meta.counts` holds the totals. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "current_page": 1, "data": [ { "id": 11, "organization_id": 1, "domain_id": 20, "name": "northwind-api.dev", "provider": "openprovider", "provider_zone_id": "zone-210", "status": "pending", "last_synced_at": "2026-01-15T10:24:00.000000Z", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "provider_credential_id": 7, "ns_live": [ "ns1.openprovider.nl", "ns2.openprovider.be" ], "ns_checked_at": "2026-01-15T11:53:00.000000Z", "security_status": "info", "ns_mismatch_since": null, "provider_modified_at": null, "price_monthly": "0.00", "price_source": "manual", "price_currency": "EUR", "price_amount": "0.0000", "billing_interval": "monthly", "interval_count": 1, "live_mismatch_count": 0, "live_checked_at": "2026-01-15T11:53:00.000000Z", "live_mismatch_since": null, "dnssec_pending": null, "dnssec_pending_since": null, "provider_settings": null, "provider_settings_checked_at": null, "default_ttl": null, "registrar_name": null, "registrar_checked_at": null, "records_count": 7, "custom_fields": [], "delegation": { "state": "matches", "reason": null, "live": [ "ns1.openprovider.nl", "ns2.openprovider.be" ], "dnssec": null, "belongs_to": "openprovider", "checked_at": "2026-01-15T11:53:00.000000Z" }, "can_sign": false, "sync_halt": null, "provider_credential": { "id": 7, "organization_id": 1, "provider": "openprovider", "label": "Openprovider — Reseller", "enabled": true, "verified_at": "2025-10-15T12:00:00.000000Z", "last_synced_at": "2026-01-15T11:48:00.000000Z", "reconnect_required_at": null, "refused_at": null }, "tags": [] }, { "id": 3, "organization_id": 1, "domain_id": 3, "name": "northwind-app.dev", "provider": "cloudflare", "provider_zone_id": "zone-202", "status": "active", "last_synced_at": "2026-01-15T11:48:00.000000Z", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "provider_credential_id": 2, "ns_live": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ], "ns_checked_at": "2026-01-15T11:53:00.000000Z", "security_status": "error", "ns_mismatch_since": null, "provider_modified_at": null, "price_monthly": "4.00", "price_source": "manual", "price_currency": "EUR", "price_amount": "4.0000", "billing_interval": "monthly", "interval_count": 1, "live_mismatch_count": 0, "live_checked_at": "2026-01-15T11:53:00.000000Z", "live_mismatch_since": null, "dnssec_pending": null, "dnssec_pending_since": null, "provider_settings": { "ssl": "flexible", "min_tls_version": "1.0", "security_header": { "strict_transport_security": { "enabled": false } }, "always_use_https": "off", "development_mode": "on", "development_mode_expires_at": "2026-01-15T14:00:00.000000Z" }, "provider_settings_checked_at": "2026-01-15T11:40:00.000000Z", "default_ttl": null, "registrar_name": null, "registrar_checked_at": null, "records_count": 8, "custom_fields": [], "delegation": { "state": "matches", "reason": null, "live": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ], "dnssec": null, "belongs_to": "cloudflare", "checked_at": "2026-01-15T11:53:00.000000Z" }, "can_sign": true, "sync_halt": null, "provider_credential": { "id": 2, "organization_id": 1, "provider": "cloudflare", "label": "Cloudflare DNS", "enabled": true, "verified_at": "2025-10-15T12:00:00.000000Z", "last_synced_at": "2026-01-15T11:48:00.000000Z", "reconnect_required_at": null, "refused_at": null }, "tags": [] } ], "first_page_url": "http://localhost/api/organizations/1/dns-zones?page=1", "from": 1, "last_page": 1, "last_page_url": "http://localhost/api/organizations/1/dns-zones?page=1", "links": [ { "url": null, "label": "« Previous", "page": null, "active": false }, { "url": "http://localhost/api/organizations/1/dns-zones?page=1", "label": "1", "page": 1, "active": true } ], "next_page_url": null, "path": "http://localhost/api/organizations/1/dns-zones", "per_page": 25, "prev_page_url": null, "to": 13, "total": 13, "meta": { "locked_ids": [], "counts": { "total": 13, "active": 12, "stale": 0, "drift": 0, "security": 4 }, "integrations": [ { "id": 2, "provider": "cloudflare", "label": "Cloudflare DNS" }, { "id": 4, "provider": "dynadot", "label": "Dynadot Registrar" } ], "tags": [], "custom_fields": [] } } ```
--- ### Get a DNS zone {#get-organizations-organization-dns-zones-dnszone}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}` **Scope:** `dns.read` · **Changes data:** no One zone with all its records, its provider connection, whether record changes can be pushed to the provider, its delegation and DNSSEC details, and findings such as records pointing at released addresses. Opening it may queue fresh nameserver and live-DNS checks in the background. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "id": 1, "organization_id": 1, "domain_id": 1, "name": "northwind.com", "provider": "cloudflare", "provider_zone_id": "zone-200", "status": "active", "last_synced_at": "2026-01-15T11:48:00.000000Z", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "provider_credential_id": 2, "ns_live": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ], "ns_checked_at": "2026-01-15T11:53:00.000000Z", "security_status": "warn", "ns_mismatch_since": null, "provider_modified_at": null, "price_monthly": "4.00", "price_source": "manual", "price_currency": "EUR", "price_amount": "4.0000", "billing_interval": "monthly", "interval_count": 1, "live_mismatch_count": 0, "live_checked_at": "2026-01-15T11:53:00.000000Z", "live_mismatch_since": null, "dnssec_pending": null, "dnssec_pending_since": null, "provider_settings": { "ssl": "strict", "min_tls_version": "1.2", "security_header": { "strict_transport_security": { "enabled": true } }, "always_use_https": "on", "development_mode": "off" }, "provider_settings_checked_at": "2026-01-15T11:40:00.000000Z", "default_ttl": null, "registrar_name": null, "registrar_checked_at": null, "records_count": 10, "sync_halt": null, "records": [ { "id": 1, "dns_zone_id": 1, "type": "A", "name": "@", "value": "198.51.100.10", "ttl": 300, "priority": null, "provider_record_id": "rec-200-0", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": true, "live_values": null, "live_checked_at": null, "value_is_placeholder": false, "live_mismatch": null }, { "id": 2, "dns_zone_id": 1, "type": "A", "name": "www", "value": "198.51.100.10", "ttl": 300, "priority": null, "provider_record_id": "rec-200-1", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": true, "live_values": null, "live_checked_at": null, "value_is_placeholder": false, "live_mismatch": null } ], "provider_credential": { "id": 2, "organization_id": 1, "provider": "cloudflare", "label": "Cloudflare DNS", "verified_at": "2025-10-15T12:00:00.000000Z", "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "enabled": true, "enabled_capabilities": null, "disabled_capabilities": null, "error_email_enabled": true, "disabled_operations": null, "alerts_muted_until": null, "spending_capacity": null, "spending_checked_at": null, "scope_check": null, "scope_checked_at": null, "removing_at": null, "last_synced_at": "2026-01-15T11:48:00.000000Z", "reconnect_required_at": null, "account_identity": null, "refused_at": null, "refusal_checked_at": null }, "tags": [], "record_types": [ "A", "AAAA" ], "ttl_options": [ 1, 120 ], "can_push_records": false, "delegation": { "state": "matches", "reason": null, "live": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ], "dnssec": false, "belongs_to": "cloudflare", "checked_at": "2026-01-15T11:53:00.000000Z" }, "proxiable_record_types": [], "can_sign": true, "signing_reason": null, "dnssec_plan": { "shape": "sign_then_paste", "dns": { "provider": "cloudflare", "can_sign": true, "exposes_key": true, "reason": null }, "registrar": { "provider": "Namecheap", "known": true, "can_publish": false, "help_url": null, "fields": [ "key_tag", "flags" ] } }, "managed_hostnames": [], "posture": [ { "id": "cf_ssl_mode", "status": "pass", "value": "strict" }, { "id": "cf_min_tls", "status": "pass", "value": "1.2" } ], "dangling_addresses": [], "unclaimed_cnames": [], "live": { "mismatches": 0, "checked_at": "2026-01-15T11:53:00.000000Z", "mismatch_since": null } } ```
--- ### Get a zone’s advisor findings {#get-organizations-organization-dns-zones-dnszone-advisor}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/advisor` **Scope:** `dns.read` · **Changes data:** no Findings about the zone’s stored records, such as missing or weak email protection, with the snapshot those findings were judged from. Changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/advisor" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "snapshot": { "spf": [ "v=spf1 include:_spf.northwind.com ~all" ], "dmarc": [ "v=DMARC1; p=quarantine; rua=mailto:dmarc@northwind.example" ], "dkim_selectors": [], "mx": [ "mx.northwind.com" ], "null_mx": false, "mail_provider": null, "mx_cname_targets": [], "apex_a": [ "198.51.100.10" ], "apex_aaaa": [], "apex_cname": [], "caa": [ "0 issue \"letsencrypt.org\"" ], "caa_required": { "certificates": [ { "id": 1, "subject": "northwind.com", "issuer": "Let's Encrypt (R3)", "ids": [ "letsencrypt.org" ], "wildcard": false, "renews_itself": true, "provider": false }, { "id": 13, "subject": "cdn.northwind.com", "issuer": "Let's Encrypt (R3)", "ids": [ "letsencrypt.org" ], "wildcard": false, "renews_itself": true, "provider": false } ], "expected": [], "fronts": [ { "key": "cloudflare", "label": "Cloudflare", "ids": [ "letsencrypt.org", "pki.goog" ], "publishes_own": true } ] }, "wildcard_a": [], "wildcard_aaaa": [], "cf_exposed_ips": [ "198.51.100.10" ], "mta_sts": [], "bimi": [], "apex_ns": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ], "ns_matches_provider": true, "mail_subdomains_missing_spf": [] }, "findings": [ { "id": "ns", "status": "pass", "variant": "pass", "values": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ] }, { "id": "dnssec", "status": "pass", "variant": "pass" } ] } ```
--- ### Preview CAA protection for a zone {#get-organizations-organization-dns-zones-dnszone-caa-protection}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/caa-protection` **Scope:** `dns.read` · **Changes data:** no Shows the CAA records that would be added, each of the zone’s certificates and whether it stays renewable, or why a change is refused. `live=1` also checks each record against public DNS. Changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `live`
query | boolean | Also check each record to add against public DNS. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/caa-protection" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "zone_id": 1, "zone": "northwind.com", "route": "here", "where": { "label": "Cloudflare", "url": null }, "records": [ { "key": "kept:7", "action": "none", "type": "CAA", "name": "@", "value": "0 issue \"letsencrypt.org\"", "current": null, "record_id": null, "reason": "caa_kept", "params": [] }, { "key": "issue:globalsign.com", "action": "create", "type": "CAA", "name": "@", "value": "0 issue \"globalsign.com\"", "current": null, "record_id": null, "reason": "caa_cert", "params": { "subject": "shop.northwind.com" } } ], "refusals": [], "certificates": [ { "id": 1, "subject": "northwind.com", "issuer": "Let's Encrypt (R3)", "ca": "letsencrypt.org", "wildcard": false, "state": "stays_renewable" }, { "id": 13, "subject": "cdn.northwind.com", "issuer": "Let's Encrypt (R3)", "ca": "letsencrypt.org", "wildcard": false, "state": "stays_renewable" } ], "fronts": [ { "label": "Cloudflare", "ids": [ "letsencrypt.org", "pki.goog" ], "publishes_own": true } ] } ```
--- ### List a zone’s recorded changes {#get-organizations-organization-dns-zones-dnszone-changes}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/changes` **Scope:** `dns.read` · **Changes data:** no The zone’s record changes from its last 50 operations, newest first, grouped by the operation that made each batch, each with whether it can still be undone or restored to. Also returns a merged timeline and how many days of changes your plan keeps. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/changes" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "batches": [], "timeline": [ { "kind": "audit", "entry": { "id": 21, "action": "dns_zone.exported", "meta": { "zone": "northwind.com", "format": "bind", "records": 10 }, "user_id": 1, "created_at": "2026-01-15T12:00:00.000000Z" } } ], "tracked_since": null, "writable": false, "retention_days": 90, "dynamic_ip_retention_days": 7 } ```
--- ### List records in a zone {#get-organizations-organization-dns-zones-dnszone-dns-records}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records` **Scope:** `dns.read` · **Changes data:** no Every record in the zone, ordered by type and name, with the dynamic IP source that keeps it up to date, if any. Filter by record type with `type`. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json [ { "id": 1, "dns_zone_id": 1, "type": "A", "name": "@", "value": "198.51.100.10", "ttl": 300, "priority": null, "provider_record_id": "rec-200-0", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": true, "live_values": null, "live_checked_at": null, "value_is_placeholder": false, "live_mismatch": null, "dynamic_ip_target": null }, { "id": 88, "dns_zone_id": 1, "type": "A", "name": "archive", "value": "198.51.100.11", "ttl": 300, "priority": null, "provider_record_id": "demo-certificate-archive", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": false, "live_values": [ "198.51.100.11" ], "live_checked_at": "2026-01-15T11:53:00.000000Z", "value_is_placeholder": false, "live_mismatch": false, "dynamic_ip_target": null } ] ```
--- ### Create a record {#post-organizations-organization-dns-zones-dnszone-dns-records}
`POST` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Creates a record in the zone and pushes it to the DNS provider; `dry_run=1` returns the change without making it. Refused with 422 when the zone is read-only or an A/AAAA value is an address your fleet released, and with 409 when an apex CAA record would block a certificate’s renewal, unless `confirm_caa_impact` is sent. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `dry_run`
query | boolean | Answer with the change this would make, and make none. | | `type`
body · required | string | One of: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS`, `SOA`, `SRV`, `CAA`, `PTR`, `ALIAS`, `CERT`, …. | | `name`
body | string, nullable | Must not be greater than 255 characters. | | `value`
body · required | string | Must not be greater than 65535 characters. | | `ttl`
body | integer | Must be at least 60. Must not be greater than 86400. | | `priority`
body | integer, nullable | Must be at least 0. Must not be greater than 65535. | | `proxied`
body | boolean | | | `comment`
body | string, nullable | Must not be greater than 500 characters. | | `confirm_caa_impact`
body | boolean | Save an apex CAA record even though it blocks the CA a certificate renews from — send after a 409 `caa_blocks_renewal`. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`idempotency_in_progress`](../errors.md#idempotency_in_progress), [`validation_failed`](../errors.md#validation_failed), [`idempotency_key_reused`](../errors.md#idempotency_key_reused), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X POST "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"type":"A","name":"api","value":"192.0.2.20","ttl":3600}' ``` **Response** · 201 — Created ```json { "id": 91, "dns_zone_id": 14, "type": "A", "name": "api", "value": "192.0.2.20", "ttl": 3600, "priority": null, "provider_record_id": null, "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": false, "live_values": null, "live_checked_at": null, "value_is_placeholder": false, "live_mismatch": null } ``` **409** — The CAA change would block a certificate's renewing CA ```json { "message": "This CAA change would block the certificate authority that renews status.example.com. Their next renewal would fail.", "error_type": "caa_blocks_renewal", "certificates": [ { "id": 12, "subject": "status.example.com", "ca": "letsencrypt.org" } ] } ```
--- ### Get a record {#get-organizations-organization-dns-zones-dnszone-dns-records-record}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records/{record}` **Scope:** `dns.read` · **Changes data:** no One record in the zone. Answers 404 when the record belongs to another zone. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `record_id`
path · required | integer | The record's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "id": 1, "dns_zone_id": 1, "type": "A", "name": "@", "value": "198.51.100.10", "ttl": 300, "priority": null, "provider_record_id": "rec-200-0", "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": true, "live_values": null, "live_checked_at": null, "value_is_placeholder": false, "live_mismatch": null } ```
--- ### Update a record {#put-organizations-organization-dns-zones-dnszone-dns-records-record}
`PUT` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records/{record}` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Changes the record and pushes the change to the DNS provider; `dry_run=1` returns the change without making it. Refused with 422 when the zone is read-only or a new A/AAAA value is an address your fleet released, and with 409 when an apex CAA change would block a certificate’s renewal, unless `confirm_caa_impact` is sent. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `record_id`
path · required | integer | The record's ID. | | `dry_run`
query | boolean | Answer with the change this would make, and make none. | | `type`
body | string | One of: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS`, `SOA`, `SRV`, `CAA`, `PTR`, `ALIAS`, `CERT`, …. | | `name`
body | string, nullable | Must not be greater than 255 characters. | | `value`
body | string | Must not be greater than 65535 characters. | | `ttl`
body | integer | Must be at least 60. Must not be greater than 86400. | | `priority`
body | integer, nullable | Must be at least 0. Must not be greater than 65535. | | `proxied`
body | boolean | | | `comment`
body | string, nullable | Must not be greater than 500 characters. | | `confirm_caa_impact`
body | boolean | Save an apex CAA change even though it blocks the CA a certificate renews from — send after a 409 `caa_blocks_renewal`. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`validation_failed`](../errors.md#validation_failed), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X PUT "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"type":"A","name":"www","value":"192.0.2.11","ttl":3600}' ``` **Response** · 200 ```json { "id": 89, "dns_zone_id": 14, "type": "A", "name": "www", "value": "192.0.2.11", "ttl": 3600, "priority": null, "provider_record_id": null, "deleted_at": null, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "comment": null, "proxied": false, "live_values": null, "live_checked_at": null, "value_is_placeholder": false, "live_mismatch": null } ``` **409** — The CAA change would block a certificate's renewing CA ```json { "message": "This CAA change would block the certificate authority that renews status.example.com. Their next renewal would fail.", "error_type": "caa_blocks_renewal", "certificates": [ { "id": 12, "subject": "status.example.com", "ca": "letsencrypt.org" } ] } ```
--- ### Delete a record {#delete-organizations-organization-dns-zones-dnszone-dns-records-record}
`DELETE` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records/{record}` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Deletes the record at the DNS provider first, then in InfraNest; if the provider refuses, the record is kept and 502 is returned. Supports `dry_run=1`, and `confirm_caa_impact=1` after a 409 for an apex CAA record whose removal would block a certificate’s renewal. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `record_id`
path · required | integer | The record's ID. | | `confirm_caa_impact`
query | boolean | Delete an apex CAA record even though that blocks the CA a certificate renews from — send after a 409 `caa_blocks_renewal`. | | `dry_run`
query | boolean | Answer with the change this would make, and make none. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X DELETE "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 204 — no content **409** — The CAA change would block a certificate's renewing CA ```json { "message": "This CAA change would block the certificate authority that renews status.example.com. Their next renewal would fail.", "error_type": "caa_blocks_renewal", "certificates": [ { "id": 12, "subject": "status.example.com", "ca": "letsencrypt.org" } ] } ```
--- ### Set a record’s label {#put-organizations-organization-dns-zones-dnszone-dns-records-record-label}
`PUT` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records/{record}/label` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Sets or clears a note on what the record is for (`label`, up to 120 characters; empty or null clears it). Kept in InfraNest only and never written to the DNS provider; a labelled record is never proposed as a leftover. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `record_id`
path · required | integer | The record's ID. | | `label`
body | string, nullable | What the record is for, up to 120 characters. Empty or null clears it. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`validation_failed`](../errors.md#validation_failed), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X PUT "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records/1/label" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"label":"Main website"}' ``` **Response** · 200 ```json { "label": "Main website", "points_at": { "kind": "custom", "label": "Main website", "url": null, "flagged": false, "since": null, "provider": null, "note": null, "ours": false } } ```
--- ### Check a record’s propagation {#get-organizations-organization-dns-zones-dnszone-dns-records-record-propagation}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/dns-records/{record}/propagation` **Scope:** `dns.read` · **Changes data:** no Asks public DNS resolvers for the record and reports, per resolver, whether its stored value is live yet; `tier=fast` uses only the quicker resolvers. With `landing=1` it instead reports whether the record’s latest change (or the one in `change_id`) has taken effect, and `landing` is null when nothing changed recently. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `record_id`
path · required | integer | The record's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/dns-records/1/propagation" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ```
--- ### Get a zone’s DNSSEC status {#get-organizations-organization-dns-zones-dnszone-dnssec}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/dnssec` **Scope:** `dns.read` · **Changes data:** no Reads the zone’s DNSSEC state from its DNS provider, including the DS details to add at your registrar when signing is waiting on that step. `status` is null when the provider cannot sign the zone or its state could not be read. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/dnssec" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "status": null } ```
--- ### Preview email protection for a zone {#get-organizations-organization-dns-zones-dnszone-email-protection}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/email-protection` **Scope:** `dns.read` · **Changes data:** no Shows each email record (such as SPF and DMARC) as it is and as it would be, or why a change is refused. `mode` is required: `protect` for a domain that receives mail, `no_mail` for one that sends none; `live=1` also checks against public DNS. Changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `mode`
query · required | string | `protect` (a domain that receives mail) or `no_mail`. | | `provider`
query | string | A mail provider key, when MX names nobody with an SPF include. | | `live`
query | boolean | Also check each record against public DNS, and look for the DKIM key there. | | `dkim_target`
query | string | Microsoft 365: the selector1 CNAME target as Defender shows it. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/email-protection?mode=protect" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "zone_id": 1, "zone": "northwind.com", "mode": "no_mail", "route": "here", "where": { "label": "Cloudflare", "url": null }, "provider": null, "providers": [], "records": [], "refusals": [ { "key": "mode", "reason": "receives_mail", "params": [] } ], "lookups": null, "dkim": null } ```
--- ### Get a zone’s audit history {#get-organizations-organization-dns-zones-dnszone-history}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/history` **Scope:** `dns.read` · **Changes data:** no The 50 most recent audit-log entries for the zone and its records, deleted records included, newest first. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/history" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json [ { "id": 21, "action": "dns_zone.exported", "meta": { "zone": "northwind.com", "format": "bind", "records": 10 }, "user_id": 1, "created_at": "2026-01-15T12:00:00.000000Z" } ] ```
--- ### List leftover records in a zone {#get-organizations-organization-dns-zones-dnszone-leftovers}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/leftovers` **Scope:** `dns.read` · **Changes data:** no Records the zone appears to have outgrown, such as old verification codes, addresses that stopped answering and duplicates, each with its reason and evidence. `writable` is false when InfraNest cannot change records in this zone. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/leftovers" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "items": [], "writable": false, "unanswered_days": 90 } ```
--- ### Compare stored records with live DNS {#get-organizations-organization-dns-zones-dnszone-live-mismatch}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/live-mismatch` **Scope:** `dns.read` · **Changes data:** no Records whose stored value differs from what public resolvers last answered, each with both values, the likely cause and whether taking the live value is offered. Also returns the zone’s delegation state; changes nothing. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/live-mismatch" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "zone": "northwind.com", "checked_at": "2026-01-15T11:53:00.000000Z", "delegation": { "state": "matches", "reason": null, "live": [ "nina.ns.cloudflare.com", "rick.ns.cloudflare.com" ], "dnssec": null, "belongs_to": "cloudflare", "checked_at": "2026-01-15T11:53:00.000000Z" }, "can_push": false, "records": [] } ```
--- ### List outside changes to a zone {#get-organizations-organization-dns-zones-dnszone-outside-changes}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/outside-changes` **Scope:** `dns.read` · **Changes data:** no Records changed at the DNS provider outside InfraNest that nobody has decided on yet, newest first, each with what the record was and is now. `writable` says whether your earlier value can be put back for this zone. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/outside-changes" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "changes": [], "writable": false } ```
--- ### Preview what a record value would point at {#get-organizations-organization-dns-zones-dnszone-points-at}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/points-at` **Scope:** `dns.read` · **Changes data:** no Says what a record of the given `type` with the given `value` (both required) would point at, before you save it. `points_at` is null when nothing is recognised. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `type`
query · required | string | The record type. | | `value`
query · required | string | The value being typed. | | `type`
body · required | string | One of: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `NS`, `SOA`, `SRV`, `CAA`, `PTR`, `ALIAS`, `CERT`, …. | | `value`
body · required | string | Must not be greater than 255 characters. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/points-at?type=A&value=203.0.113.7" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "points_at": { "kind": "server", "label": "web-01", "url": "/servers/1", "flagged": false, "since": null, "provider": "Hetzner", "note": null, "ours": false } } ```
--- ### Get what a zone’s records point at {#get-organizations-organization-dns-zones-dnszone-record-context}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/record-context` **Scope:** `dns.read` · **Changes data:** no For each record in the zone, keyed by record id: what it points at (one of your servers, a recognised service, a connection or a released address) and what uses it, such as monitors, certificates and status pages. Records with neither are left out; `long_ttl` is the TTL above which InfraNest calls a TTL long. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/record-context" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "records": { "1": { "points_at": { "kind": "server", "label": "web-01", "url": "/servers/1", "flagged": false, "since": null, "provider": "Hetzner", "note": { "key": "proxied", "params": { "provider": "Cloudflare" } }, "ours": false }, "used_by": [ { "type": "monitor", "id": 1, "label": "Marketing site", "sublabel": "https://northwind.com", "url": "/monitoring/1", "meta": { "type": "http", "last_status": "up", "last_checked_at": "2026-01-15T11:58:00.000000Z" } }, { "type": "monitor", "id": 4, "label": "Checkout flow", "sublabel": "https://northwind.com/checkout", "url": "/monitoring/4", "meta": { "type": "http", "last_status": "down", "last_checked_at": "2026-01-15T11:58:00.000000Z" } } ] }, "2": { "points_at": { "kind": "server", "label": "web-01", "url": "/servers/1", "flagged": false, "since": null, "provider": "Hetzner", "note": { "key": "proxied", "params": { "provider": "Cloudflare" } }, "ours": false }, "used_by": [ { "type": "monitor", "id": 8, "label": "www → apex redirect", "sublabel": "https://www.northwind.com", "url": "/monitoring/8", "meta": { "type": "redirect", "last_status": "up", "last_checked_at": "2026-01-15T11:58:00.000000Z" } }, { "type": "certificate", "id": 1, "label": "northwind.com", "sublabel": "2026-03-30", "url": "/certificates/1", "meta": [] } ] } }, "long_ttl": 3600 } ```
--- ### Check whether an address left your fleet {#get-organizations-organization-dns-zones-dnszone-released-address}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/released-address` **Scope:** `dns.read` · **Changes data:** no Says whether the `address` query parameter (required) belongs to one of your servers, or is an address one of your servers held and released. A record cannot be pointed at a released address. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `address`
query · required | string | The A/AAAA value being typed. | | `address`
body · required | string | Must not be greater than 64 characters. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/released-address?address=203.0.113.7" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "server": { "id": 1, "name": "web-01" }, "released": null } ```
--- ### Preview restoring a zone to an earlier point {#get-organizations-organization-dns-zones-dnszone-restore-preview}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/restore-preview` **Scope:** `dns.read` · **Changes data:** no Shows what putting every record back the way it was just after the batch `batch_id` (required) would change. Changes nothing; answers 404 when that batch is not in the zone’s history. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | | `batch_id`
query · required | string | The batch to restore to. | | `batch_id`
body · required | string | Must be a valid UUID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/restore-preview?batch_id=9b2c6a1e-3f7d-4a8e-9c1b-2d3e4f5a6b7c" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ```
--- ### Check a zone’s live DNS security {#get-organizations-organization-dns-zones-dnszone-security-check}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/security-check` **Scope:** `dns.read` · **Changes data:** no Resolves the zone’s SPF, DMARC, DKIM, MX, CAA, MTA-STS, BIMI, NS, address and DNSSEC answers from public DNS right now, with findings judged by the same rules as the stored-record advisor. Reflects what the internet answers, not what InfraNest stores. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/security-check" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "zone": "northwind.com", "checked_at": "2026-01-15T12:00:00.000000Z", "dnssec": { "state": "unknown", "ds": [], "validates": null }, "spf": [], "dmarc": [], "dkim": [], "mx": [], "a": [], "aaaa": [], "caa": [], "wildcard_a": [], "wildcard_aaaa": [], "mta_sts": [], "bimi": [], "ns": [], "ns_matches_provider": null, "snapshot": { "spf": [], "dmarc": [], "dkim_selectors": [], "mx": [], "null_mx": false, "mail_provider": null, "mx_cname_targets": [], "apex_a": [], "apex_aaaa": [], "apex_cname": [], "caa": [], "wildcard_a": [], "wildcard_aaaa": [], "cf_exposed_ips": [], "mta_sts": [], "bimi": [], "apex_ns": [], "ns_matches_provider": null, "mail_subdomains_missing_spf": [] }, "findings": [ { "id": "dnssec", "status": "pass", "variant": "pass" }, { "id": "no_mail", "status": "info", "variant": "info", "fix": { "type": "no_mail" } } ] } ```
--- ### List server addresses for records {#get-organizations-organization-dns-zones-dnszone-server-addresses}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/server-addresses` **Scope:** `dns.read` · **Changes data:** no Your servers that have an address, each with the IPv4 and IPv6 address an A or AAAA record should hold to reach it. Where a server holds a whole IPv6 block, `basis` says how the address was chosen. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/server-addresses" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "data": [ { "id": 13, "name": "analytics-01", "status": "stopped", "ipv4": { "address": "192.0.2.80", "basis": "exact" }, "ipv6": null }, { "id": 3, "name": "api-01", "status": "running", "ipv4": { "address": "198.51.100.20", "basis": "exact" }, "ipv6": { "address": "2001:db8:1c1e:5a20::1", "basis": "default", "block": "2001:db8:1c1e:5a20::/64" } } ] } ```
--- ### Sync a zone from its provider {#post-organizations-organization-dns-zones-dnszone-sync}
`POST` `/organizations/{organization}/dns-zones/{dnsZone}/sync` **Scope:** `dns.write` · **Changes data:** yes — only where API writes are switched on for the organization Queues a fresh import of the zone’s records from its DNS provider and answers with `queued: true`. Refused with 422 for a zone with no linked provider connection; manual syncs are limited per hour by your plan. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`write_disabled`](../errors.md#write_disabled), [`organization_suspended`](../errors.md#organization_suspended), [`not_found`](../errors.md#not_found), [`idempotency_in_progress`](../errors.md#idempotency_in_progress), [`validation_failed`](../errors.md#validation_failed), [`idempotency_key_reused`](../errors.md#idempotency_key_reused), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl -X POST "https://api.infranest.io/api/organizations/42/dns-zones/1/sync" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ```
--- ### Download a zone as a BIND file {#get-organizations-organization-dns-zones-dnszone-zone-file}
`GET` `/organizations/{organization}/dns-zones/{dnsZone}/zone-file` **Scope:** `dns.read` · **Changes data:** no Returns the zone’s records as a BIND master file, in plain text, as an attachment named after the zone. The export is recorded in your audit log. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dnsZone_id`
path · required | integer | The dnsZone's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dns-zones/1/zone-file" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 — The zone in BIND format. (`text/plain`)
## Dynamic IPs ### List dynamic IP sources {#get-organizations-organization-dynamic-ips}
`GET` `/organizations/{organization}/dynamic-ips` **Scope:** `monitoring.read` · **Changes data:** no Your dynamic IP sources (connections whose address changes, such as a home or office line), ordered by label, each with its tags and the DNS records and firewalls it keeps up to date. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dynamic-ips" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json [ { "id": 1, "organization_id": 1, "label": "Office — Amsterdam", "current_ipv4": null, "current_ipv6": null, "ipv4_updated_at": null, "ipv6_updated_at": null, "last_used_at": null, "created_by": 1, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "observed_ipv4": null, "observed_ipv6": null, "alert_on_change": true, "firewall_strict": false, "firewall_fail_closed": false, "country_code": null, "city": null, "asn": null, "asn_org": null, "is_hosting": false, "silent_notified_at": null, "public_id": null, "last_attempt_at": null, "last_attempt_result": null, "last_attempt_ip": null, "last_attempt_agent": null, "agent_server_id": null, "targets_count": 0, "tags": [], "targets": [] } ] ```
--- ### Get a dynamic IP source {#get-organizations-organization-dynamic-ips-dynamicip}
`GET` `/organizations/{organization}/dynamic-ips/{dynamicIp}` **Scope:** `monitoring.read` · **Changes data:** no One source with its targets, its 20 most recent address events and the server agent it follows, if any. The update token is never included; `can_reveal` says whether it can be shown again. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dynamicIp_id`
path · required | integer | The dynamicIp's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dynamic-ips/1" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "id": 1, "organization_id": 1, "label": "Office — Amsterdam", "current_ipv4": null, "current_ipv6": null, "ipv4_updated_at": null, "ipv6_updated_at": null, "last_used_at": null, "created_by": 1, "created_at": "2026-01-15T12:00:00.000000Z", "updated_at": "2026-01-15T12:00:00.000000Z", "observed_ipv4": null, "observed_ipv6": null, "alert_on_change": true, "firewall_strict": false, "firewall_fail_closed": false, "country_code": null, "city": null, "asn": null, "asn_org": null, "is_hosting": false, "silent_notified_at": null, "public_id": null, "last_attempt_at": null, "last_attempt_result": null, "last_attempt_ip": null, "last_attempt_agent": null, "agent_server_id": null, "targets_count": 0, "targets": [], "events": [], "agent_server": null, "can_reveal": false } ```
--- ### Get a dynamic IP source’s history {#get-organizations-organization-dynamic-ips-dynamicip-history}
`GET` `/organizations/{organization}/dynamic-ips/{dynamicIp}/history` **Scope:** `monitoring.read` · **Changes data:** no Address changes, what each one updated or failed to update, and changes to the source’s settings, newest first. Drawn from your audit log, so it ages out with your plan’s audit retention. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | | `dynamicIp_id`
path · required | integer | The dynamicIp's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dynamic-ips/1/history" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json { "entries": [], "truncated": false } ```
--- ### List servers a dynamic IP can follow {#get-organizations-organization-dynamic-ips-agent-servers}
`GET` `/organizations/{organization}/dynamic-ips/agent-servers` **Scope:** `monitoring.read` · **Changes data:** no Your servers with an InfraNest agent, which a dynamic IP source can take its address from, most recent agent check-in first. | Field | Type | Notes | |---|---|---| | `organization_id`
path · required | integer | Your organization's ID. | **Errors:** [`unauthenticated`](../errors.md#unauthenticated), [`scope_missing`](../errors.md#scope_missing), [`wrong_organization`](../errors.md#wrong_organization), [`test_token_required`](../errors.md#test_token_required), [`test_token_sandbox_only`](../errors.md#test_token_sandbox_only), [`permission_denied`](../errors.md#permission_denied), [`not_found`](../errors.md#not_found), [`rate_limited`](../errors.md#rate_limited)
**Request** ```sh curl "https://api.infranest.io/api/organizations/42/dynamic-ips/agent-servers" \ -H "Authorization: Bearer $INFRANEST_TOKEN" \ -H "Accept: application/json" ``` **Response** · 200 ```json [ { "id": 3, "name": "api-01", "agent_last_seen_at": "2026-01-15T11:57:00+00:00" } ] ```