Add explainable lead scoring to your Clay table
Enrichment columns are good at telling you what a lead is. They are quieter about whether the lead is worth your time, and quieter still about why. You end up with a firmographic record, maybe a score from somewhere, and a column of numbers you cannot interrogate. That gap matters because attention is the scarce resource: Gartner found B2B buyers spend just 17% of their purchase time meeting with all potential suppliers combined (2020), so the rows you choose to work are most of the game.
kenbun’s scoring endpoint returns both halves. Call it from a Clay HTTP API enrichment column, and you get explainable lead scoring on every row: each contact or account comes back with a fit score and the specific rules that produced it. You can sort the table by fit and still see the reason every row landed where it did.
Most enrichment returns a number. We return the score and the rule-level reasons it fired, so the cell shows why, not just what. That is the whole point of an explainable scoring framework: a score nobody can explain is a score nobody trusts, which is usually why scoring quietly stops being used.
Prerequisites
- A kenbun Personal Access Token with the
write:scoringscope. Sending aprofilealso needswrite:leads; sending anaccountalso needswrite:accounts. Create the token in your kenbun workspace settings. - A Clay workspace on a tier that includes HTTP API enrichment columns.
Wire it up
-
Add the column. In your table, add a new enrichment column and choose the HTTP API action.
-
Set the request. Method
POST, endpointhttps://api.kenbun.io/scoring/score. -
Add the auth header.
Authorization: Bearer YOUR_TOKEN Content-Type: application/json -
Map your Clay columns into the body. Send an
external_idto identify the row (an email or CRM id works), then one or both scorable dimensions:profilefor contact fit,accountfor company fit. Use Clay’s/field picker to drop in each value.{ "external_id": "/Email", "profile": { "role": "/Job Title", "company_size": "/Employee Count", "industry": "/Industry" }, "account": { "domain": "/Company Domain", "employees": "/Employee Count", "industry": "/Industry" } } -
Run the column. Run it on a single row first to confirm the response, then run the rest of the table.
-
Map the response back into Clay. Each dimension returns its own object. Pull
profile.scoreandaccount.scoreinto columns, and the matchingdriversarray into another.
Example
Request:
POST https://api.kenbun.io/scoring/score
{
"external_id": "[email protected]",
"profile": {
"role": "VP Marketing Operations",
"company_size": "480",
"industry": "B2B SaaS"
},
"account": {
"domain": "northwind.example",
"employees": 480,
"industry": "B2B SaaS"
}
}
Response:
{
"external_id": "[email protected]",
"scored_at": "2026-06-12T15:04:01Z",
"disqualified": false,
"dimensions_scored": ["profile", "account"],
"profile": {
"score": 40,
"level": "strong-fit",
"drivers": [
{ "property": "role", "condition": "contains", "value": "Marketing Operations", "weight": 40, "matched": true }
]
},
"account": {
"score": 60,
"level": "tier-1",
"drivers": [
{ "property": "employees", "condition": "gte", "value": "250", "weight": 35, "matched": true },
{ "property": "industry", "condition": "equals", "value": "B2B SaaS", "weight": 25, "matched": true }
]
}
}
Reading the reasons
The drivers array is what makes the score explainable. Each entry is one rule kenbun evaluated against the attributes you sent: the property it looked at, the condition it applied, the value it compared against, the weight it carries, and whether it matched. The drivers with matched: true are the rules that fired and moved the score. Drop the array into a Clay column and the reasoning rides along with the number, so a teammate reading the row sees the same evidence you do.
Responses worth handling
- A genuine score of
0and a disqualified row both return200. Whendisqualifiedistrue, readdisqualified_reasonfor the rule that knocked the row out (for example, a competitor domain). The row is still scored, so you can route it out deliberately. - A
403 insufficient_scopemeans the token is missing a scope:write:scoringfor any call, pluswrite:leadswhen you sendprofileandwrite:accountswhen you sendaccount. - A
400means no scorable dimension was supplied, orexternal_idis missing.
Use it in Clay
Sort or filter the table by profile.score or account.score, and route high-fit rows to your next step. Speed compounds the benefit: in the Harvard Business Review study The Short Life of Online Sales Leads (Oldroyd, McElheran, and Elkington, 2011), firms that followed up within an hour were close to seven times likelier to qualify a lead than those that waited longer, so getting the right rows to a rep sooner is worth the effort. Because the drivers travel with each row, whoever picks it up sees why it scored that way, instead of taking a bare number on faith.
If you are still weighing whether to build scoring inside Clay at all, versus running it as a managed layer in HubSpot, we lay out the honest tradeoffs in our Clay alternatives for lead scoring breakdown.