DNS records
Every record is checked before it's saved, and every write returns once both nameservers serve it.
The 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
| Type | Value | Example |
|---|---|---|
| A | An IPv4 address | 192.0.2.7 |
| AAAA | An IPv6 address | 2001:db8::7 |
| CNAME | A hostname | cname.vercel-dns.com |
| MX | A hostname, with priority | mx.zoho.com |
| TXT | Any text up to 4000 characters, unquoted (already-quoted strings are accepted too) | v=spf1 include:zoho.com ~all |
| CAA | Flags, tag (issue, issuewild or iodef), quoted value | 0 issue "letsencrypt.org" |
| SRV | Priority, weight, port, target, all in value; the name is like _sip._tcp | 10 5 5060 sip.example.com |
| NS | A 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 likewww.yourteam.itoka.devis shortened for you. Wildcards are*or*.sub. ttlis 60 to 604800 seconds; leave it out (or send 0) for Auto, 1800.priorityis 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.
{
"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:
{
"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.