---
title: Domain Validation
path: concepts/validation
status: published
---

# Domain Validation

Before ScaiDNS will serve DNS for a zone, you must prove you control that domain. This prevents anyone from creating `example.com` and immediately stealing traffic if delegation is later pointed at the ScaiDNS nameservers.

Validation happens once, at zone creation. After it completes, the zone is permanently marked `active` — you don't re-validate unless you've let it lapse.

## How it works

When you create a domain (`POST /api/v1/domains/`), the API:

1. Inserts a row in MariaDB with status `pending_validation`.
2. Generates a challenge token.
3. Returns instructions for publishing a TXT record at your current DNS provider.

You then publish the TXT record, call `POST /api/v1/domains/{id}/validation/check`, and ScaiDNS queries DNS directly — bypassing caches — to confirm the record is live.

## The challenge

Fetch the challenge with:

```bash
curl https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/validation \
  -H "X-API-Key: $SCAIDNS_API_KEY"
```

Response:

```json
{
  "validation_type": "txt_record",
  "status": "pending",
  "dns_record": {
    "name": "_scaidns-verify.example.com",
    "type": "TXT",
    "value": "scaidns-verify=a1b2c3d4e5f6..."
  },
  "expires_at": "2026-04-26T10:42:00Z"
}
```

Publish that TXT record at your **current** DNS provider. The domain's NS delegation doesn't need to point at ScaiDNS yet — validation queries whatever nameservers are authoritative today.

## How ScaiDNS checks

When you trigger a check:

```bash
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/validation/check \
  -H "X-API-Key: $SCAIDNS_API_KEY"
```

ScaiDNS:

1. Looks up the NS records for your domain using public recursive resolvers (`1.1.1.1`, `8.8.8.8`, `9.9.9.9`). This avoids your local resolver's cache.
2. Resolves each NS hostname to an IP.
3. Queries each authoritative nameserver directly for the `_scaidns-verify.<domain>` TXT record.
4. Compares the value to the expected challenge.

Querying authoritative nameservers directly bypasses caching. This is why validation usually works within seconds of publishing the TXT record — there's no intermediate cache to invalidate.

If authoritative nameservers can't be discovered (e.g., the domain itself doesn't exist in DNS yet), ScaiDNS falls back to public resolvers. A domain that isn't registered or hasn't been delegated won't validate until it resolves somewhere.

## Response

On success:

```json
{"is_valid": true, "message": "Domain validated"}
```

The domain's status flips to `active` and the PowerDNS zone is created.

On failure:

```json
{
  "is_valid": false,
  "message": "TXT record found but value doesn't match. Expected: scaidns-verify=a1b2c3..."
}
```

Common failure messages and what they mean:

| Message | Cause |
|---------|-------|
| `No DNS record found for _scaidns-verify.<domain>` | TXT record not published or not yet propagated |
| `TXT record found but value doesn't match` | Wrong value published, or cached stale value on your side |
| `No TXT record found for ...` | TXT record exists at a different name, or wasn't created |
| `Domain <name> does not exist in DNS` | The domain itself has no DNS delegation at the TLD |
| `DNS query timed out` | Authoritative nameserver unreachable or overloaded |

## When validation fails repeatedly

**Check propagation.** After publishing a TXT record, your own resolver may cache the absence for the original negative TTL. Use `dig @1.1.1.1 TXT _scaidns-verify.<domain>` to check from a fresh resolver.

**Check you wrote it at the right place.** The record must be at `_scaidns-verify.<domain>`, not `<domain>` itself, not a subdomain. Watch for providers that append the zone automatically — enter `_scaidns-verify` as the record name, not `_scaidns-verify.example.com`.

**Check the challenge didn't expire.** Challenges expire (default 72 hours). Fetch a fresh one via `POST /api/v1/domains/{id}/validation` to regenerate.

**Check the domain resolves at all.** `dig NS <domain>` should return your current nameservers. If it returns NXDOMAIN, the domain isn't registered or delegation is broken at the registrar.

## Re-validation

Once `active`, a zone doesn't re-validate on every change. However, you can cancel and restart validation if something's wrong:

```bash
# Cancel current validation
curl -X DELETE https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/validation \
  -H "X-API-Key: $SCAIDNS_API_KEY"

# Start a new one
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/validation \
  -H "X-API-Key: $SCAIDNS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"validation_type": "txt_record"}'
```

## Nameserver validation (alternative)

Some deployments support NS-record validation in addition to TXT. In this mode, ScaiDNS confirms the domain's NS records at the parent zone match the ScaiDNS nameservers. This is useful for zones being migrated in — delegation and validation happen in one step.

NS validation requires platform operator configuration. If enabled, create the challenge with `{"validation_type": "nameservers"}`.

## Bypassing validation

Users with the `validation_bypass` permission can create zones that skip validation entirely. This is intended for:

- Internal zones that don't exist on the public internet (private or split-horizon).
- Migrations from another ScaiDNS-managed system where ownership is already established.
- Automated pipelines that manage the delegation separately.

Grant `validation_bypass` sparingly — it's a trust boundary.

## What's next

- [Managing Domains](../tutorials/managing-domains.md) — zone CRUD with validation.
- [Domain Validation reference](../reference/validation.md) — the endpoint surface.
- [Errors](./errors.md) — interpreting validation error responses.
