Guides

DNS records

Every record is checked before it's saved, and every write returns once both nameservers serve it.

The record

Record
{
  "id": "9f2c4e1a7b3d5e60",  // changes whenever the record changes
  "type": "MX",
  "name": "@",               // relative to the zone; "@" is the apex, "*" a wildcard
  "priority": 10,            // MX only
  "value": "mx.zoho.com",    // hostnames without a trailing dot; TXT unquoted
  "display": "10 mx.zoho.com",
  "ttl": 3600,               // seconds; 1800 means Auto
  "managed": false,          // the apex NS for ns1/ns2.itoka.ng: can't be changed
  "readonly": false,         // a type Itoka can't edit yet
  "txt_note": ""
}

You send a RecordInput: type, name, value, ttl (seconds, 60 to 604800; null or 0 for Auto, which is 30 minutes) and, for MX, priority.

Adding, changing and deleting

# Add
curl https://api.itoka.ng/api/v1/zones/ojaara.com/records \
  -H "Authorization: Bearer $ITOKA_API_KEY" -H "Content-Type: application/json" \
  -d '{"record": {"type": "MX", "name": "@", "priority": 10, "value": "mx.zoho.com", "ttl": 3600}}'

# Change (replaces the record with this id)
curl -X PUT https://api.itoka.ng/api/v1/zones/ojaara.com/records/RECORD_ID \
  -H "Authorization: Bearer $ITOKA_API_KEY" -H "Content-Type: application/json" \
  -d '{"record": {"type": "MX", "name": "@", "priority": 20, "value": "mx.zoho.com", "ttl": 3600}}'

# Delete (returns the deleted record)
curl -X DELETE https://api.itoka.ng/api/v1/zones/ojaara.com/records/RECORD_ID \
  -H "Authorization: Bearer $ITOKA_API_KEY"

A record's id changes whenever the record changes, so always use the id from the latest response. Using an old id answers 409 record_changed: read the zone again. Each write answers with the record and the zone; when zone.replication.state is in_sync, ns2 is already serving it.

GET /zones/{zone} returns the zone and all its records, sorted by type (NS, A, AAAA, CNAME, MX, TXT, CAA, SRV), then name and value.

Types and their values

TypeValueExample
AAn IPv4 address192.0.2.7
AAAAAn IPv6 address2001:db8::7
CNAMEA hostnamecname.vercel-dns.com
MXA hostname, with prioritymx.zoho.com
TXTAny text up to 4000 characters, unquoted (already-quoted strings are accepted too)v=spf1 include:zoho.com ~all
CAAFlags, tag (issue, issuewild or iodef), quoted value0 issue "letsencrypt.org"
SRVPriority, weight, port, target, all in value; the name is like _sip._tcp10 5 5060 sip.example.com
NSA hostname (delegating a subdomain)ns1.example.net

Rules Itoka checks

  • A CNAME can't sit at the apex (@), or at a name that has other records.
  • MX and NS targets must be hostnames, never IP addresses.
  • TXT values over 255 characters are split into several strings automatically; resolvers join them back.
  • The apex NS records for ns1 and ns2.itoka.ng are managed: without them ns2 stops serving the zone. Other NS records at @ are refused (apex_ns); NS is for delegating a subdomain.
  • Names are relative to the zone (www, @ for the apex); a full name like www.yourteam.itoka.dev is shortened for you. Wildcards are * or *.sub.
  • ttl is 60 to 604800 seconds; leave it out (or send 0) for Auto, 1800. priority is for MX only.
  • Common mistakes get a warning that doesn't block saving: a verification TXT at the wrong name, DMARC not at _dmarc, DKIM not under ._domainkey.

Checking a record first

POST /zones/{zone}/records/validate takes the same body as creating a record and saves nothing. It answers {ok: true, note, warning} or {ok: false, problem}, where the problem has a message, a reason and a fix. Some problems carry an action, a one-click fix: for a CNAME at the apex pointing at Vercel, it suggests the A record instead.

Creating or changing a record that breaks a rule answers 422 invalid_record with the message, reason and fix. The specific code (cname_at_apex, invalid_srv, apex_ns…) comes from validate, so branch on validate if your code needs it.

problem
{
  "code": "cname_at_apex",
  "message": "A CNAME can't go at the apex (@)",
  "fix": "add an A record for 76.76.21.21, Vercel's apex address.",
  "field": "type",
  "action": { "label": "Do that", "type": "A", "value": "76.76.21.21" }
}

Propagation

GET /zones/{zone}/propagation?name=www&type=A asks ns1 and ns2, and Google, Cloudflare and Quad9, what they answer right now:

propagation (abridged)
{
  "name": "www.ojaara.com", "type": "A",
  "expected": ["192.0.2.7"],
  "servers": [
    { "name": "ns1.itoka.ng", "kind": "itoka", "state": "current", "values": ["192.0.2.7"] },
    { "name": "Google", "kind": "resolver", "state": "old", "values": ["192.0.2.1"], "ttl": 1740 }
  ],
  "settles_at": "2026-10-14T18:31:00Z",
  "settled": false,
  "delegated": true,
  "message": "Google still has the previous answer cached, for up to 29 min more. Nothing to do; it clears on its own."
}

Results are reused for 5 seconds. Each account can run two different checks at a time (429 busy beyond that). See also webhooks: zone.records_changed fires on every change.