CoverClicks

Documentation

For AI assistants and agents

If you are an AI assistant, a coding agent or an automation tool setting CoverClicks up for a customer, this page is for you. It points at the machine-readable description of every endpoint, gives working scripts you can run as-is, and lists the rules that keep a customer out of trouble. Humans are welcome too.

Machine-readable files

  • /openapi.json: OpenAPI 3.1 description of the buyer upload, buyer lookup, publisher lookup and publisher event endpoints, plus the real-time webhook as a callback. Load it into any API client or agent toolkit.
  • /llms.txt: a one-page plain-text summary of what CoverClicks does, every documented URL and the key facts (hash rule, limits, prices).
  • The human pages (buyers, publishers, integrations) are plain server-rendered HTML with no login, so they read cleanly as text.

Setting a buyer up in four calls

  1. Account and key. The customer signs up at portal.coverclicks.com/buyer-apply and issues a bearer key (prefix ccb_) on the API key tab. There is no API for creating accounts or keys; a human does this once.
  2. Hash the list. sha256_hex(email.strip().lower()), nothing else. Keep your own record id next to each hash so matches come back with it.
  3. Upload. POST /api/buyer/v1/dormant/upload with a CSV body (hashed_email,external_id). The upload replaces the whole list, so send the full current list each time rather than a delta.
  4. Receive matches. Read the daily file from the dashboard, set a webhook URL (signed, one POST per person), or call POST /api/buyer/v1/profile/lookup for up to 100 people at a time. Or skip steps 2 to 4: the customer connects their ATS or CRM on the Integrations tab and we pull the list and write matches back ourselves.

Upload script (Python, standard library only)

#!/usr/bin/env python3
"""Hash a CSV of emails and upload it as the buyer's dormant list.
Usage: COVERCLICKS_KEY=ccb_... python3 upload.py contacts.csv
contacts.csv needs an 'email' column; an 'id' column is passed through as external_id."""
import csv, hashlib, io, os, sys, urllib.request

key = os.environ["COVERCLICKS_KEY"]
out = io.StringIO()
w = csv.writer(out)
w.writerow(["hashed_email", "external_id"])
with open(sys.argv[1], newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        email = (row.get("email") or "").strip().lower()
        if not email:
            continue
        w.writerow([hashlib.sha256(email.encode("utf-8")).hexdigest(), (row.get("id") or "")[:128]])

req = urllib.request.Request(
    "https://portal.coverclicks.com/api/buyer/v1/dormant/upload",
    data=out.getvalue().encode("utf-8"),
    method="POST",
    headers={"Authorization": f"Bearer {key}", "Content-Type": "text/csv"},
)
with urllib.request.urlopen(req, timeout=600) as resp:
    print(resp.status, resp.read().decode())

Answers: 200 with the stored row count, 401 bad key, 413 over 800 MB, 429 more than 20 uploads in an hour. Retry only 429 and 5xx.

Webhook receiver (Node, no packages)

// COVERCLICKS_SECRET=<signing secret from the API key tab> node receiver.js
// Listens on port 8080; put it behind HTTPS and enter the URL on the Reactivation signals tab.
const http = require("http");
const crypto = require("crypto");
const secret = process.env.COVERCLICKS_SECRET;
const seen = new Set();

http.createServer((req, res) => {
  if (req.method !== "POST") { res.writeHead(405); return res.end(); }
  let raw = "";
  req.on("data", (c) => (raw += c));
  req.on("end", () => {
    const expected = "sha256=" + crypto.createHmac("sha256", secret).update(raw, "utf8").digest("hex");
    const given = req.headers["x-coverclicks-signature"] || "";
    if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
      res.writeHead(401); return res.end("bad signature");
    }
    const match = JSON.parse(raw);
    if (!seen.has(match.event_id)) {        // repeats carry the same event_id
      seen.add(match.event_id);
      // match.external_id = your record id; match.job_search_intensity etc. = the six signals
      console.log("active again:", match.external_id || match.hashed_email, match.job_search_intensity);
      // TODO: update the record in your CRM/ATS here
    }
    res.writeHead(200); res.end("ok");       // answer 2xx within 10 seconds
  });
}).listen(8080);
Zapier, Make and n8n can receive the same webhook with no code; see Integrations.

Example prompts that work

  • "Read https://www3.coverclicks.com/openapi.json and write a script that uploads our contacts export to CoverClicks with our contact id as external_id."
  • "Build a webhook endpoint for CoverClicks match signals that verifies the HMAC signature and adds a 'Job hunting again' tag to the matching HubSpot contact."
  • "Using the CoverClicks lookup API, check these 100 hashed emails and return the ones with job_search_intensity high and recency under 7 days."
  • "Explain the three ways a CoverClicks buyer can receive matches and recommend one for a staffing firm on Bullhorn." (Answer: connect Bullhorn on the Integrations tab; no code.)

Rules an agent must keep

  • Never send a raw email address to the lookup or upload APIs unless the CSV header is literally email. Hash first.
  • Treat the key (ccb_...) and the webhook signing secret as secrets: environment variables, never in code, logs or chat.
  • An upload replaces the list. Never upload a partial list as a "sync"; send the full current list.
  • Every match found by the lookup API costs the customer one match. Do not loop a lookup over the same people; use the daily file or webhook for ongoing monitoring.
  • Matches never include a site name, a raw email, a date of activity or a hit count. Do not promise those to the customer.
  • Anyone can opt out of the whole network at portal.coverclicks.com/opt-out; opted-out people simply stop appearing.

Something missing from this page? support@coverclicks.com.