Bulk Domain Lookup API — Check 10 Domains in One Request

February 28, 2026

You manage 50 domains and need to check which ones expire in the next 30 days. With single-domain lookups, that's 50 HTTP round-trips — 50 separate connections, 50 response parses, and separate error handling for each.

The RDAP API bulk endpoint takes up to 10 domains per request and resolves them concurrently server-side. 50 domains becomes 5 requests instead of 50 — and each one returns normalized JSON for every domain in a single response.

Why bulk instead of parallel single calls?

You could fire 10 single-domain requests in parallel with Promise.all() or asyncio.gather(). That works, but bulk gives you:

  • One HTTP round-trip instead of ten — less connection overhead, fewer sockets, simpler timeout handling
  • Server-side concurrency — the API resolves all 10 domains in parallel against upstream RDAP servers, handling per-server rate limits and retries internally
  • Atomic response — one JSON object with a summary telling you exactly how many succeeded and failed. No need to merge 10 separate responses yourself

Quick start

curl -X POST https://rdapapi.io/api/v1/domains/bulk \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["google.com", "github.com", "example.net"]}'

Response:

{
  "results": [
    {
      "domain": "google.com",
      "status": "success",
      "data": {
        "domain": "google.com",
        "registrar": {
          "name": "MarkMonitor Inc.",
          "iana_id": "292",
          "abuse_email": "[email protected]"
        },
        "dates": {
          "registered": "1997-09-15T04:00:00Z",
          "expires": "2028-09-14T04:00:00Z",
          "updated": "2019-09-09T15:39:04Z"
        },
        "nameservers": ["ns1.google.com", "ns2.google.com"],
        "dnssec": false
      }
    },
    {
      "domain": "github.com",
      "status": "success",
      "data": { "..." : "..." }
    },
    {
      "domain": "example.net",
      "status": "success",
      "data": { "..." : "..." }
    }
  ],
  "summary": {
    "total": 3,
    "successful": 3,
    "failed": 0
  }
}

Each domain is resolved independently — if one fails, the others still return data.

Practical example: domain expiration monitor

Here's a script that checks a list of domains and flags any expiring within 30 days. Run it as a daily cron job.

from rdapapi import RdapApi
from datetime import datetime, timezone

DOMAINS = [
    "mycompany.com", "mycompany.io", "mycompany.dev",
    "myproduct.com", "myproduct.app",
    "mybrand.org", "mybrand.net", "mybrand.co",
]

client = RdapApi("YOUR_API_KEY")
now = datetime.now(timezone.utc)

# Bulk accepts 10 domains max — batch if you have more
for i in range(0, len(DOMAINS), 10):
    results = client.domains.bulk(DOMAINS[i:i + 10])

    for r in results.results:
        if r.status != "success":
            print(f"WARNING: {r.domain} — lookup failed ({r.error})")
            continue

        expires = datetime.fromisoformat(r.data.dates.expires)
        days_left = (expires - now).days

        if days_left <= 30:
            print(f"URGENT: {r.domain} expires in {days_left} days")
        elif days_left <= 90:
            print(f"SOON: {r.domain} expires in {days_left} days")
import { RdapApi } from "rdapapi";

const DOMAINS = [
  "mycompany.com", "mycompany.io", "mycompany.dev",
  "myproduct.com", "myproduct.app",
  "mybrand.org", "mybrand.net", "mybrand.co",
];

async function checkExpirations() {
  const client = new RdapApi("YOUR_API_KEY");
  const now = new Date();

  // Bulk accepts 10 domains max — batch if you have more
  for (let i = 0; i < DOMAINS.length; i += 10) {
    const { results } = await client.domains.bulk(DOMAINS.slice(i, i + 10));

    for (const r of results) {
      if (r.status !== "success") {
        console.log(`WARNING: ${r.domain} — lookup failed (${r.error})`);
        continue;
      }

      const expires = new Date(r.data.dates.expires);
      const daysLeft = Math.floor((expires - now) / 86400000);

      if (daysLeft <= 30) {
        console.log(`URGENT: ${r.domain} expires in ${daysLeft} days`);
      } else if (daysLeft <= 90) {
        console.log(`SOON: ${r.domain} expires in ${daysLeft} days`);
      }
    }
  }
}

checkExpirations();

A few things to note about the bulk endpoint:

  • 10 domains per request. If you have more, batch in chunks of 10 — both examples above do this. There's no delay needed between requests. We cap at 10 to keep response times predictable (uncached domains require upstream RDAP fetches)
  • Duplicate domains in the same request are automatically deduplicated (case-insensitive)
  • Responses are cached for 24 hours. If you poll the same domains daily, you'll mostly hit cache — fast responses, but each domain still counts as one lookup against your quota. The trade-off is predictable billing: you always know the cost upfront

Registrar follow-through

For thin registries like .com and .net, the registry only stores basic data. The registrar (GoDaddy, Cloudflare, etc.) holds contacts and entity details on a separate RDAP server.

Set follow: true to automatically follow the registrar link and merge both responses:

curl -X POST https://rdapapi.io/api/v1/domains/bulk \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["google.com", "github.com"], "follow": true}'

With follow, the entities field includes registrant details when available:

{
  "entities": {
    "registrant": {
      "organization": "Google LLC",
      "country_code": "US"
    }
  }
}

Without it, entities is typically empty for .com domains because VeriSign's registry doesn't store contact data. See our RDAP vs WHOIS guide for background on thin vs. thick registries.

Error handling

The bulk endpoint always returns HTTP 200 — even if individual domains fail. Check each result's status field:

{
  "results": [
    {
      "domain": "google.com",
      "status": "success",
      "data": { "..." : "..." }
    },
    {
      "domain": "invalid..com",
      "status": "error",
      "error": "invalid_domain",
      "message": "The provided domain name is not valid."
    }
  ],
  "summary": {
    "total": 2,
    "successful": 1,
    "failed": 1
  }
}

One bad domain doesn't break the others — no retry logic needed for the whole batch.

HTTP-level errors (these affect the entire request):

Code Meaning
401 Missing or invalid API key
403 Plan doesn't support bulk (Starter) — upgrade here
422 Validation error (>10 domains, empty array)
429 Rate limit or monthly quota exceeded

Pricing

Bulk requires a Pro ($49/mo, 200k lookups) or Business ($199/mo, 1M lookups) plan. Starter plans return a 403.

Each domain in a bulk request counts as one lookup against your monthly quota — a 10-domain request uses 10 lookups. All plans come with a free 7-day trial.

What's next


Ready to try RDAP lookups?