DomainProof
Start here

Quickstart

Claim a sandbox domain and watch it verify, in one curl session, no real DNS required.

This walks through the entire verification lifecycle — claim, check, verified — against a sandbox domain that never touches real DNS. You'll need a test-mode API key (it looks like dp_test_...; grab one from your project's dashboard). Everything below uses curl, but the same three calls work from any HTTP client.

1Claim a domain

curl https://api.domainproof.dev/v1/domains \
  -H "Authorization: Bearer dp_test_k7h2m5p3qrtx_a3b4c5d6e7f2g3h4i5j6k7l2m3" \
  -H "Content-Type: application/json" \
  -d '{"domain": "pending-then-verified.test"}'

pending-then-verified.test is a sandbox domain (see Sandbox: verify without a real domain) — it simulates real-world DNS propagation delay instead of hitting the network. The response is the claim and its verification instructions:

{
  "domain": {
    "id": "3f2a1c4e-8b6d-4a1f-9c2e-7d5b6a1f0e2c",
    "domain": "pending-then-verified.test",
    "mode": "test",
    "status": "pending",
    "external_id": null,
    "createdAt": "2026-07-22T10:00:00.000Z",
    "updatedAt": "2026-07-22T10:00:00.000Z",
    "verifiedAt": null,
    "verificationUrl": "https://domainproof.dev/verify/f4h5k6m7n2p3q5r6s7t2u3v4w5",
    "records": [
      {
        "type": "TXT",
        "name": "_domainproof-challenge.pending-then-verified.test",
        "value": "domainproof-verify=n4p3q5r6s7t2u3v4w5x6y2z3a4",
        "purpose": "ownership",
        "description": "Proves control of pending-then-verified.test. This record does nothing else and can be removed after verification.",
        "status": "pending"
      }
    ]
  }
}

The record's _domainproof-challenge/domainproof-verify= prefix above is this guide's placeholder — yours is derived from your own project's name, so use the name/value your response actually returns, not this example's literal strings.

Save domain.id — you'll need it for the next call.

2Run a check

curl -X POST https://api.domainproof.dev/v1/domains/3f2a1c4e-8b6d-4a1f-9c2e-7d5b6a1f0e2c/verify \
  -H "Authorization: Bearer dp_test_k7h2m5p3qrtx_a3b4c5d6e7f2g3h4i5j6k7l2m3"

For the first 12 seconds after the claim, this sandbox domain reports the record isn't there yet:

{
  "domain": { "status": "pending", "...": "..." },
  "check": {
    "outcome": "not_found",
    "checkedAt": "2026-07-22T10:00:05.000Z",
    "explanation": "No record found yet. DNS changes usually take a few minutes to appear — we checked pending-then-verified.test's own nameservers to skip stale caches. Try verifying again shortly."
  }
}

This endpoint is safe to poll — it re-runs the check every time it's called, so you can call it again a moment later.

3Watch it verify

Call the same endpoint again after 12 seconds have passed since the claim:

{
  "domain": {
    "status": "verified",
    "verifiedAt": "2026-07-22T10:00:12.000Z",
    "...": "..."
  },
  "check": {
    "outcome": "found",
    "checkedAt": "2026-07-22T10:00:12.000Z"
  }
}

Done — that's the full loop: claim, check, verified. Polling /verify is entirely optional in production: a background scheduler re-checks every claimed domain on its own backoff schedule, so a claim eventually reflects a correct DNS record even if you never call this endpoint again. See How verification works for the full state machine and recheck cadence.

Using the SDK

The three calls above collapse into a claim, then a poll loop, using @domainproof/sdk:

import { DomainProof } from '@domainproof/sdk'

const domainproof = new DomainProof({
  apiKey: process.env.DOMAINPROOF_API_KEY!,
})

const claim = await domainproof.domains.claim({
  domain: 'pending-then-verified.test',
})
if (claim.error) throw claim.error

let result = await domainproof.domains.verify(claim.data.id)
while (!result.error && result.data.domain.status === 'pending') {
  await new Promise((resolve) => setTimeout(resolve, 5_000))
  result = await domainproof.domains.verify(claim.data.id)
}

if (result.error) throw result.error
console.log(result.data.domain.status) // 'verified'

Same sandbox domain, same 12-second settle time — the loop above just calls verify every 5 seconds instead of you timing two manual calls. See Claim and verify a domain for the SDK method behind every other call in this guide.

Where to next

Quickstart — DomainProof docs