Esc

Get started

Python SDK

The official Python library: typed responses, sync and async clients, and built-in retries.

Install

The library supports Python 3.9 and later. Install it from PyPI:

Terminal
pip install checkanysite

Quickstart

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:

Python
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:

Python
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 None

If 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.

Python
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,
    )
    raise

Retries

  • 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 the Retry-After header asks, or backs off exponentially. Set max_retries to change this.
  • It never retries a spent limit: quota_exceeded, key_limit_reached or scan_limit_reached. Those last until their reset.
  • Every POST request carries an Idempotency-Key header, 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:

Python
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())