Reference

ICP rubrics

An ideal customer profile, written by you as a short list of weighted criteria and scored by arithmetic. A model helps you write the rubric; it never produces the number.

Why a rubric and not a score from a model

A model that says a lead is a 73 cannot tell you why, cannot be replayed, and cannot be argued with. A rubric that says “industry match 30 points, 50–500 employees 25, director or above 20” can be read by a sales rep, changed in a minute, and checked against what your signups went on to do. The same rubric over the same facts is the same number, every time.

Endpoints

MethodPathDoes
POST/v1/icp/compile{ text } — English in, candidate rubric out. Saves nothing.
GET/v1/icp{ active, versions } — the active rubric and every version's number, author and date.
POST/v1/icpSave a rubric version. Activates it unless you send activate: false. 201 with any warnings.
POST/v1/icp/activate{ version } — make an earlier or later version active.

The rubric

POST /v1/icp
{
  "criteria": [
    { "id": "c_industry",  "label": "Fintech or insurance", "weight": 30,
      "when": { "field": "enrichment.company.industry", "op": "in", "value": ["fintech", "insurance"] } },
    { "id": "c_size",      "label": "50–500 employees",     "weight": 25,
      "when": { "all": [
        { "field": "enrichment.company.employees", "op": "gte", "value": 50 },
        { "field": "enrichment.company.employees", "op": "lte", "value": 500 } ] } },
    { "id": "c_seniority", "label": "Director or above",    "weight": 25,
      "when": { "field": "enrichment.person.seniority", "op": "in", "value": ["director", "vp", "c_suite"] } },
    { "id": "c_geo",       "label": "US or Canada",         "weight": 20,
      "when": { "field": "enrichment.company.country", "op": "in", "value": ["US", "CA"] } },
    { "id": "c_tech",      "label": "Uses HubSpot (bonus)", "weight": 10, "bonus": true,
      "when": { "field": "enrichment.company.tech", "op": "in", "value": ["hubspot"] } }
  ],
  "thresholds": { "strong": 75, "fit": 55, "weak": 30 },
  "source_text": "We sell to fintech and insurance companies...",
  "activate": true
}
  • Criteria use the same predicates and the same field vocabulary as policy rules, except that a criterion may not read icp.* — a score cannot depend on itself.
  • A bonus criterion adds points but is left out of coverage, so a missing nice-to-have does not make a score look less trustworthy.
  • Saving warns, without failing, when the non-bonus weights do not sum to 100 or when the strong threshold is higher than any score the rubric can reach.

The score

Each criterion that matches earns its weight. A miss or an unknown earns nothing. The total is rounded and clamped to 0–100, so bonus points can lift a score to 100 but not past it.

"icp": {
  "score": 55,
  "fit": "fit",
  "coverage": 0.75,
  "version": 3,
  "criteria": [
    { "id": "c_industry",  "result": "match",   "points": 30 },
    { "id": "c_size",      "result": "match",   "points": 25 },
    { "id": "c_seniority", "result": "unknown", "points": 0 },
    { "id": "c_geo",       "result": "miss",    "points": 0 },
    { "id": "c_tech",      "result": "miss",    "points": 0 }
  ]
}

Example values. Seniority was unknown, so 25 of the 100 non-bonus points could not be evaluated: coverage 0.75.

FieldTypeMeaning
scorenumber0–100, rounded and clamped. The sum of the weights of the criteria that matched.
fitenumstrong, fit, weak or no_fit, from the thresholds. Coverage does not move the band.
coveragenumber0–1. The share of non-bonus weight whose criteria could be evaluated. Mandatory; read it every time.
versionnumberThe rubric version that produced the score.
criteriaarray{ id, result, points } per criterion. result is match, miss or unknown.

The coverage rule

A 55 at coverage 0.95 and a 55 at coverage 0.3 are different facts. The second one mostly measures what the enrichment vendors did not know. Coverage is on every score, and a policy can read it, so the rule you probably want looks like this:

{
  "id": "r_low_fit",
  "label": "Review low ICP fit, only when we actually know enough",
  "when": { "all": [
    { "field": "icp.score",    "op": "lt",  "value": 55 },
    { "field": "icp.coverage", "op": "gte", "value": 0.7 }
  ] },
  "then": { "action": "review" },
  "on_unknown": "skip"
}

ICP inside a decision

  • When your account has an active rubric, every /v1/decide response carries an icp score. With no rubric it is null.
  • Having a rubric does not, by itself, buy enrichment. A profile is bought when evaluation reaches a policy rule that reads an icp.* or enrichment field (or you pass enrich: "always"). Otherwise the score is computed over what is already known, and its coverage will say how little that is.
  • Whether a high score actually converts better is a question for your outcomes. Once events or Stripe are flowing, GET /v1/segments reports conversion by icp.fit; if strong does not out-convert no_fit, the rubric is wrong. See events.

Limits

WhatLimit
Criteria per rubric20
Criterion idc_ followed by lowercase letters, digits and _
WeightGreater than 0, at most 100
ThresholdsEach 0–100, strictly ordered strong > fit > weak
Predicate depth / nodes8 / 100
Values in an in / not_in set100
Label200 characters

Errors

StatusCodeWhen
400invalid_requestMissing or empty text, or a bad version number.
400policy_invalidThe rubric fails validation: an unknown field or value, a criterion reading icp.*, bad weights or thresholds. The first five errors are in the message.
404not_foundNo such version.
422compile_failedThe compiler could not produce a valid rubric, or is not configured on this deployment. error.questions carries what it needs answered.