---
title: "DNS — API v1 i MCP | Dokumentacja Nodea"
description: "Pełny reference modułu DNS: endpointy REST API v1 (strefy i rekordy PowerDNS, wyszukiwanie strefy, usuwanie po wartości), zakresy infra:read/write i dns:acme, automatyzacja certyfikatów DNS-01, narzędzia MCP oraz przykłady curl i JSON-RPC."
url: "https://nodea.pl/pl/dokumentacja-api/dns"
lang: "pl"
updated: "2026-07-16"
---

# DNS — API v1 i MCP | Dokumentacja Nodea

> Reference modułu DNS — endpointy API v1 dla stref i rekordów na klastrze PowerDNS, wyszukiwanie strefy po nazwie, zakres dns:acme do certyfikatów DNS-01, obsługiwane typy rekordów, narzędzia MCP i przykłady.

Moduł **DNS** udostępnia zarządzanie strefami i rekordami hostowanymi na naszym autorytatywnym klastrze **PowerDNS**: listowanie stref, podgląd strefy z pełną listą rekordów oraz tworzenie, aktualizację i usuwanie pojedynczych rekordów. Dokument opisuje warstwę REST API v1 (/api/v1/dns) oraz narzędzia MCP z tego modułu.

## Uwierzytelnianie i zakresy (scopes)

API v1 uwierzytelnia się tokenem osobistym Passport przez nagłówek Authorization: Bearer \<token\>. Poziomy dostępu:

- infra:read — listowanie stref i podgląd strefy z rekordami (index, show).
- infra:write — tworzenie, aktualizacja i **usuwanie** rekordów oraz wyszukiwanie strefy po nazwie. Rekord to podrzędna konfiguracja strefy, dlatego jego skasowanie NIE wymaga infra:delete (ten poziom rezerwujemy dla niszczenia całych zasobów, np. urządzeń czy serwerów).
- dns:acme — wąski zakres do automatyzacji certyfikatów: wyszukanie strefy po nazwie oraz dodawanie i usuwanie wyłącznie rekordów TXT o nazwie \_acme-challenge.\* (patrz [Automatyzacja certyfikatów DNS-01](#dns-01)).

Wyszukiwanie strefy działa z każdym z trzech zakresów: infra:read, infra:write i dns:acme. infra:write nie obejmuje odczytu listy stref ani rekordów — do tego połącz go z infra:read.

Cały moduł jest dodatkowo bramkowany flagą Pennant features:hosting-suite — wyłączenie pakietu hostingowego ukrywa DNS zarówno w web UI, jak i w API/MCP. Limit zapytań: throttle:api-v1 = 240/min na użytkownika tokenu.

## Konto klienta, własność i widoczność

Strefy DNS należą do **konta klienta** (patrz [Konta klientów](https://nodea.pl/pl/dokumentacja-api/accounts)). Żądanie działa w bieżącym koncie (przypięcie tokenu → nagłówek X-Account-Id → konto domyślne). Dostęp wymaga jednocześnie scope'a tokenu — infra:\* albo, dla wyszukiwania strefy i rekordów TXT \_acme-challenge, dns:acme (patrz niżej) — i uprawnienia konta:

EndpointUprawnienie kontaGET /dns/zones, GET /dns/zones/lookup, GET /dns/zones/{zone}domains POST / PATCH / DELETE /dns/zones/{zone}/records\[/{record}\]domains + managedomains

Lista zwraca wyłącznie strefy **bieżącego konta**, a DnsZoneResource niesie pole account\_id. Rekordy dziedziczą konto swojej strefy. Strefa spoza bieżącego konta (innego klienta albo innego Twojego konta), systemowa (wspólna infrastruktura, bez konta) lub nieznana zwraca zawsze 404 — API nigdy nie potwierdza istnienia cudzej strefy. Parametr {record} korzysta ze scoped route binding względem {zone}, więc rekord należący do innej strefy zwraca 404 jeszcze przed wejściem do kontrolera. Członek bez uprawnienia dostaje 403 account\_permission\_denied z polem permission.

Zapisy najpierw utrwalają lokalny wiersz (baza jest źródłem prawdy), potem synchronicznie wysyłają strefę do PowerDNS. **Gdy wysyłka się nie uda, żądanie kończy się 502 z "code": "dns\_push\_failed"** (do października 2026 API zwracało sukces i tylko logowało błąd). Przy POST i PATCH z infra:write rekord zostaje zapisany, a odpowiedź niesie go w data (z id, żeby można było posprzątać); następna wysyłka strefy dostarczy go na serwery nazw. Przy POST z samym dns:acme rekord **nie** zostaje zapisany (odpowiedź bez data, wystarczy ponowić) — chyba że PowerDNS przyjął właśnie ten RRset, a odrzucił tylko inne RRsety strefy: wtedy rekord działa i odpowiedź to 201. Jeden RRset odrzucony przez PowerDNS (HTTP 4xx) nie blokuje wysyłki pozostałych; gdy PowerDNS jest nieosiągalny (błąd połączenia, przekroczony czas, HTTP 5xx), wysyłka kończy się na pierwszym nieudanym RRsecie, zamiast czekać na limit czasu przy każdym kolejnym, a RRsety po nim liczą się jako niedostarczone.

**Równoległe zmiany jednej strefy są wykonywane po kolei** — w API v1, w MCP i w panelu (klienta i administratora), więc także zmiana zapisana w panelu w tej samej chwili czeka na swoją kolej i nie usuwa z serwerów nazw wartości wysłanej przez API: żądanie, które tworzy, zmienia lub usuwa rekordy, czeka, aż zakończy się wcześniejsza zmiana tej samej strefy, i dopiero wtedy działa na pełnym, aktualnym zestawie rekordów. Każda wysyłka niesie więc wszystkie zapisane wartości RRsetu — klient ACME może równolegle opublikować wyzwanie dla domeny i dla wildcardu (dwie wartości pod jedną nazwą) — a limit 20 rekordów wyzwań dla dns:acme liczy także rekordy z równoległych żądań.

**Usuwanie** najpierw poprawia na serwerach nazw sam dotknięty RRset, jednym atomowym wywołaniem (zastąpienie pozostałymi wartościami albo usunięcie RRsetu, gdy nic nie zostaje). Gdy PowerDNS to odrzuci, nic nie zostaje usunięte: 502 z "deleted": false, można ponowić. Gdy się uda, usunięcie jest zatwierdzane w bazie, a dopiero potem wysyłana jest cała strefa — jeśli ta wysyłka zawiedzie, odpowiedź to 502 z "deleted": true: rekordu nie ma już ani w bazie, ani na serwerach nazw, na następną synchronizację czeka tylko reszta strefy (nie ponawiaj usunięcia — kolejna próba zwróci 404 dns\_record\_not\_found).

**Ponowienie POST** z tą samą nazwą (bez względu na wielkość liter), typem i treścią nie tworzy duplikatu: odpowiedź to 200 z istniejącym rekordem (strefa jest przy tym wysyłana ponownie; jeśli znów się nie uda — 502 z tym rekordem w data).

## API v1 — endpointy

MetodaŚcieżkaScopeOpisGET/api/v1/dns/zonesinfra:readLista stref bieżącego konta, paginowana; z licznikiem rekordów. GET/api/v1/dns/zones/lookup?fqdn=infra:read, infra:write lub dns:acmeStrefa bieżącego konta, w której leży fqdn (najdłuższe dopasowanie sufiksu). Zwraca tylko id i name. GET/api/v1/dns/zones/{zone}infra:readSzczegóły strefy wraz z pełną listą rekordów (sort: type, name). POST/api/v1/dns/zones/{zone}/recordsinfra:write lub dns:acme\*Utworzenie rekordu w strefie. Zwraca 201 z rekordem (w tym id), 200 z istniejącym rekordem, gdy identyczny już jest. PATCH/api/v1/dns/zones/{zone}/records/{record}infra:writeCzęściowa aktualizacja rekordu (tylko przesłane pola). Scoped binding. DELETE/api/v1/dns/zones/{zone}/records/{record}infra:write lub dns:acme\*Usunięcie rekordu (scoped binding). Zwraca 204. DELETE/api/v1/dns/zones/{zone}/recordsinfra:write lub dns:acme\*Usunięcie rekordu (rekordów) dokładnie pasujących do name + type + content. Zwraca 204.

\* Z samym dns:acme: tylko rekordy TXT, których nazwa zaczyna się od etykiety \_acme-challenge; wszystko inne to 403 dns\_acme\_record\_forbidden.

### GET /dns/zones/lookup — strefa po nazwie

- fqdn (string, wymagany, max 255) — dowolna nazwa w strefie, np. \_acme-challenge.www.example.com. Przed dopasowaniem normalizowana: przycięcie spacji, małe litery, bez kropki końcowej, nazwy IDN zamieniane na punycode. Etykiety z podkreślnikiem są dozwolone, \* nie.

Wygrywa strefa bieżącego konta z **najdłuższym pasującym sufiksem** (przy strefach example.com. i eu.example.com. nazwa api.eu.example.com wskazuje tę drugą); sufiks pasuje tylko na granicy etykiety. Odpowiedź 200: {"data": {"id": 42, "name": "example.com."}} — bez rekordów i metadanych strefy. 404 z "code": "dns\_zone\_not\_found", gdy żadna strefa konta nie pasuje (strefy innych kont i systemowe nigdy nie pasują), 422 przy braku lub błędnym fqdn.

### DELETE /dns/zones/{zone}/records — usunięcie po wartości

Ciało (JSON) lub query string: name (string, max 253; @, goła etykieta lub FQDN, jak w POST), type (jeden z typów rekordów), content (string, max 1000). Usuwa każdy rekord strefy o tej nazwie (bez względu na wielkość liter), typie i treści. Treść musi pasować dokładnie; dla TXT forma w cudzysłowach i bez nich jest równoważna (abc = "abc"), wielkość liter ma znaczenie. 404 z "code": "dns\_record\_not\_found", gdy nic nie pasuje.

### GET /dns/zones — parametry zapytania

- search (string, opcjonalny, max 255) — dopasowanie po nazwie strefy.
- per\_page (int 1–100, domyślnie 20), page (int ≥1).

Odpowiedź: kolekcja DnsZoneResource — pola: id, name, kind, serial (SOA), dnssec (bool), records\_count, last\_synced\_at, created\_at, updated\_at. W show dochodzi tablica records\[\] (DnsRecordResource). Konfiguracja wewnętrzna strefy (masters, nsec3param, uchwyt do zdalnego PowerDNS) pozostaje po stronie serwera i nie jest serializowana.

### DnsRecordResource — pola rekordu

id, zone\_id, name (FQDN), type, content, ttl, priority (nullable), comment (nullable), disabled (bool), created\_at, updated\_at.

### POST/PATCH — parametry ciała rekordu

- name (string, max 253) — akceptuje @ (apex strefy), gołą etykietę (zostanie dopełniona nazwą strefy) lub pełny FQDN. Po dopełnieniu nazwa musi być poprawną nazwą DNS: tylko ASCII (nazwy IDN w punycode, np. xn--ka-ola7v), bez spacji i znaków sterujących, bez pustych etykiet (a..b), etykiety do 63 znaków, cała nazwa do 253; w etykiecie litery, cyfry, - i \_, a \* wyłącznie jako cała skrajnie lewa etykieta (rekord wildcard). Inaczej 422. Przy PATCH nazwa jest sprawdzana tylko, gdy ją zmieniasz.
- type (enum) — jeden z: A, AAAA, CNAME, MX, TXT, NS, SRV, CAA, PTR.
- content (string, max 1000) — adres IP, host docelowy lub wartość tekstowa. Wartość TXT wysłana bez cudzysłowów zostaje zapisana w cudzysłowach (abc → "abc"); wartość zaczynająca się od cudzysłowu zostaje bez zmian, ale musi składać się z kompletnych łańcuchów w cudzysłowach rozdzielonych spacjami (cudzysłów w środku jako \\"). TXT ze znakami sterującymi albo niezbalansowanymi cudzysłowami to 422.
- ttl (int, 60–604800 s).
- priority (int, 0–65535, opcjonalny) — dla MX/SRV.
- comment (string, max 255, opcjonalny).
- disabled (bool, opcjonalny) — rekord utworzony/oznaczony jako nieserwowany.

**CNAME** nie może dzielić nazwy z żadnym innym rekordem (RFC 1034): rekord pod nazwą, która ma CNAME, drugi CNAME albo CNAME pod nazwą z innymi rekordami (także przez PATCH) kończy się 409 z "code": "dns\_cname\_conflict".

**POST** wymaga kompletu name/type/content/ttl (z samym dns:acme obowiązują zasady z sekcji [DNS-01](#dns-01)). **PATCH** jest częściowy — zmienią się wyłącznie przesłane pola (reszta bez zmian), inaczej niż w web UI, gdzie każdorazowo repostowany jest cały rekord.

### Kody błędów

- 401 — brak/niepoprawny token.
- 403 — brak wymaganego scope.
- 403 dns\_acme\_record\_forbidden — token z samym dns:acme próbował rekordu innego niż TXT \_acme-challenge.\*.
- 403 — błąd kontekstu konta: account\_permission\_denied (pole permission podaje brakującą flagę) — domains przy odczycie, managedomains przy zmianie rekordów, no\_account\_context, account\_forbidden / account\_mismatch (nagłówek X-Account-Id lub przypięcie tokenu) — patrz [Konta klientów](https://nodea.pl/pl/dokumentacja-api/accounts).
- 404 — nieznana, systemowa lub spoza bieżącego konta strefa; rekord z innej strefy (scoped binding).
- 404 dns\_zone\_not\_found (wyszukiwanie) / 404 dns\_record\_not\_found (usunięcie po wartości) — nic nie pasuje.
- 409 dns\_cname\_conflict — CNAME dzieliłby nazwę z innym rekordem.
- 409 dns\_acme\_record\_limit — token z samym dns:acme: strefa ma już 20 rekordów \_acme-challenge.
- 422 — walidacja (niedozwolony typ, TTL poza zakresem, zbyt długa lub niepoprawna nazwa, niepoprawna wartość TXT, błędny fqdn; przy dns:acme także comment/disabled/priority i wartość niebędąca tokenem ACME).
- 502 dns\_push\_failed — zmiany nie udało się wysłać na serwery nazw; co zostaje zapisane — patrz wyżej (przy usuwaniu pole deleted mówi, czy rekord usunięto).

## Przykłady — API

### Lista stref z wyszukiwaniem

curl -s "https://app.nodea.io/api/v1/dns/zones?search=example&per\_page=20" \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Accept: application/json"

### Utworzenie rekordu A

curl -s -X POST https://app.nodea.io/api/v1/dns/zones/42/records \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Content-Type: application/json" \\ -d '{ "name": "www", "type": "A", "content": "203.0.113.10", "ttl": 3600 }'

### Rekord MX z priorytetem

curl -s -X POST https://app.nodea.io/api/v1/dns/zones/42/records \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Content-Type: application/json" \\ -d '{ "name": "@", "type": "MX", "content": "mail.example.com", "ttl": 3600, "priority": 10 }'

### Częściowa aktualizacja (tylko TTL)

curl -s -X PATCH https://app.nodea.io/api/v1/dns/zones/42/records/1001 \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Content-Type: application/json" \\ -d '{ "ttl": 300 }'

## Automatyzacja certyfikatów DNS-01 (ACME)

Do automatycznych certyfikatów TLS (Let's Encrypt lub inny urząd ACME) utwórz token wyłącznie z zakresem dns:acme. Taki token może dokładnie trzy rzeczy: wyszukać strefę po nazwie, dodać rekord TXT, którego nazwa zaczyna się od etykiety \_acme-challenge (\_acme-challenge.example.com, \_acme-challenge.www.example.com), i usunąć taki rekord. Nie wylistuje stref, nie odczyta rekordów, nie wykona PATCH ani żadnego innego wywołania API. Inny typ lub nazwa rekordu kończy się 403 z "code": "dns\_acme\_record\_forbidden". Nadal obowiązują uprawnienia konta (domains dla wyszukiwania, domains + managedomains dla rekordów), a token widzi tylko strefy swojego konta. Tokenu nie da się ograniczyć do wybranych stref — jeśli tego potrzebujesz, użyj osobnego konta (albo przypnij token do jednego).

Dodatkowe ograniczenia dla tokenu z samym dns:acme (naruszenie to 422, o ile nie podano inaczej):

- nazwa: etykiety LDH (litery, cyfry, myślniki w środku) z opcjonalnym podkreślnikiem na początku etykiety, bez \*; nazwa jest zapisywana **małymi literami** (\_ACME-CHALLENGE.WWW → \_acme-challenge.www.example.com.), więc wartości wysłane różną wielkością liter trafiają do jednego RRsetu;
- content: pojedynczy token ACME — 1–255 znaków A–Z a–z 0–9 - \_ (typowo 43), bez cudzysłowów albo w jednej parze cudzysłowów;
- ttl: opcjonalny, 60–600 s, domyślnie 60;
- comment, disabled, priority: niedozwolone;
- najwyżej 20 rekordów \_acme-challenge w strefie — kolejny to 409 z "code": "dns\_acme\_record\_limit" (usuń stare wyzwania);
- ponowienie identycznego POST zwraca 200 z istniejącym rekordem zamiast duplikatu; przy 502 dns\_push\_failed rekord nie zostaje zapisany, więc ponowienie jest bezpieczne.

1. **Znajdź strefę** — GET /api/v1/dns/zones/lookup?fqdn=\_acme-challenge.www.example.com → {"data": {"id": 42, "name": "example.com."}}.
2. **Opublikuj wyzwanie** — POST /api/v1/dns/zones/42/records z {"name": "\_acme-challenge.www", "type": "TXT", "content": "\<skrót autoryzacji klucza\>", "ttl": 60} → 201 z id rekordu (200, gdy ten sam rekord już istnieje). Wartość można wysłać bez cudzysłowów — zapisujemy ją w cudzysłowach, tak jak PowerDNS oczekuje danych TXT. Przy certyfikacie wildcard + apex dwie wartości pod tą samą nazwą są w porządku.
3. **Poczekaj na propagację** — odpytuj bezpośrednio autorytatywne serwery nazw strefy (np. dig +short TXT \_acme-challenge.www.example.com @ns1.nodea.io), aż wartość się pojawi, potem pozwól urzędowi zweryfikować.
4. **Posprzątaj** — DELETE /api/v1/dns/zones/42/records/{id} albo bez id: DELETE /api/v1/dns/zones/42/records z {"name": "\_acme-challenge.www", "type": "TXT", "content": "\<ta sama wartość\>"} → 204. 502 z "deleted": true oznacza, że rekord usunięto — nie ponawiaj.

ZONE=$(curl -s "https://app.nodea.io/api/v1/dns/zones/lookup?fqdn=\_acme-challenge.www.example.com" \\ -H "Authorization: Bearer $NODEA\_ACME\_TOKEN" -H "Accept: application/json" | jq -r .data.id) curl -s -X POST "https://app.nodea.io/api/v1/dns/zones/$ZONE/records" \\ -H "Authorization: Bearer $NODEA\_ACME\_TOKEN" -H "Content-Type: application/json" \\ -d '{"name": "\_acme-challenge.www", "type": "TXT", "content": "'"$TOKEN\_VALUE"'", "ttl": 60}' # ... weryfikacja przez urząd ACME ... curl -s -X DELETE "https://app.nodea.io/api/v1/dns/zones/$ZONE/records" \\ -H "Authorization: Bearer $NODEA\_ACME\_TOKEN" -H "Content-Type: application/json" \\ -d '{"name": "\_acme-challenge.www", "type": "TXT", "content": "'"$TOKEN\_VALUE"'"}'

## MCP — narzędzia

Narzędzia MCP dzielą model uwierzytelniania i zakresów z API v1 (Bearer + scope sprawdzany w ensureScope) oraz flagę features:hosting-suite (narzędzie na nieaktywnej fladze znika z tools/list). Strefy i rekordy są adresowane po całkowitych identyfikatorach; brakujący, cudzy i systemowy identyfikator zwracają ten sam błąd (brak enumeracji między najemcami). Nieudana wysyłka na serwery nazw to błąd narzędzia zakończony (code: dns\_push\_failed, record\_id: \<id\>) — ten sam kod co 502 w API v1: utworzony lub zmieniony rekord zostaje zapisany i trafi na serwery przy następnej synchronizacji; usunięcie jest wycofywane, gdy PowerDNS odrzuci samą zmianę RRsetu, a błąd z deleted: true oznacza, że rekord usunięto i na synchronizację czeka tylko reszta strefy. Narzędzia zapisu sprawdzają nazwę, wartość TXT i konflikt CNAME tak jak API v1 dla infra:write (konflikt: (code: dns\_cname\_conflict); dns-record-update sprawdza regułę CNAME tylko przy zmianie nazwy lub typu). Zmiany jednej strefy są wykonywane po kolei, jak w API v1. Udane zapisy niosą synced: true. Wartości TXT są zapisywane w cudzysłowach, jak w API. Zakres dns:acme działa tylko w API — narzędzia MCP wymagają infra:\*.

NarzędzieScopeTrybOpisdns-zones-listinfra:readread-onlyLista stref; filtr search, page, per\_page (max 50). Zwraca id/nazwę/kind/serial/dnssec/licznik/last\_synced. dns-zone-getinfra:readread-onlyStrefa wraz z każdym rekordem (sort type, name). Param: id. dns-record-createinfra:writezapisDodanie rekordu; zone\_id, name, type, content, ttl, opcjonalnie priority/comment/disabled. dns-record-updateinfra:writeidempotentne (pełny replace)Podmiana rekordu; name/type/content/ttl wymagane za każdym razem (odczytaj wcześniej przez dns-zone-get). dns-record-deleteinfra:writedestrukcyjneUsunięcie rekordu; wymaga confirm: true. Nazwa przestaje się rozwiązywać natychmiast.

**Konta klientów w MCP.** Narzędzia działają w bieżącym koncie połączenia MCP (patrz [Konta klientów](https://nodea.pl/pl/dokumentacja-api/accounts)). dns-zones-list i dns-zone-get wymagają uprawnienia domains; dns-record-create, dns-record-update i dns-record-delete — domains + managedomains. Rekordy dziedziczą konto strefy. Odmowa kończy się sufiksem (code: account\_permission\_denied, permission: \<flaga\>) lub (code: no\_account\_context).

### Przykład — MCP (JSON-RPC)

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "dns-record-create", "arguments": { "zone\_id": 42, "name": "www", "type": "A", "content": "203.0.113.10", "ttl": 3600 } } } { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "dns-record-delete", "arguments": { "zone\_id": 42, "record\_id": 1001, "confirm": true } } }

## Powiązane

- [Serwery — API v1 i MCP | Dokumentacja Nodea](https://nodea.pl/pl/dokumentacja-api/servers)
- [Klucze SSH — API v1 i MCP | Dokumentacja Nodea](https://nodea.pl/pl/dokumentacja-api/ssh-keys)
- [Urządzenia (Connection Manager) — API v1 i MCP | Dokumentacja Nodea](https://nodea.pl/pl/dokumentacja-api/vault-devices)
- [Konta klientów — API v1 i MCP | Dokumentacja Nodea](https://nodea.pl/pl/dokumentacja-api/accounts)

---

Wersje językowe: [en](https://nodea.io/en/api-docs/dns.md)  
Spis treści witryny: [llms.txt](https://nodea.pl/pl/llms.txt)
