Install
The library supports Python 3.9 and later. Install it from PyPI:
pip install checkanysiteQuickstart
CheckAnySite() reads your key from the CHECKANYSITE_API_KEY environment variable; pass api_key= to use another. The following example checks whether a site shows traces of an AI website builder:
from checkanysite import CheckAnySite
client = CheckAnySite() # Reads the key from CHECKANYSITE_API_KEY.
site = client.sites.retrieve("stripe.com", checks=["ai"])
if site.ai.level == "none":
print(f"No AI builder traces found on {site.domain}.")
else:
builders = ", ".join(builder.name for builder in site.ai.builders)
print(f"{site.domain}: traces of {builders}, {site.ai.level} confidence")
print(f"Evidence: {site.result_url}")client.sites.retrieve returns our stored result for a site straight away and never starts a scan. For a site we haven’t checked yet, it raises NotFoundError.
Scans
client.scans.create_and_wait starts a scan and polls it until it is complete; most scans take a few seconds. To start a scan without waiting, call client.scans.create, then client.scans.wait when you need the result. A stored result costs fewer credits than a new scan, so the following function uses one when it exists and scans the site otherwise:
from checkanysite import CheckAnySite, NotFoundError
client = CheckAnySite()
def ai_traces(domain):
"""Returns the AI builder traces of a site, or None if its scan failed."""
try:
return client.sites.retrieve(domain, checks=["ai"]).ai
except NotFoundError:
scan = client.scans.create_and_wait(domain, checks=["ai"])
return scan.result.ai if scan.status == "completed" else NoneIf a scan takes longer than timeout (120 seconds by default), the library raises ScanTimeoutError. The scan keeps running, so you can read it later with client.scans.retrieve(error.scan_id). To check many sites in one request, see Batches.
Errors
When the API returns an error, the library raises a subclass of checkanysite.APIStatusError: AuthenticationError for 401, NotFoundError for 404, RateLimitError for 429, and so on. Each carries the error’s code and message, and the request_id to include when you contact us. When the API can’t be reached, it raises checkanysite.APIConnectionError. The errors page explains every code.
import logging
import checkanysite
from checkanysite import CheckAnySite
logger = logging.getLogger(__name__)
client = CheckAnySite()
try:
site = client.sites.retrieve("wikipedia.org", checks=["ai"])
except checkanysite.AuthenticationError as error:
logger.error("CheckAnySite rejected the API key: %s", error.message)
raise
except checkanysite.RateLimitError as error:
# Requests over the rate limit were already retried. A spent monthly or
# daily limit lasts until its reset, which the message gives.
logger.warning("CheckAnySite limit reached: %s", error.message)
raise
except checkanysite.APIStatusError as error:
logger.error(
"CheckAnySite returned %s (%s): %s Request ID: %s",
error.status_code,
error.code,
error.message,
error.request_id,
)
raiseRetries
- The library retries a failed request twice by default: connection errors and timeouts,
409,429 rate_limited, and statuses of 500 and above. It waits as long as theRetry-Afterheader asks, or backs off exponentially. Setmax_retriesto change this. - It never retries a spent limit:
quota_exceeded,key_limit_reachedorscan_limit_reached. Those last until their reset. - Every POST request carries an
Idempotency-Keyheader, the same on each retry, so a retried scan is never started or charged twice (Idempotency).
Async
AsyncCheckAnySite has the same methods as CheckAnySite; await each call. Open it with async with to close its connections when you’re done. The following example finds the framework of two sites at once:
import asyncio
from checkanysite import AsyncCheckAnySite
async def main():
async with AsyncCheckAnySite() as client:
sites = await asyncio.gather(
client.sites.retrieve("stripe.com", checks=["tech"]),
client.sites.retrieve("vercel.com", checks=["tech"]),
)
for site in sites:
frameworks = [
item.name
for item in site.tech.technologies
if item.category == "Framework"
]
print(f"{site.domain}: {', '.join(frameworks) or 'no framework found'}")
asyncio.run(main())