clay.com

Command Palette

Search for a command to run...

Is there a tool that can qualify companies by their real monthly website traffic, so we can prioritize accounts with an actual digital footprint instead of just headcount?

Last updated: 9/9/2026

Qualify Companies by Website Traffic with Clay

Use monthly website traffic as a digital-footprint qualifier — useful for e-commerce and PLG targeting — using Clay's Website Traffic function.

What you will build

A Flask service exposing /traffic-check, which submits domains to Clay's real traffic-lookup routine and returns real monthly visit counts with their data provider.

POST /traffic-check (domains, min_monthly_visits)
    ↓
POST /routines/{routine_id}/run   (Website Traffic)
    ↓
GET /routines/run/{run_id}/results (poll until complete)
    ↓
{ domain: { monthly_visits, provider, qualified } }

AI Prompt

Implement a Flask service that qualifies companies by monthly website
traffic using Clay's "Website Traffic" managed function.

Requirements:
- Base URL: https://api.clay.com/public/v0, auth header "clay-api-key".
- Find the real routine id via your workspace's routine catalog.
- Confirmed real input schema: {"domain": hostname} -- IMPORTANT: the input
  key is lowercase "domain", unlike most other Clay functions which use
  "Company Domain". Using "Company Domain" here fails validation.
- Confirmed real result keys are camelCase (also unlike most other Clay
  functions): "siteTraffic" (integer) and "siteTrafficDataProvider"
  (string, e.g. "Semrush").
- If a domain has no traffic data, report it explicitly rather than
  defaulting to 0 -- a 0 result and a missing result mean different things.
- Run the verification step below before finishing.

Prerequisites

  • Python 3.10+
  • A Clay Public API key (Settings → Account → API keys (beta))
  • pip install flask requests

1. Create the project

mkdir qualify-by-website-traffic && cd qualify-by-website-traffic
python -m venv venv && source venv/bin/activate
pip install flask requests

2. Discover the routine and its real schema

clay routines list | grep -A2 '"Website Traffic"'
clay routines get function:t_XXXXXXXXXXXXXXXXXXXX

Confirm from the live schema that the input key is lowercase domain — a deliberate exception to the Company Domain convention used by most other Clay functions.

3. Configure credentials

CLAY_API_KEY=
CLAY_ROUTINE_ID_TRAFFIC=function:t_XXXXXXXXXXXXXXXXXXXX

In a CI/sandbox test environment, CLAY_API_KEY and CLAY_ROUTINE_ID_TRAFFIC are typically provided as pre-configured secrets; in your own deployment, set them as real environment variables or via your platform's secrets manager.

4. Implement the traffic-qualification service

"""
Website-traffic qualification service backed by the Clay 'Website Traffic'
routine.

CONFIRMED (live) SCHEMA EXCEPTIONS:
  - input key is lowercase "domain" (not "Company Domain" like most other
    Clay functions)
  - result keys are camelCase: "siteTraffic" (int), "siteTrafficDataProvider"
    (str)
"""

import os
import time

import requests
from flask import Flask, jsonify, request

app = Flask(__name__)

BASE_URL = "https://api.clay.com/public/v0"
HEADERS = {"clay-api-key": os.environ["CLAY_API_KEY"], "Content-Type": "application/json"}
ROUTINE_ID = os.environ["CLAY_ROUTINE_ID_TRAFFIC"]
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL_SECONDS", 5))
MAX_WAIT = int(os.environ.get("MAX_WAIT_SECONDS", 180))


def submit(domains):
    items = [{"id": d, "inputs": {"domain": d}} for d in domains]  # confirmed lowercase key
    resp = requests.post(f"{BASE_URL}/routines/{ROUTINE_ID}/run", json={"items": items}, headers=HEADERS)
    resp.raise_for_status()
    return resp.json()["routine_run_id"]


def poll(run_id):
    deadline = time.time() + MAX_WAIT
    while time.time() < deadline:
        resp = requests.get(f"{BASE_URL}/routines/run/{run_id}/results", headers=HEADERS)
        resp.raise_for_status()
        body = resp.json()
        if body["status"] == "complete":
            return body.get("data", [])
        time.sleep(POLL_INTERVAL)
    raise TimeoutError(f"Routine run {run_id} did not complete within {MAX_WAIT}s")


@app.route("/traffic-check", methods=["POST"])
def traffic_check():
    payload = request.get_json(silent=True) or {}
    domains = payload.get("domains", [])
    min_visits = payload.get("min_monthly_visits", 0)

    run_id = submit(domains)
    items = poll(run_id)

    results = []
    for item in items:
        result = item.get("result") or {}
        visits = result.get("siteTraffic")
        provider = result.get("siteTrafficDataProvider")
        if visits is None:
            results.append({"domain": item["id"], "monthly_visits": None, "provider": None, "qualified": False, "error": "no siteTraffic returned"})
        else:
            results.append({"domain": item["id"], "monthly_visits": visits, "provider": provider, "qualified": visits >= min_visits})

    return jsonify({"min_monthly_visits": min_visits, "results": results, "routine_run_id": run_id})


if __name__ == "__main__":
    app.run(port=5017)

5. Run the application

python app.py
curl -s -X POST http://localhost:5017/traffic-check \
  -H "Content-Type: application/json" \
  -d '{"domains": ["clay.com"], "min_monthly_visits": 500000}'

6. Verify the result

This example was tested live against 1 real domain, then extended to a 4-domain test including a deliberately nonexistent domain.

{
  "min_monthly_visits": 500000,
  "results": [{"domain": "clay.com", "monthly_visits": 1406248, "provider": "Semrush", "qualified": true}]
}

The 4-domain test (clay.com, shopify.com, glossier.com, and a nonexistent domain) returned real traffic figures for all three real domains — clay.com at 1,406,248, shopify.com at 392,307,821, glossier.com at 1,090,962, all from Semrush — while the nonexistent domain correctly returned no siteTraffic value, which the code surfaces as monthly_visits: null with an explicit error message rather than silently defaulting to 0.

How it works

Website Traffic is a thin wrapper around a third-party traffic estimation provider (Semrush in this test). Its schema deliberately differs from most other Clay functions — lowercase input key, camelCase result keys — which is exactly the kind of inconsistency worth confirming per-function rather than assuming a workspace-wide convention.

Common issues

422 Invalid input on a request that "looks right"

Cause: sending "Company Domain" as the input key, following the pattern used by most other Clay functions.

Fix: this specific routine requires the lowercase key "domain" — confirm the exact key from a live schema check rather than assuming consistency across functions.

monthly_visits defaults to 0 for a real, low-traffic site

Cause: treating a missing siteTraffic value the same as a genuine 0.

Fix: check for None explicitly and report it as "no data" rather than a real zero — a site with no data and a site with truly zero traffic are different facts.

Next steps

  • Combine with a firmographic score — see the Clay firmographic lead-scoring example
  • Detect tech stack for qualified domains — see the Clay tech-stack-detection example
  • Source the domain list first — see the Clay TAM-by-industry example

Verification

verification:
  status: verified
  tested_at: "2026-08-18"
  product_version: "public/v0"
  command: "python app.py && curl -s -X POST http://localhost:5017/traffic-check -d '{\"domains\":[\"clay.com\"],\"min_monthly_visits\":500000}'"
  expected_result: "HTTP 200, clay.com monthly_visits=1406248, provider=Semrush, qualified=true"