Score
curl --request POST \
--url https://api.opennous.cloud/v2/score \
--header 'Content-Type: application/json' \
--data '
{
"identifier": "<string>",
"attributes": {},
"leads": [
{}
],
"identifiers": [
{}
],
"intent": "<string>"
}
'import requests
url = "https://api.opennous.cloud/v2/score"
payload = {
"identifier": "<string>",
"attributes": {},
"leads": [{}],
"identifiers": [{}],
"intent": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
identifier: '<string>',
attributes: {},
leads: [{}],
identifiers: [{}],
intent: '<string>'
})
};
fetch('https://api.opennous.cloud/v2/score', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.opennous.cloud/v2/score",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'identifier' => '<string>',
'attributes' => [
],
'leads' => [
[
]
],
'identifiers' => [
[
]
],
'intent' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.opennous.cloud/v2/score"
payload := strings.NewReader("{\n \"identifier\": \"<string>\",\n \"attributes\": {},\n \"leads\": [\n {}\n ],\n \"identifiers\": [\n {}\n ],\n \"intent\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.opennous.cloud/v2/score")
.header("Content-Type", "application/json")
.body("{\n \"identifier\": \"<string>\",\n \"attributes\": {},\n \"leads\": [\n {}\n ],\n \"identifiers\": [\n {}\n ],\n \"intent\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.opennous.cloud/v2/score")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"identifier\": \"<string>\",\n \"attributes\": {},\n \"leads\": [\n {}\n ],\n \"identifiers\": [\n {}\n ],\n \"intent\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"layers": {},
"missing": [
{}
],
"priority": 123,
"play": "<string>"
}Search
Score
Score a lead against your live ICP model — layered Fit · Pain · Intent · Ability plus an enrichment worklist. Works on accounts in Nous and on cold leads that aren’t.
POST
/
v2
/
score
Score
curl --request POST \
--url https://api.opennous.cloud/v2/score \
--header 'Content-Type: application/json' \
--data '
{
"identifier": "<string>",
"attributes": {},
"leads": [
{}
],
"identifiers": [
{}
],
"intent": "<string>"
}
'import requests
url = "https://api.opennous.cloud/v2/score"
payload = {
"identifier": "<string>",
"attributes": {},
"leads": [{}],
"identifiers": [{}],
"intent": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
identifier: '<string>',
attributes: {},
leads: [{}],
identifiers: [{}],
intent: '<string>'
})
};
fetch('https://api.opennous.cloud/v2/score', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.opennous.cloud/v2/score",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'identifier' => '<string>',
'attributes' => [
],
'leads' => [
[
]
],
'identifiers' => [
[
]
],
'intent' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.opennous.cloud/v2/score"
payload := strings.NewReader("{\n \"identifier\": \"<string>\",\n \"attributes\": {},\n \"leads\": [\n {}\n ],\n \"identifiers\": [\n {}\n ],\n \"intent\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.opennous.cloud/v2/score")
.header("Content-Type", "application/json")
.body("{\n \"identifier\": \"<string>\",\n \"attributes\": {},\n \"leads\": [\n {}\n ],\n \"identifiers\": [\n {}\n ],\n \"intent\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.opennous.cloud/v2/score")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"identifier\": \"<string>\",\n \"attributes\": {},\n \"leads\": [\n {}\n ],\n \"identifiers\": [\n {}\n ],\n \"intent\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"layers": {},
"missing": [
{}
],
"priority": 123,
"play": "<string>"
}Return your workspace’s own ICP judgment on a lead: a 0–100 ICP fit + tier, a decaying intent score, and the layered breakdown — Fit · Pain · Intent · Ability. Any layer you have no evidence for comes back unknown, never 0, alongside a
The
The score is a progressively-resolved hypothesis: a LinkedIn headline resolves Fit with real confidence and leaves the rest unknown (never 0), with
missing worklist that tells your agent exactly what to enrich next.
There are three ways to call it:
- A lead already in Nous — pass an
identifier. It’s scored off the resolved claims on that account, the score is staked into the graph, and it keeps evolving on its own afterwards. - One cold lead not in Nous — pass
attributes. It’s scored inline from what you have (title, company, keywords); nothing is written to the graph. - A whole external list — pass
leads(up to 1000). All scored inline in one call, the model loaded once, nothing written. Built to triage a big list before you decide which accounts to bring in.
score needs a scoring model. Build one first with build_icp_model, then this endpoint returns live scores.Request
curl -X POST https://api.opennous.cloud/v2/score \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ "identifier": "sarah@acme.com" }'
curl -X POST https://api.opennous.cloud/v2/score \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"attributes": {
"job_title": "Head of Sales",
"company_type": "b2b_saas",
"employee_count": 40,
"headline": "scaling outbound, our stack is fragmented"
}
}'
curl -X POST https://api.opennous.cloud/v2/score \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"leads": [
{ "ref": "row-1", "job_title": "Head of Sales", "company_type": "b2b_saas", "headline": "scaling outbound, fragmented stack" },
{ "ref": "row-2", "job_title": "Designer", "company_type": "agency" }
]
}'
Body
string
One lead already in the graph — an email, domain, LinkedIn URL, or entity UUID. Scored off its resolved claims and staked.
object
A cold lead not in Nous, scored inline from what you have — nothing is written. Fields (with friendly aliases):
job_title (title), seniority, company_type (type), industry, employee_count (company_size, size), location (country), and keywords or headline (a raw LinkedIn headline is matched for keyword-based signals). You may also pass any signal.* you’ve already computed. If an identifier is given but isn’t in the graph, attributes is used as the fallback.array
An external list scored inline in one call (up to 1000). Each item is an
attributes object (same fields as above), optionally with a ref (or id) that is echoed back so you can match results to your rows. Nothing is written to the graph.array
A batch of graph leads (emails / domains / LinkedIn URLs / UUIDs), max 100 per call. Each is resolved and scored like
identifier; the response is a results array.string
Optional hint about why you’re scoring. Recorded; does not change the score.
Response
A scored lead — the layered read
{
"resolved": true,
"scored": true,
"entity_id": "…",
"icp": { "score": 88, "fit": true, "tier": "tier_1", "reason": "Scorecard: 3 signals fired — …" },
"intent": { "score": 0, "band": "Dormant" },
"layered": {
"priority": 88,
"band": "high",
"layers": { "fit": 88 },
"missing": ["pain", "intent", "ability"],
"play": "research — strong fit; enrich pain and intent before outreach"
}
}
layered block is the important part:
object
The resolved layers,
{ fit, pain, intent, ability } — each 0–100. A layer with no observed evidence is omitted, not set to 0. For a cold lead you’ll usually see fit (and pain, if the headline carried the signal) and nothing else yet.array
The layers still unknown — your enrichment worklist.
["pain","intent","ability"] means: resolve these before treating the account as fully judged.number
The combined 0–100 score — the geometric mean of the known layers only. Unknown layers are excluded, never zeroed, so a partly-observed lead is neither dragged down nor inflated.
null when nothing is resolved.string
The recommended action from the shape of the resolved hypothesis:
research, nurture, work now, enrich, or suppress.The flat
icp.score is a single number for back-compat. On a cold lead it reflects Fit — it never fabricates a Pain number and an unobserved signal never lowers it. Read layered.missing to know how much is actually known, not just icp.score.A cold lead scored inline
Same shape, flagged so you know it isn’t in the graph:{
"resolved": true, "scored": true,
"source": "inline", "persisted": false,
"icp": { "score": 88, "fit": true, "tier": "tier_1", "reason": "…" },
"intent": { "score": 0, "band": "Dormant" },
"layered": { "priority": 88, "layers": { "fit": 88, "pain": 71 }, "missing": ["intent","ability"], "play": "…" }
}
An external list
{
"persisted": false,
"results": [
{ "ref": "row-1", "scored": true, "persisted": false, "source": "inline",
"icp": { "score": 88, "tier": "tier_1", "fit": true },
"layered": { "priority": 88, "layers": { "fit": 88, "pain": 71 }, "missing": ["intent","ability"], "play": "…" } },
{ "ref": "row-2", "scored": false, "source": "inline", "reason": "no_attributes" }
]
}
Partial — in the graph but too thin to stake
A lead that carries some claims (keywords, company, location) but none of the strict scoreable fields is not staked (that protects calibration), yet still resolves what it can:{ "resolved": true, "scored": false, "reason": "partial", "entity_id": "…",
"layered": { "layers": { "fit": 62 }, "missing": ["pain","intent","ability"], "play": "enrich — resolve pain, intent, ability" } }
Not resolvable
{ "resolved": false, "reason": "unknown_identifier" } // not in the graph and no attributes passed
{ "resolved": false, "reason": "ambiguous", "candidates": [ … ] } // a name matched several people
The layered model
The score isn’t one number — it’s four independent layers, each resolved on its own evidence:| Layer | Question | Resolves from |
|---|---|---|
| Fit | Is this fundamentally our customer? | title, seniority, company type, size, geography |
| Pain | Do they actually have the problem? | fragmented stack, manual CRM, tool sprawl (posts, site, keywords) |
| Intent | Is something happening now? | hiring, funding, stack change, replies |
| Ability | Can they buy and deploy? | maturity, infrastructure |
missing telling you what to enrich. As enrichment arrives, the layers light up and priority becomes the blend of all four. Tune the model on the ICP page, or with build_icp_model.
When to call it
- Triage a cold list before spending on enrichment —
leads, nothing written. - Score one prospect from a LinkedIn scrape —
attributes. - Get the live number on an account already in Nous —
identifier. - Read
layered.playto decide the next action; readmissingto know what to enrich.