API reference (machine endpoints)

Auth is always an API token (nhtdns_ prefix), HTTP Basic password slot or a Bearer header. Machine endpoints never read browser sessions and need no CSRF. Tokens are created in the panel and come in three tiers by where they will live; each endpoint group accepts only its own tier — no crossover.

Picking a token: decide by where it will be stored.

DDNS token For routers / NAS. Bound to one hostname, only valid on /nic/update; a leak loses at most that one record
ACME token For cert-renewal servers. All domains, but only _acme-challenge TXT plus managed-cert download
Manage token Only for servers / scripts you control. Add/delete domains plus full record CRUD and import/export across all your domains; optional expiry

Device endpoints (DDNS / ACME tokens)

GET /nic/update DynDNS2 update. Responds with text/plain protocol literals: good (updated) / nochg (no change) / nohost (hostname not owned by token) / badauth (auth failed) / abuse (rate limited) / 911 (server error)
POST /api/dns/acme/present body {"fqdn":"_acme-challenge.example.com.","value":"..."}{"code":200}; fqdn must fall inside the token's authorized zone and its first label must be _acme-challenge
POST /api/dns/acme/cleanup Same shape as present; removes the matching TXT value
GET /api/dns/certs/current Fetch the managed certificate (ACME token, query domain=<fqdn>). Pull daily from cron and reload when the content changes
Rate limits Per token 10/min and 300/h; per source IP 30/min and 600/h. Minute-level DDNS polling fits comfortably
Error shapes ACME endpoints: 400 = body missing fqdn/value; 401 = invalid token; 403 = out of scope (wrong scope / zone suspended). DDNS endpoint errors always use the protocol literals above with HTTP status fixed at 200

Management API (/api/dns/v1, manage token)

GET /api/dns/v1/zones Zone list (with ids and public_ns). Scripts use it to resolve a domain to its zone id first
POST /api/dns/v1/zones body {"zone_name":"example.com"}. Add a domain through the same admission pipeline as the panel (normalization / hold checks / quotas); the response includes public_ns — point your registrar's NS at them next
DELETE /api/dns/v1/zones/:id Delete a domain (with all its records). If DNSSEC is enabled the body must carry {"confirm_dnssec":true} — guards against deleting while the DS is still at the registry, which would blackhole the domain
GET /api/dns/v1/zones/:id/records List records
POST /api/dns/v1/zones/:id/records body {"id":0,"name":"www","type":"A","value":"192.0.2.10","ttl":300,"line":"default"}; id absent or 0 = create, >0 = update that record. Validation shares the panel's pipeline (record types / CNAME exclusivity / quotas / lines)
DELETE /api/dns/v1/zones/:id/records/:rid Delete a record
GET /api/dns/v1/zones/:id/export Export the BIND zone file (text/plain) — your data is always one request away
POST /api/dns/v1/zones/:id/import body {"text":"..."}(BIND text, 1MB max). Each entry runs the validation pipeline; a per-entry report is returned and partial success is expected
Rate limits Per token 60/min and 1000/h; export/import additionally 3/min and 30/h per token. Per source IP 120/min and 2400/h
Error shapes 401 = invalid or expired token; 403 = wrong token tier (DDNS/ACME tokens cannot reach management endpoints); 404 = zone missing or not yours; 400 = validation error (reason in the error field); 429 = rate limited
Minimal example (curl)
TOKEN=nhtdns_xxxxxxxxxxxxxxxxxxxxxx

# 1) 列出 zone,拿到 id
curl -s https://anyns.io/api/dns/v1/zones -H "Authorization: Bearer $TOKEN"

# 2) 加一条 A 记录
curl -s -X POST https://anyns.io/api/dns/v1/zones/<id>/records \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"www","type":"A","value":"192.0.2.10","ttl":300}'

# 3) 整域导出备份
curl -s https://anyns.io/api/dns/v1/zones/<id>/export -H "Authorization: Bearer $TOKEN"