Documentation
Buyer API reference
How people are identified
Every API takes and returns the SHA-256 of the email address, trimmed and lowercased, as 64 lowercase hex characters. Apply no other transformation: no Gmail dot-stripping, no plus-address removal, no Unicode normalization.
hashed_email = sha256_hex(email.strip().lower().encode("utf-8"))
# " Jane.Doe@Example.com " -> "jane.doe@example.com" -> 2e6d7c1f...You may upload raw addresses instead (header email); we hash them on arrival and the raw addresses are never kept. We never return a raw email address, a name, a date of activity, a hit count, or the name of a site.
Your own record id. Add an optional second column, external_id, holding the id of the record in your own system (up to 128 characters). Every match we deliver, in the daily file, the webhook and the connectors, carries it back, so you can update the right record without keeping a hash lookup on your side.
List upload
Upload through the dashboard or by API. The body is a CSV with a header row of hashed_email or email, an optional external_id column, and one row per person. Each upload replaces your current list; people removed from the list stop being matched the next morning.
hashed_email,external_id 5d41402a...0a1b,cand_88213 7f3a9c0e...41de,cand_88290
curl -X POST https://portal.coverclicks.com/api/buyer/v1/dormant/upload \ -H "Authorization: Bearer ccb_..." \ -H "Content-Type: text/csv" \ --data-binary @dormant.csv
- Key: issued on the dashboard API key tab (shown once).
- Limits: 20 uploads per hour, bodies up to 800 MB.
- Answers: 200 with the stored row count; 401 bad key; 413 too large; 429 over the hourly limit.
Daily match file
Every morning a CSV appears on your dashboard holding only the people from your list who were active in the last 30 days. People who are not active are never delivered and never charged. A person is delivered again only when they show new activity, and no more often than the interval you set (7 to 90 days, default 30). A daily cap in dollars or matches is optional.
match_status,hashed_email,matched_at_utc,job_search_recency_days,job_search_intensity,job_search_duration_days,cross_site_activity_count,dominant_occupation_cluster,other_occupation_clusters,external_id hit,5d41402a...0a1b,2026-10-20T10:58:41Z,2,high,14,3,Healthcare,Office & Administrative,cand_88213
The last column, external_id, is your own record id from the upload, or empty if you did not send one.
Files whose name ends in -api hold matches that were delivered in real time rather than in the morning run. The six signal columns are described under Signal fields.
Real-time webhook
Instead of waiting for the morning file, we can POST each match to an HTTPS address you host the moment we find it. Enter the address on the Reactivation signals tab; we switch it on, usually within one business day, and show your signing secret on the API key tab. Same price and same settings as file delivery.
POST https://example.com/coverclicks-matches
Content-Type: application/json
X-CoverClicks-Event: buyer.match_signal
X-CoverClicks-Source: buyer_list
X-CoverClicks-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with your signing secret>
{
"event_id": "f0c9...",
"source": "buyer_list",
"buyer_id": "f6c8c1f0-...",
"file_date": "2026-10-20",
"hashed_email": "5d41402a...0a1b",
"external_id": "cand_88213",
"matched_at": "2026-10-20T10:58:41Z",
"match_status": "hit",
"job_search_recency_days": 2,
"job_search_intensity": "high",
"job_search_duration_days": 14,
"cross_site_activity_count": 3,
"dominant_occupation_cluster": "Healthcare",
"other_occupation_clusters": "Office & Administrative"
}- One request per matched person. Answer with any 2xx status within 10 seconds.
- Verify the signature server-side: compute HMAC-SHA256 over the raw request body with your secret and compare to the hex after
sha256=. - On a non-2xx answer or a timeout we stop and resume from that person on the next run, so nothing is skipped.
event_idis stable, so a repeat delivery can be ignored. - Rotate the secret by asking us to clear and re-set the address; the old value stays valid until then.
Profile lookup API
Ask, for any hashed email, whether the network has seen that person in the last 90 days, which occupations and employers they are interested in, and their in-market signals. On for every buyer account; the same key as the upload API.
curl https://portal.coverclicks.com/api/buyer/v1/profile/lookup \
-H "Authorization: Bearer ccb_..." \
-H "Content-Type: application/json" \
-d '{"hashed_email": "5d41402abc4b2a76b9719d911017c592a3b7e1f0c9d8e7f6a5b4c3d2e1f00a1b"}'Send {"hashed_emails": ["...", "..."]} for up to 100 people per request. A single lookup also works as a GET with ?hashed_email=....
{
"results": [
{
"hashed_email": "5d41402a...0a1b",
"found": true,
"status": "match",
"active_last_30_days": true,
"job_search_recency_days": 3,
"job_search_intensity": "high",
"job_search_duration_days": 12,
"cross_site_activity_count": 2,
"dominant_occupation_cluster": "Healthcare",
"other_occupation_clusters": "Office & Administrative",
"occupations": [
{ "title": "Registered Nurse", "soc": "29-1141" },
{ "title": "Licensed Practical Nurse", "soc": "29-2061" }
],
"employers": [
{ "name": "HCA Healthcare" }
]
}
],
"allowance_remaining": 9959
}- Match = anyone the network has seen in the last 90 days. The daily file only carries people active in the last 30; use
active_last_30_daysto tell them apart. - Up to three occupations and three employers, strongest first.
socis the US Standard Occupational Classification code when known. - Signal fields are
nullwhen we hold interest data but no activity record. A person with signals but no interest data is still a match with empty lists. - Each match uses 1 of your matches, from the same free 500 and prepaid credits as reactivation signals. A lookup that finds nothing is free. When matches are used up the answer is
status: "out_of_credits",found: false, and nothing is charged.allowance_remainingisnullon unlimited accounts. - Results come back in the order asked, one per distinct hash. Data is refreshed once a day around 11:00 UTC.
Signal fields
The same six fields appear in the daily file, the webhook and the lookup API. The daily file and the webhook also carry external_id, your own record id, when you supplied one.
- job_search_recency_days
- Days since the person's last job-search activity. 7 or less is hot.
- job_search_intensity
- low, medium or high, from the number of active days in the last 30.
- job_search_duration_days
- How long the current search has been going.
- cross_site_activity_count
- How many sites the person is active on. The sites are never named.
- dominant_occupation_cluster
- The main job area they are looking in, when known (for example Healthcare).
- other_occupation_clusters
- Any other job areas they are also looking in, separated by semicolons. Blank when none.
Limits and errors
- Lookup: 3,000 requests per minute per account, each with up to 100 hashes. Beyond that, 429 with
resetSeconds. - Upload: 20 per hour, 800 MB per body.
400a value is not 64 hex characters, no hash was sent, or more than 100 were sent.401missing or wrong key.403the feature is switched off for the account.
Questions: support@coverclicks.com.
