Search the docs

Find a page, a section, or an endpoint.

Connecting a domain

Your own domain, which is an upgrade rather than a first step.

A domain is the one thing this API cannot finish for you. We can generate every record, check them, and tell you the moment they land — but we cannot write in somebody else's zone, and a product that asked you to move your nameservers so it could would be a different product.

So the job here is to make that step delegable. You get the records in the same response as the domain, you show them to your own customer in your own words, and nobody ever sees a Pidgeon screen.

Connect one

curl https://pidgeon.ai/api/v1/domains \
-H "Authorization: Bearer $PIDGEON_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "acme.com" }'

Five kinds of record come back. ownership is a TXT record proving the domain is yours; mx routes inbound mail to us; spf and dkim are what stop your outgoing mail being treated as spam; dmarc asks other providers to report on anything claiming to be you. Each carries a one-line description written for the person who has to publish it, so you do not have to write those sentences yourself.

Check dns_complete before you show the list. The DKIM records are derived from what our mail provider issued, and until that has happened the list looks complete and is missing three CNAMEs. Publishing it in that state gives you a domain that verifies and cannot sign mail — which fails in a way nobody debugs quickly.

Which name to type

Every record carries its name three ways. fqdn is the full name. host_relative is the same name relative to zone, the registrable domain it is published in, read from the Public Suffix List. name is relative to the domain you connected, and is kept as it always was.

For acme.com the difference is cosmetic. For a subdomain it is the whole job:

Recordnamezonehost_relativefqdn
MX, SPF@acme.comagentsagents.acme.com
DKIMabc123._domainkeyacme.comabc123._domainkey.agentsabc123._domainkey.agents.acme.com
DMARC_dmarcacme.com_dmarc.agents_dmarc.agents.acme.com

Squarespace, GoDaddy and Namecheap want host_relative and add the zone themselves, so a full name pasted there becomes agents.acme.com.acme.com. Route 53 asks for the full name. Cloudflare takes either. Route 53 and Vercel want the name field empty, not @, for a record at the zone itself.

If the subdomain is a zone of its own at your provider — its own hosted zone in Route 53, say — names are relative to that instead: @ for the subdomain, _dmarc for DMARC. fqdn is right either way.

Wait for it

curl -X POST https://pidgeon.ai/api/v1/domains/$DOMAIN_ID/verify \
-H "Authorization: Bearer $PIDGEON_API_KEY"

Idempotent and safe to poll. Calling it on a domain that is already verified re-checks and says so rather than erroring, because a poll loop that has to special-case success is a poll loop with a bug in it.

Two states, not one: verified means we believe the domain is yours, mail_configured means it can actually send and receive. A domain can sit at the first for a while — somebody publishes the TXT record and stops reading the instructions.

Better than polling: subscribe to domain.verified and do not poll at all. DNS propagation is measured in a zone's TTL and neither of us controls it, so a webhook costs you one request and a poll loop costs you hundreds.

When it does not verify

GET /v1/domains/{id} returns every record with its own status — connected, waiting, incorrect, or not_required — found, which is what we actually read at that name, and checked_at.

state says why, from our own lookup of public DNS beside the mail provider's verdict:

stateMeansDo
connectedWorking.Nothing.
publishedCorrect in public DNS; Amazon has not confirmed it yet. DKIM only.Wait.
missingNothing at the name yet. We ask the zone’s own nameservers, so a record you just published shows on the next check.Publish it, checking the name.
wrong_valueSomething else is at the name; found is what.Replace it.
wrong_nameOur value is at found_at instead, usually the zone typed twice.Change the name to host_relative.

published is normal, and can last. Amazon checks DKIM on its own schedule: usually within an hour of the records appearing, and it allows up to 72 hours. Pidgeon checks every domain still being set up every 5 minutes for two weeks after it is added, so checked_at moves without you polling.

That last field is the one that matters. A record with the right name and the wrong value looks exactly like a missing one until you can see what is there instead, and "your domain is not verified" is not something your customer can act on. "Your MX record points at mail.oldhost.com" is.

Disconnecting

DELETE /v1/domains/{id} takes the domain's addresses, their mail, their routing rules and their webhooks with it. There is no undo and no soft form of it. It takes an id rather than a name on purpose: an id comes from a list you just read, and a name comes from a variable that might be wrong.