DNS and rDNS through the API and MCP
Everything the DNS & rDNS page does is also available in the YorkHost client API and MCP server, with exactly the same checks: ownership of domains and IP addresses, validations, protected NS and SOA, read-only cPanel zones, hourly change limit shared with the client area.
This page covers DNS and rDNS. For getting started (two-factor authentication required, key creation, connecting an assistant), see:
- Getting started with the API
- MCP server: connecting Claude, ChatGPT, Cursor… and the full tool list
- Endpoints and Permissions (pages generated from the API)
Permissions
| Permission | Label in the client area (API keys) | What it opens |
|---|---|---|
dns:read | Read domains and DNS zones | Domain list, DNS state, records, BIND export, DNSSEC state, template list |
dns:write | Edit DNS records | Adding, editing and deleting records, zone creation, BIND import, DNSSEC, applying a template |
rdns:read | Read IP reverse DNS (PTR) | List of IP addresses and their PTR |
rdns:write | Edit IP reverse DNS (PTR) | Setting, resetting and bulk changes of PTR records |
The Read only template contains dns:read and rdns:read; the AI agent (no destructive action) template adds dns:write and rdns:write.
- Protected NS and SOA: the SOA and the name servers of the domain itself (
protected: truein the record list) cannot be changed or deleted (coderec_err_protected); an import ignores them. - Read-only cPanel zones: a zone hosted on cPanel (
readonly: true) can be read through the API but is edited in cPanel. Any write returns403 zone_err_readonly. PATCH …/records/{record_id}replaces the whole record: send the complete record (name, type, TTL and every field of the type), not only the changed field. The id changes on every edit: use the one returned in the response./dns/*routes ignore the service restriction of the key: a domain is not a service. Adns:writekey restricted to a single VPS can change every domain of the account. The/rdns*routes do follow the restriction (an IP address outside the allowed services returns404).- Changes are public and cached by resolvers for the TTL.
Authentication
Base: https://api.yorkhost.fr, header Authorization: Bearer <key>. The examples assume the key is in a variable:
export YH_KEY="yhk_client_v1_..."
Reverse DNS
| Method and route | Purpose | Permission |
|---|---|---|
GET /client/v1/rdns (?service_id=) | IP addresses of the account's services and their PTR | rdns:read |
PUT /client/v1/rdns/{ip} | Set the PTR of an IP address ({"hostname": "…"}) | rdns:write |
DELETE /client/v1/rdns/{ip} | Restore the default YorkHost PTR | rdns:write |
POST /client/v1/rdns/bulk | Bulk change (512 lines max) | rdns:write |
List IP addresses
curl -H "Authorization: Bearer $YH_KEY" \
"https://api.yorkhost.fr/client/v1/rdns?service_id=12345"
Response: {"ips": [{ip, service_id, service_name, list, ptr, ipam_value, state, default}]}. state is ok, pending, nozone (reverse zone not managed by YorkHost) or error. Without service_id, every IP address the key can access is returned.
Set the PTR of an IP address
curl -X PUT https://api.yorkhost.fr/client/v1/rdns/192.0.2.10 \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" \
-d '{"hostname": "mail.example.com"}'
Response: {"ip": "192.0.2.10", "hostname": "mail.example.com", "warning": null}. If the name does not resolve to the IP address, warning is rdns_warn_forward: add the matching A or AAAA record (see forward-confirmed reverse DNS). The name must be a fully qualified host name.
Restore the default PTR
curl -X DELETE https://api.yorkhost.fr/client/v1/rdns/192.0.2.10 \
-H "Authorization: Bearer $YH_KEY"
Response: {"ip": "192.0.2.10", "hostname": "<default name>"}. PUT with an empty hostname is refused: use DELETE.
Bulk rDNS
# 1. Check: nothing is changed
curl -X POST https://api.yorkhost.fr/client/v1/rdns/bulk \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" \
-d '{"check_only": true, "entries": [
{"ip": "192.0.2.10", "hostname": "mail.example.com"},
{"ip": "192.0.2.11", "hostname": "web1.example.com"},
{"ip": "2001:db8::10", "hostname": null}
]}'
# 2. Apply: same call with "check_only": false
- Empty or
nullhostname: default PTR. - Response:
{mode, needed, remaining, counts: {ok, unchanged, error, skipped}, rows: [{n, ip, hostname, name, result, error, message, warning}]}.neededis the number of changes in the batch,remainingwhat is left of the hourly limit. - From 1 to 512 lines per call, 5 calls per minute per key.
- If the batch exceeds the remaining hourly limit, it is refused as a whole (
429 err_rate): nothing is written.
check_only is requiredA call without check_only (or with a non-boolean value) is rejected with 400 bad_request and nothing is changed. Send "check_only": true first to check, then the same call with "check_only": false to apply. The rdns_bulk MCP tool follows the same rule.
Each line follows the rules of the bulk change in the client area.
Domains and zones
A domain is identified by its name in the path (example.com, IDN in Unicode or punycode), among the domains and DNS zones of your account.
| Method and route | Purpose | Permission |
|---|---|---|
GET /client/v1/dns/domains | Domains and zones of the account, with their state | dns:read |
GET /client/v1/dns/domains/{domain} | DNS state of a domain: name servers and zone | dns:read |
GET /client/v1/dns/domains/{domain}/records | Records of the zone | dns:read |
POST /client/v1/dns/domains/{domain}/records | Add a record | dns:write |
PATCH /client/v1/dns/domains/{domain}/records/{record_id} | Replace a record | dns:write |
DELETE /client/v1/dns/domains/{domain}/records/{record_id} | Delete a record | dns:write |
POST /client/v1/dns/domains/{domain}/zone | Create the zone ("Prepare the zone") | dns:write |
GET /client/v1/dns/domains/{domain}/export | Export the zone in BIND format | dns:read |
POST /client/v1/dns/domains/{domain}/import | Import a BIND zone (5 per minute per key) | dns:write |
GET /client/v1/dns/domains/{domain}/dnssec | DNSSEC state, DS to set, keys | dns:read |
PUT /client/v1/dns/domains/{domain}/dnssec | Enable or disable DNSSEC ({"enabled": true}) | dns:write |
GET /client/v1/dns/domains/{domain}/presets | Available e-mail templates | dns:read |
POST /client/v1/dns/domains/{domain}/presets/{preset} | Apply a template | dns:write |
List domains
curl -H "Authorization: Bearer $YH_KEY" https://api.yorkhost.fr/client/v1/dns/domains
Response: {"domains": [{domain, display, kind, status, expires, ns_state, zone_exists, readonly, source}]}.
kind:domainordnszone;ns_state:managed(delegated to the YorkHost servers),mixed,externalorunknown. Records are only served publicly formanaged; an existing zone (zone_exists: true) can be edited whatever this state;readonly: true: zone hosted on cPanel.
GET /client/v1/dns/domains/example.com returns {domain, display, ns: {state, live, registrar, managed}, zone: {exists, readonly, source, dnssec, serial, count}}.
Read records
curl -H "Authorization: Bearer $YH_KEY" \
https://api.yorkhost.fr/client/v1/dns/domains/example.com/records
Response: {"domain": "example.com", "records": [{id, name, fqdn, type, ttl, content, protected, …fields of the type}]}. name is relative to the zone (@ for the domain itself); id (32 hexadecimal characters) is used to edit or delete.
Add a record
curl -X POST https://api.yorkhost.fr/client/v1/dns/domains/example.com/records \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" \
-d '{"name": "www", "type": "A", "value": "192.0.2.10", "ttl": 3600}'
curl -X POST https://api.yorkhost.fr/client/v1/dns/domains/example.com/records \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" \
-d '{"name": "@", "type": "MX", "priority": 10, "target": "mail.example.com"}'
Response 201: {"id": "…"}. Fields to send depend on the type (the same as the zone editor form); any other key is refused (400 bad_request):
| Type | Fields |
|---|---|
| A, AAAA, CNAME, ALIAS, TXT, NS, PTR | value |
| MX | priority, target (or value) |
| SRV | priority, weight, port, target (or value) |
| CAA | flags, tag (issue, issuewild, iodef), value |
| TLSA | usage, selector, matching, data |
| SSHFP | algorithm, fptype, fingerprint |
| DS | keytag, algorithm, digesttype, digest |
| NAPTR | order, preference, flags, service, regexp, replacement |
Plus name, type and, optionally, ttl (60 to 604800; default TTL when absent). For a TXT record, send the text without quotes: {"name": "_dmarc", "type": "TXT", "value": "v=DMARC1; p=none"}.
Edit a record
curl -X PATCH https://api.yorkhost.fr/client/v1/dns/domains/example.com/records/0123456789abcdef0123456789abcdef \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" \
-d '{"name": "www", "type": "A", "value": "192.0.2.20", "ttl": 3600}'
Send the complete record. Response: {"id": "…"} with the new id.
Delete a record
curl -X DELETE https://api.yorkhost.fr/client/v1/dns/domains/example.com/records/0123456789abcdef0123456789abcdef \
-H "Authorization: Bearer $YH_KEY"
Response: {"ok": true, "domain": "example.com", "id": "…"}.
Create the zone
curl -X POST https://api.yorkhost.fr/client/v1/dns/domains/example.com/zone \
-H "Authorization: Bearer $YH_KEY"
Response: {"created": true}, or false if the zone already existed. For a .fr domain, create the zone before changing the name servers (see Point a domain to YorkHost).
Export and import (BIND)
# Export: the zone file is in the "bind" field
curl -s -H "Authorization: Bearer $YH_KEY" \
https://api.yorkhost.fr/client/v1/dns/domains/example.com/export | jq -r .bind > example.com.zone
# Import: adds the records ("replace": true replaces those with the same name and type)
jq -Rs '{bind: ., replace: false}' example.com.zone | \
curl -X POST https://api.yorkhost.fr/client/v1/dns/domains/example.com/import \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" --data-binary @-
Import response: {added, skipped} (skipped lists the skipped lines and why). The SOA and NS of the domain are always ignored. Zone file of 256 KB max, 5 imports per minute per key.
DNSSEC
curl -X PUT https://api.yorkhost.fr/client/v1/dns/domains/example.com/dnssec \
-H "Authorization: Bearer $YH_KEY" -H "Content-Type: application/json" \
-d '{"enabled": true}'
Response: {enabled, ds}. Then set the DS at your registrar. To disable, first remove the DS at the registrar, then send {"enabled": false}: see DNSSEC.
E-mail templates
curl -H "Authorization: Bearer $YH_KEY" \
https://api.yorkhost.fr/client/v1/dns/domains/example.com/presets
curl -X POST https://api.yorkhost.fr/client/v1/dns/domains/example.com/presets/google_workspace \
-H "Authorization: Bearer $YH_KEY"
Template keys: google_workspace, microsoft365, zoho_eu, protonmail, ovh_mx. As in the client area, existing MX and SPF records are replaced (see E-mail templates). Response: {"applied": <number of records>}.
MCP tools
| Tool | Purpose | Permission |
|---|---|---|
dns_list_domains | Domains and zones of the account, name server state, read-only zones | dns:read |
dns_get_domain | DNS state of a domain (name servers, zone, DNSSEC, serial) | dns:read |
dns_list_records | Records of a zone, with their id and protected | dns:read |
dns_export_zone | BIND export of the zone (backup before changes) | dns:read |
dns_add_record | Add a record (A, AAAA, CNAME, MX, TXT, SRV, CAA…) | dns:write |
dns_update_record | Replace a record: the assistant must send the complete record | dns:write |
dns_delete_record | Delete a record | dns:write |
rdns_list | IP addresses of the services and their PTR | rdns:read |
rdns_set | Set the PTR of an IP address | rdns:write |
rdns_reset | Restore the default PTR | rdns:write |
rdns_bulk | Bulk rDNS (512 lines max), always in check mode (check_only: true) first | rdns:write |
Zone creation, BIND import, DNSSEC and e-mail templates are not available as MCP tools: use the client area or the REST API. The tools ask the assistant to confirm with you before any DNS or rDNS change.
Examples of requests to a connected assistant:
- "List my domains and tell me which ones do not point to YorkHost yet."
- "Add a
_dmarcTXT record on example.com withv=DMARC1; p=none." - "Set the reverse DNS of 192.0.2.10 to mail.example.com and check that the A record exists."
Limits
- Per key: 120 reads and 20 writes per minute (all routes together).
POST /client/v1/rdns/bulk: 5 calls per minute per key, 512 lines per call.POST …/import: 5 calls per minute per key.- Hourly limit of DNS and rDNS changes for the account, shared with the client area:
429 err_rateonce reached.
Errors
Errors use the {"error": "<message>", "code": "<code>"} format. Business refusals carry a message in French, meant to be read as is.
| HTTP | code | Cause |
|---|---|---|
| 400 | bad_request | Missing, extra or wrongly typed parameter; unreadable IP address or domain name; invalid record_id |
| 400 | rec_err_*, zone_err_* | Record or zone refused (type not allowed, invalid name or value, TTL out of range, CNAME conflict, protected record, unreadable import…) |
| 400 | rdns_err_hostname, rdns_err_nozone, rdns_err_forward, rdns_bulk_err_* | Invalid host name, reverse zone not managed, name that does not resolve to the IP address, invalid rDNS batch |
| 400 | err_preset | Unknown template |
| 403 | zone_err_readonly | Zone hosted on cPanel: read-only |
| 403 | rdns_err_owner | IP address that this account cannot change |
| 403 | missing_scope | Missing permission (required_scope tells which one) |
| 404 | not_found | Domain, IP address, service or record unknown, outside the key restriction or belonging to another account |
| 413 | too_large | Request body too large |
| 429 | err_rate | Hourly change limit reached (or rDNS batch too large for what is left) |
| 502 | err_backend, err_internal | DNS service failing or unreachable: try again later |
| 503 | dns_unavailable | DNS management temporarily unavailable through the API |