webXtrend API

Query live domain intelligence for leads, market analysis, and research.

Getting started

Every endpoint takes a JSON body over POST and answers with JSON, unless it delivers a file. The same call also works as a GET with the parameters in the query string, convenient for a browser, wget or a cron job, at the price of putting your token into the URL, where proxies and shell history can see it.

Base URL
https://api.webxtrend.com/v1.0/
Endpoints at a glance
EndpointPurposeAccess
/lookupCharacteristics of a single domainany plan
/redirectDomains that forward to a domainany plan
/reverseipDomains currently sharing an IP addressany plan
/dnshistoryHow a domain’s DNS records changed over timeBasic or Premium
/dailyDomains discovered on one day, gzipped, plus follow-upBasic; follow-up Premium
/snapshotThe complete domain list, gzipped, names or enrichedList / Data Export
Headers
Content-Typeapplication/json
Acceptapplication/json
Accept-Encodinggzip (recommended, responses can be large)
Authentication

There is no separate auth header. Pass your token in the body of every request. You find it in your dashboard under API access.

All queries made with any token of your account count towards one shared daily limit, and issuing a second token does not raise it.
Example request
curl -X POST https://api.webxtrend.com/v1.0/lookup \
  -H 'Accept-Encoding: gzip' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "token": "<your_token>",
    "query": "example.com"
  }'

Limits & errors

Each account has a daily query budget. It resets at midnight, and a 429 carries a Retry-After header with the exact seconds until then; read that rather than working out the boundary from your own clock.

Daily quota
PlanQueries per day
Free100
Basic25,000
Premium250,000
When the quota is used up

The API answers with 429 and a Retry-After header holding the seconds until reset.

{
  "rcode": 429,
  "error": "rate_limited",
  "msg": "daily query limit reached",
  "limit": 25000,
  "used": 25000
}
Shape of an error
HTTP/1.1 403

{
  "rcode": 403,
  "error": "after_subscription",
  "msg": "Your subscription ended. Daily
    exports up to 2026-10-03 stay available."
}
Branch on error, not on msg. The message is English prose meant for a person and gets rewritten when a clearer wording turns up; several messages carry a date or a product name inside the sentence, so even an exact comparison would not hold. error stays as it is.

The status code alone is not enough either, since seven different situations answer with 403. Only error tells them apart.

Every error key
StatuserrorMeaning
400query_missingNo query in the body, or it is empty.
400domain_formatThe value has no dot in it and cannot be read as a domain. An unknown suffix is not checked here; that comes back as 404 no_data.
400date_missingNo date in the body.
400date_formatThe date is not YYYY-MM-DD.
400type_invalidtype is not one of the seven record types.
400followup_invalidfollowup is not 7 or 30.
400cursor_invalidThe next cursor cannot be read, or it was altered on the way. Pass back what you received rather than building one.
400cursor_expiredThe cursor is older than 24 hours. Start the run again from the first page.
400cursor_mismatchThe cursor was issued for a different query.
400cursor_needs_typenext without type on /dnshistory.
401token_missingNo token in the body.
401token_invalidThe token is unknown or no longer active. Generate a new one.
403plan_requiredThe endpoint needs a Basic or Premium plan.
403premium_requiredThe endpoint or option needs Premium.
403addon_requiredThe export add-on has not been purchased. msg names which one.
403no_subscriptionThe account has no subscription period at all.
403before_subscriptionThe date lies before your first subscription. msg names the earliest one available.
403after_subscriptionYour subscription ended and the date lies after it. msg names your last available day.
403outside_subscriptionThe date falls in a gap between two subscription periods.
404no_dataNothing stored for that domain, that date, or no snapshot built yet. Not an error in your request.
404endpoint_unknownNo such endpoint under /v1.0/.
429rate_limitedDaily limit reached. The response carries limit and used, plus a Retry-After header counting down to midnight.
500internal_errorSomething broke on our side. Worth retrying.
/lookup, /redirect and /reverseip still wrap their error inside result rather than putting it at the top level, the way the other endpoints do. rcode, error and msg carry the same values in both shapes. Look one level deeper on those three.

Domain lookup

The key characteristics of a single domain in one call: whether a website answers, how it redirects, what its certificate looks like, and how many other domains point at it or share its address.

POST /lookup Free Try it
Request parameters
ParameterTypeRequiredDescription
tokenstringrequiredYour API token.
querystringrequiredThe domain to look up, without scheme and without a trailing slash: example.com, not https://example.com/. Case is ignored.
Request
curl -X POST https://api.webxtrend.com/v1.0/lookup \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN",
    "query": "example.com"
  }'
Response
{
  "meta": {
    "query": "example.com",
    "rcode": 200,
    "msg": "ok"
  },
  "result": {
    "has_website": "yes",
    "redirect_to_https": "no",
    "redirect_to_www": "no",
    "redirect_to_domain": "no",
    "only_with_www": "no",
    "only_dns_no_web": "no",
    "ssl": "nocert",
    "ip": "both",
    "redirect_count": "1114",
    "reverse_ip_count": "3",
    "social_networks": [],
    "social_accounts": []
  }
}
Response fields
FieldTypeDescription
has_websiteyes / noA website is hosted under the domain and does not redirect somewhere else.
redirect_to_httpsyes / noThe domain redirects from http to https.
redirect_to_wwwyes / noThe domain redirects to www. plus itself.
redirect_to_domainyes / noThe domain redirects to a different domain. Which one, and who else points there, is what /redirect answers.
only_with_wwwyes / noReachable only as www.example.com, the bare domain answers nothing.
only_dns_no_webyes / noThe domain has DNS entries but no A or AAAA record, so nothing can be reached over the web.
sslvalid / nocert / invalidnocert means no certificate was presented, invalid that one was presented but did not validate: expired, self-signed or issued for another name.
ipv4only / v6only / both / servfail / noip / nxdomainWhich address families resolve. nxdomain means the domain is not in the zone right now. It is not a statement about the past: a domain that carries this flag was usually registered once and has been deleted or has expired, and the records you get for it describe that earlier state, not today. servfail means the responsible name server failed to answer. The field is left out entirely for domains whose resolution has not been recorded yet. It is the one key in result that can be missing.
redirect_countnumberHow many domains redirect to this one. Returned as a string.
reverse_ip_countnumberHow many domains currently share its IP addresses, summed over every address the domain resolves to, which is what /reverseip returns. A domain on two of those addresses counts once per address. Returned as a string.
social_networksarray of stringsThe networks the domain is present on, for example facebook, instagram, x, linkedin, youtube, tiktok, pinterest. Empty array when none were found. Use this to filter without walking social_accounts.
social_accountsarray of objectsThe accounts themselves, each with network, type, username and url. A domain can have more than one account per network. type is finer than network: a Facebook entry can be facebook or facebook-pages, a LinkedIn one linked-in-company. Treat it as a label, not as a closed set: it comes from the crawler and new values appear when new link patterns do. The links are the ones found on the crawled start page, so they are what the site itself points to, not what the network says about the domain. Empty array when none were found, which is the case for example.com above. A domain that has them looks like this:
"social_networks": ["instagram", "facebook", "linkedin"],
"social_accounts": [
  {
    "network": "facebook",
    "type": "facebook",
    "username": "heiseonline",
    "url": "https://facebook.com/heiseonline"
  },
  {
    "network": "linkedin",
    "type": "linked-in-company",
    "username": "heiseonline",
    "url": "https://de.linkedin.com/company/heiseonline"
  }
]
Errors
StatuserrorWhen
400query_missingNo query in the body.
400domain_formatThe value has no dot in it, so there is nothing to read as a domain. A name with an unknown suffix passes this check and comes back as 404 no_data.

The errors every endpoint can return (invalid token, daily limit) are listed under Errors & limits.

Incoming redirects

Every domain that redirects to the one you ask about. Useful for finding the parked or expired names a brand collects, and for spotting typo domains pointed at a site.

POST /redirect Free Try it
Request parameters
ParameterTypeRequiredDescription
tokenstringrequiredYour API token.
querystringrequiredThe target domain, the one others redirect to.
limitnumberoptionalRows per page. Default 1,000. Free stays at 1,000, Basic caps at 10,000, Premium at 50,000; a higher value is silently reduced to the cap rather than refused.
nextstringoptionalThe cursor from the previous response, passed back verbatim. It is tied to the query it was issued for; sending it with a different domain is refused.
Request
curl -X POST https://api.webxtrend.com/v1.0/redirect \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN",
    "query": "example.com"
  }'
Response
{
  "meta": {
    "query": "example.com",
    "limit": 1000,
    "max_limit": 10000,
    "rcode": 200,
    "msg": "ok",
    "next": null
  },
  "result": [
    {"target": "coovii.co.jp"},
    {"target": "supara.fun"},
    {"target": "aethra.org"}
  ]
}
Response fields
FieldTypeDescription
result[].targetstringA domain that redirects to the one you asked about. Despite the name, this is the source of the redirect.
meta.limitnumberThe page size actually applied: your value, or your plan’s ceiling where it was higher.
meta.max_limitnumberThe ceiling your plan allows for limit.
meta.nextstring / nullCursor for the following page. null means you have seen everything. That, not an empty result, is the signal to stop.
Paging: send the request again with next set to the value you just received, leaving query unchanged. Keep going until meta.next comes back null. Every call counts against your daily quota and a cursor expires after 24 hours, so on a target with millions of rows a larger limit is not just faster; it decides whether you get through at all.
The cursor is an opaque, sealed string: it says nothing about how the page was found, it cannot be built by hand, and an altered one is refused rather than acted upon. Treat it as a token to hand back unchanged. It is safe in a URL as it stands, and it is valid for 24 hours, long enough to walk through a result set, not long enough to bookmark.
Errors
StatuserrorWhen
400query_missingNo query in the body.
400domain_formatThe value does not parse as a domain.
400cursor_invalidThe next cursor could not be read, or it was altered on the way. Do not build one yourself; pass back what you received.
400cursor_expiredThe cursor is older than 24 hours. A run is meant to be walked through in one go. Start again from the first page.
400cursor_mismatchThe cursor was issued for a different query.

Reverse IP

The neighbours: every domain that currently resolves to the same address as the one you ask about. On shared hosting that is the other sites on the box; on a dedicated address it is usually just the domain itself.

A domain rarely has just one address: an A record and an AAAA record, or several behind a round robin. The answer covers all of them, and every row names the address it belongs to. You can also ask with an address directly and skip the domain.

Only the present state is returned. A domain that moves, or stops resolving altogether, drops out of the answer on its next crawl, and addresses it used to sit on are not included.

POST /reverseip Free Try it
Request parameters
ParameterTypeRequiredDescription
tokenstringrequiredYour API token.
querystringrequiredA domain, or an IP address. With a domain you get the neighbours on every address it resolves to; with an address, the domains on that one. IPv4 and IPv6 both work, and an IPv6 may be written either way: 2606:2800:0220:0001:0000:0000:0000:0001 and 2606:2800:220:1::1 are the same query. Addresses are stored numerically, so the two forms are the same value, not two spellings that have to be tried.
limitnumberoptionalRows per page. Default 1,000. Free stays at 1,000, Basic caps at 10,000, Premium at 50,000; a higher value is silently reduced to the cap rather than refused.
nextstringoptionalThe cursor from the previous response, passed back verbatim. It is tied to the query it was issued for. Paging walks the addresses one after another, so a page may end in the middle of one and the next page carries on there.
Request
curl -X POST https://api.webxtrend.com/v1.0/reverseip \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN",
    "query": "example.com"
  }'
Response
{
  "meta": {
    "query": "example.com",
    "limit": 1000,
    "max_limit": 10000,
    "rcode": 200,
    "msg": "ok",
    "ips": ["104.20.23.154", "2606:4700:10::6814:179a"],
    "ip_count": 2,
    "next": null
  },
  "result": [
    {"target": "altair-labs.de",     "ip": "104.20.23.154", "since": "2026-04-14T02:23:44"},
    {"target": "kranich-versand.at", "ip": "104.20.23.154", "since": "2026-08-02T05:43:46"},
    {"target": "nordlicht.shop",     "ip": "2606:4700:10::6814:179a", "since": "2026-09-16T08:25:46"}
  ]
}
Response fields
FieldTypeDescription
result[].targetstringA domain sharing an IP address with the one you asked about. The field carries the same name as in /redirect, but means something else here: a neighbour, not a redirect source.
result[].ipstringThe address this row belongs to, always in its shortest form (2606:4700:10::6814:179a, never the written-out variant). With several addresses in play, this is what tells them apart; group on it to get one neighbourhood per address.
result[].sincestringWhen this domain first appeared on this address, in the same notation as first_seen in /dnshistory. That the row is here at all already means it is there now, and this tells you for how long. It replaces the short-lived last_seen, which answered a question the present-state index no longer asks.
meta.ipsarrayEvery address the domain currently resolves to, in the order the rows come in. Asked with an address, it holds just that one.
meta.ip_countnumberHow many addresses that is.
meta.limitnumberThe page size actually applied: your value, or your plan’s ceiling where it was higher.
meta.max_limitnumberThe ceiling your plan allows for limit.
meta.nextstring / nullCursor for the following page, null when the list is exhausted.
A domain sharing an address says little on its own. Large hosters put thousands of unrelated sites behind one IP, and a CDN address is shared by everyone using it. Read the list as a hosting neighbourhood, not as a relationship between the sites. Behind an anycast front the addresses rotate: each crawl sees a different slice of the pool, so meta.ips holds the slice we last resolved, not the whole pool.
Errors
StatuserrorWhen
400query_missingNo query in the body.
400domain_formatThe value parses as neither a domain nor an IP address.
400cursor_invalidThe next cursor could not be read, or it was altered on the way.
400cursor_expiredThe cursor is older than 24 hours. A run is meant to be walked through in one go. Start again from the first page.
400cursor_mismatchThe cursor belongs to another query.
404no_dataNothing on record for that domain or address.

DNS history

How a domain’s records changed over time. Each entry is a state with the date it was first seen, so you can follow a move between hosters, a change of mail provider or the moment a domain was parked.

POST /dnshistory Basic & Premium Try it
Request parameters
ParameterTypeRequiredDescription
tokenstringrequiredYour API token.
querystringrequiredThe domain to trace.
typeA, AAAA, MX, TXT, NS, SOA, CNAMEoptionalOne record type. Left out, you get all seven at once, which is convenient for a first look but cannot be paged.
limitnumberoptionalEntries per record type. Default 10. Basic caps at 10, Premium at 100; a higher value is silently reduced to the cap rather than refused.
nextstringoptionalThe meta.next of the previous response, passed back verbatim. It names the domain and the record type itself, so type may be left out alongside it; sent anyway, it has to agree. A bare first_seen from the response also works and then does need type.
Request
curl -X POST https://api.webxtrend.com/v1.0/dnshistory \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN",
    "query": "example.com",
    "type": "NS"
  }'
Response
{
  "meta": {
    "query": "example.com",
    "rcode": 200,
    "count": 2,
    "max_limit": 100,
    "totals": {"NS": 2}
  },
  "result": {
    "NS": [
      {
        "first_seen": "2025-12-17T22:30:00",
        "records": [
          {"nameserver": "elliott.ns.cloudflare.com"}
        ]
      }
    ]
  }
}
Response fields
FieldTypeDescription
meta.countnumberHow many states this response contains.
meta.max_limitnumberThe ceiling your plan allows for limit: 10 on Basic, 100 on Premium.
meta.limitnumberThe limit actually applied: your value, or your plan’s ceiling where it was higher.
meta.totalsobjectHow many states exist per record type, regardless of limit. Use it to tell “that is all there is” apart from “the page was full”.
meta.totals_cappedarrayRecord types whose count stopped at 1,000. Their number in totals reads as “at least”. Absent when nothing was capped.
meta.truncatedarrayRecord types that have older states beyond this page. Absent when every type is complete.
meta.nextstringThe cursor for the following page, present only when you asked for a single type and that type is truncated. On a request without type the field is absent; truncated then names the types to continue one at a time.
result.<type>[].first_seenstringWhen this state was first observed, as YYYY-MM-DDThh:mm:ss. It can be passed back as next together with type, useful for resuming days later from a value you stored. For paging through in one go, meta.next is the shorter path.
result.<type>[].recordsarrayThe records of that state. Their shape depends on the type (see below).
Paging: one record type at a time. Send type, then repeat the call with next set to the meta.next you just received, until that field is gone. Without type there is no cursor: a collection call is a first look, and meta.truncated tells you which types to go through afterwards.
Record shapes
TypeFields of each entry in records
Aipv4_address
AAAAipv6_address
NSnameserver, the authoritative name server.
CNAMEtarget, the name the alias points to.
MXpriority, mail_server; lower priority wins.
TXTtext, parts; DNS splits anything longer than 255 bytes, so a long SPF or DKIM record arrives in several character strings. parts keeps them, text is those parts joined.
SOAprimary_nameserver, admin_email, serial, refresh_seconds, retry_seconds, expire_seconds. The zone stores the administrator’s address as a domain name, where the first dot stands for the @; admin_email is that address written out. minimum_ttl_seconds is present only where the zone reported it; older history predates its collection, and the field is then left out rather than returned as null.
Errors
StatuserrorWhen
403plan_requiredThe DNS history needs a Basic or Premium plan.
400query_missingNo query in the body, or it is empty.
400domain_formatThe value does not parse as a domain.
400type_invalidNot one of A, AAAA, MX, TXT, NS, SOA, CNAME.
400cursor_needs_typeYou sent next without type; paging works one record type at a time.
400cursor_invalidnext is neither a cursor from a previous response nor a first_seen value.
400cursor_expiredThe cursor is older than 24 hours. A run is meant to be walked through in one go. Start again from the first page.
400cursor_mismatchThe cursor was issued for a different domain, or for another record type than the type you sent alongside it.

Daily domains

Every domain registered on one day, as a gzipped list with one name per line. The file for a day is written the following night, so the newest one available always carries yesterday’s date.

POST /daily Basic & Premium Try it
On success the body is the .txt.gz file itself, not JSON. Only an error comes back as JSON, so check the status code before you unpack.
Every endpoint answers to GET with the same parameters in the query string, and for a download that is the natural form: it can be handed to wget or a cron job as a plain URL: ?token=…&date=2026-09-03&followup=7. That is the form the explorer shows under “Request URL”. Keep in mind that the token then sits in the URL, where proxies and shell history can see it.
Request parameters
ParameterTypeRequiredDescription
tokenstringrequiredYour API token.
dateYYYY-MM-DDrequiredThe registration day. Anything but this exact shape is refused before the file is looked up.
followup7 or 30optionalInstead of the bare name list, the follow-up file for that day: what became of those domains a week or a month later. Premium only.
Request
curl -X POST https://api.webxtrend.com/v1.0/daily \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN",
    "date": "2026-09-03"
  }' \
  -o 2026-09-03.txt.gz
Response headers
HTTP/1.1 200
Content-Type: application/gzip
Content-Disposition: attachment;
  filename="2026-09-03.txt.gz"
Content-Length: 1272043

# unpacked, one domain per line:
nordlicht.shop
altair-labs.de
kranich-versand.at
Follow-up files
followupContainsTypical size
7State one week after registration~324,000 domains
30State one month after registration~389,000 domains

These carry website, mail, SSL and social columns alongside the name, which is what makes a freshly registered domain worth judging: on registration day almost none of them resolve to anything yet.

Which days you can fetch
Only days covered by a subscription period. The file for your start date is the one named the day before it, and the same shift applies to your last day: a subscription running out on 4 October has 2026-10-03 as its final delivery. Days you paid for stay available after the subscription ends; days outside any period are refused with 403.
Errors
StatuserrorWhen
403plan_requiredDaily downloads need a Basic or Premium plan, and the account has neither now nor in the past.
400date_missingNo date in the body.
400date_formatNot YYYY-MM-DD.
403before_subscriptionThe date lies before your first subscription. The message names the earliest one available to you.
403after_subscriptionYour subscription has ended and the date lies after it. The message names your last available day.
403outside_subscriptionThe date falls in a gap between two subscription periods.
403no_subscriptionThe account has no subscription period at all.
403premium_requiredfollowup on a period that ran on Basic.
400followup_invalidNot 7 or 30.
404no_dataThe day is within your period, but no file was produced.

Domain list export

One file with every registered domain we know of, over a billion names. Without download the call reports what is available; with it you get the file.

POST /snapshot Add-on purchase Try it
As with /daily, the call also works as a GET with the parameters in the query string (?token=…&download=1), which is the easier form for a file of this size. The token is then part of the URL.
Request parameters
ParameterTypeRequiredDescription
tokenstringrequiredYour API token.
download"1"optionalSend the file instead of the report. Default: report only.
enriched"1"optionalThe enriched edition, with website, mail and SSL columns beside each name. Needs its own add-on.
Request: what is available
curl -X POST https://api.webxtrend.com/v1.0/snapshot \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN"
  }'
Request: the file
curl -X POST https://api.webxtrend.com/v1.0/snapshot \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "YOUR_TOKEN",
    "download": "1"
  }' \
  -o domains.txt.gz
Response
{
  "meta": {
    "rcode": 200,
    "msg": "snapshot available",
    "generated": "2026-09-01T03:12:44+02:00",
    "domains": 1148392011,
    "size": 1073741824,
    "filename": "domains-2026-09-01.txt.gz"
  },
  "result": []
}
Response fields
FieldTypeDescription
meta.generatedstringWhen the snapshot was built, with time zone offset. A new one is produced nightly.
meta.domainsnumberHow many names the file contains.
meta.sizenumberCompressed size in bytes. Worth checking before you start the transfer: this is a multi-gigabyte file.
meta.filenamestringThe name the download will carry.
meta.formatnames / enrichedWhich edition the figures above describe, so a client that passes enriched can confirm it got what it asked for.
meta.enriched_availabletrue / falseWhether this account may request the enriched edition. It is answered on every call, including one without enriched, so you can tell an add-on you already hold from one you would still have to buy without provoking a 403.
Errors
StatuserrorWhen
403addon_requiredThe export has not been purchased, also for enriched without that second add-on. The message names which one is missing.
404no_dataNo snapshot has been built yet.

Need something else?

If you need additional endpoints, bulk exports, or higher limits, reach out to us.