Skip to content
tutorial

Check domain availability with one API call

| 5 min read
Domain search bar on a minimalist screen
Photo by Stephen Phillips on Unsplash

You're building a SaaS onboarding flow that suggests custom domains for new workspaces. The user types "acme" and your UI needs to check acme.com, acme.io, acme.dev in real time. Registrar APIs charge per lookup, require approval workflows, and impose strict rate limits. You need a lightweight availability check that returns a boolean and gets out of the way.

The botoi /v1/domain/availability endpoint does one thing: tell you whether a domain is registered. POST a domain name, get back available, registered, and the registrar name if it exists. No registrar account, no WHOIS parsing, no scraping.

The API call

curl -X POST https://api.botoi.com/v1/domain/availability \
  -H "Content-Type: application/json" \
  -d '{"domain": "acme.dev"}'

When a domain is available:

{
  "success": true,
  "data": {
    "domain": "acme.dev",
    "available": true,
    "registered": false
  }
}

When a domain is taken:

{
  "success": true,
  "data": {
    "domain": "google.com",
    "available": false,
    "registered": true,
    "registrar": "MarkMonitor Inc."
  }
}

The response is minimal on purpose. available is the boolean you'll branch on. registered is its inverse, included for readability. When the domain is taken, the registrar field shows who holds it. The endpoint queries RDAP (the official WHOIS successor), so the data is authoritative.

Browser address bar with domain name
Photo by Glenn Carstens-Peters on Unsplash

Real-time domain search UI

For a search-as-you-type experience, debounce the input and cancel stale requests with an AbortController. A 300ms delay prevents firing on every keystroke while keeping the UI responsive.

const controller = { current: null };

async function checkDomain(name) {
  if (controller.current) controller.current.abort();
  controller.current = new AbortController();

  const res = await fetch("https://api.botoi.com/v1/domain/availability", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ domain: name }),
    signal: controller.current.signal,
  });

  return res.json();
}

const input = document.querySelector("#domain-input");
const results = document.querySelector("#domain-results");
let timeout;

input.addEventListener("input", (e) => {
  clearTimeout(timeout);
  timeout = setTimeout(async () => {
    const name = e.target.value.trim();
    if (!name) return;

    const { data } = await checkDomain(name);
    results.innerHTML = data.available
      ? `<span class="text-green-600">\${data.domain} is available</span>`
      : `<span class="text-red-600">\${data.domain} is taken</span>`;
  }, 300);
});

The abort logic matters. Without it, a slow response for "acm" can overwrite the result for "acme" if the requests resolve out of order. The AbortController guarantees you always display the result for the latest input.

Batch-check multiple TLDs

Most domain search tools check several TLDs at once. Fire parallel requests with Promise.all and show results as a grid.

const TLDS = [".com", ".io", ".dev", ".app", ".co"];

async function checkAllTlds(name) {
  const checks = TLDS.map((tld) =>
    fetch("https://api.botoi.com/v1/domain/availability", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ domain: name + tld }),
    }).then((r) => r.json())
  );

  const results = await Promise.all(checks);
  return results.map((r) => ({
    domain: r.data.domain,
    available: r.data.available,
  }));
}

// Usage
const options = await checkAllTlds("acme");
console.log(options);
// [
//   { domain: "acme.com", available: false },
//   { domain: "acme.io", available: true },
//   { domain: "acme.dev", available: true },
//   { domain: "acme.app", available: true },
//   { domain: "acme.co", available: false }
// ]

Five parallel requests complete in the time of one sequential request. On the free tier (5 req/min, 100 req/day), this works for development and demos. For production, add an API key to remove rate limits.

Node.js domain suggestion feature

A domain suggestion engine generates candidates by combining the user's keyword with common prefixes and TLDs, then filters down to what's available. Here's a complete function you can drop into an Express or Hono backend.

async function suggestDomains(keyword) {
  const prefixes = [keyword, `get\${keyword}`, `try\${keyword}`, `use\${keyword}`];
  const tlds = [".com", ".io", ".dev"];
  const candidates = prefixes.flatMap((p) => tlds.map((t) => p + t));

  const checks = candidates.map((domain) =>
    fetch("https://api.botoi.com/v1/domain/availability", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer YOUR_API_KEY",
      },
      body: JSON.stringify({ domain }),
    }).then((r) => r.json())
  );

  const results = await Promise.all(checks);
  return results
    .filter((r) => r.data.available)
    .map((r) => r.data.domain);
}

// Returns something like:
// ["getacme.com", "tryacme.io", "useacme.dev", ...]
const available = await suggestDomains("acme");
console.log("Available domains:", available);

This checks 12 candidates in parallel (4 prefixes x 3 TLDs). The function returns only available domains, so your frontend can render them as suggestions without any extra filtering.

Express endpoint for a SaaS onboarding flow

Wire the batch check into an Express route that your frontend calls during workspace creation. The backend handles the API key, so it never reaches the client.

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/check-domain", async (req, res) => {
  const { name } = req.body;
  if (!name || typeof name !== "string") {
    return res.status(400).json({ error: "name is required" });
  }

  const tlds = [".com", ".io", ".dev", ".app"];
  const checks = tlds.map((tld) =>
    fetch("https://api.botoi.com/v1/domain/availability", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer \${process.env.BOTOI_API_KEY}`,
      },
      body: JSON.stringify({ domain: name + tld }),
    }).then((r) => r.json())
  );

  const results = await Promise.all(checks);
  const domains = results.map((r) => ({
    domain: r.data.domain,
    available: r.data.available,
    registrar: r.data.registrar || null,
  }));

  res.json({ domains });
});

app.listen(3000);

Your frontend POSTs { name: "acme" } and gets back a list of domains with availability status. From there, render green/red indicators and let the user pick.

Key points

  • One endpoint, one purpose. POST /v1/domain/availability with a domain field. The response tells you if it's registered, who the registrar is, and nothing else.
  • RDAP-backed. The endpoint queries RDAP, the ICANN-sanctioned replacement for WHOIS. No screen scraping, no brittle text parsing.
  • Works without an API key. Anonymous access at 5 requests per minute covers prototyping and development. Add a key when you go to production.
  • Parallel-friendly. Batch-check TLDs with Promise.all. Five checks complete in the same wall-clock time as one.
  • Sub-200ms responses. Edge-deployed on Cloudflare Workers, so the latency bottleneck is the upstream RDAP server, not the API itself.

Frequently asked questions

Do I need an API key to check domain availability?
No. The free tier allows anonymous access at 5 requests per minute with IP-based rate limiting. For batch checks or production apps, add an API key to the Authorization header for higher limits.
Which TLDs does the domain availability endpoint support?
The endpoint queries RDAP servers, which cover all ICANN-accredited TLDs including .com, .net, .org, .io, .dev, .app, and hundreds of country-code TLDs. Coverage depends on whether the TLD operator publishes RDAP data.
How accurate is the availability check?
The endpoint queries RDAP, the official successor to WHOIS. A 404 from RDAP means the domain is not registered. A 200 with registration data means it is taken. Edge cases like premium domains or registry-reserved names may show as available even though they cannot be registered through normal channels.
Does the response include WHOIS or registrar information?
Yes. When a domain is registered, the response includes the registrar name extracted from the RDAP entity data. If the RDAP response does not contain registrar information, the registrar field is omitted.
Can I check multiple domains in a single request?
The endpoint accepts one domain per request. For batch checks, send parallel requests using Promise.all in JavaScript or asyncio.gather in Python. The API handles concurrent requests with no issues at standard rate limits.

Try this API

WHOIS Lookup API — interactive playground and code examples

More tutorial posts

Start building with botoi

150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.