Skip to main content

Sales Disposition API

Capture how a sales rep actually felt about a lead after working it. A disposition is a one-tap outcome ("Qualified", "Not ICP", "Nurture", and so on) that your team records on a lead. Dispositions close the loop between scoring and reality: they tell kenbun which leads your reps agreed were worth pursuing, so your workspace's scoring can be calibrated against real sales judgment.

Reps can set a disposition from three places: this API, the Log Outcome menu on a lead's Slack alert, and the lead detail page under Analyze > Leads.

Why Dispositions Matter

Scoring tells your team who looks promising. Dispositions tell you who was promising. When a rep marks a lead Qualified (SQL) or Closed Won, kenbun records the lead's score and the scoring rules in effect at that moment. Over time, this builds a labeled history of outcomes that the calibration engine uses to suggest better rule weights.

Dispositions never change your scoring weights on their own. They are feedback, not automation. Suggested weight changes always require a person to review and apply them under Configure > Scoring.

Understanding the Six Outcomes

Each disposition has a plain-language label your team sees throughout the app:

ValueLabel shown to your teamMeaning
accepted_sqlQualified (SQL)The rep accepted the lead as a Sales Qualified Lead
rejected_not_icpRejected (Not ICP)The lead is not a fit for your ideal customer profile
rejected_no_responseRejected (No Response)The rep could not reach the lead
nurtureNurtureNot ready now; send back to marketing nurture
closed_wonClosed WonThe deal closed successfully
closed_lostClosed LostThe deal was lost

A lead can also have no disposition (the default). Clearing a disposition returns the lead to that state.

The Slack alert's Log Outcome menu offers all six outcomes in one tap — Qualified (SQL), Rejected (Not ICP), Rejected (No Response), Nurture, Closed Won, and Closed Lost — the same set available from the lead detail page and this API.

The Optional Comment

Every disposition can carry a free-text comment (up to 2000 characters) — for example, "Booked a demo for Thursday" or "Competitor scraping our pricing." Comments are shown to your marketing operations team in the Sales feedback card and in the Recent Rep Notes list. They help reps explain their reasoning.

Comments are never used in scoring math. They are read by people, not by the calibration engine.

What Gets Recorded Automatically

When a disposition is set, kenbun stamps the lead with two values, captured automatically at the moment the disposition is set:

  • Score — the lead's engagement score at that moment.
  • Scoring rules version — which version of your scoring rules produced that score.

Together these form a (score, rules version, outcome) record that the calibration engine can learn from. The stamp is taken at the moment the rep records the disposition, not at the time the alert was sent — so it reflects the lead's state when the decision was actually made.

A few notes on the stamp:

  • If a lead has no active primary scoring ruleset, the score and rules version are left empty. The disposition is still recorded.
  • The stamp is captured once, when an outcome is first chosen. Changing the comment later does not re-stamp it; choosing a different outcome does.

Sample Leads Cannot Be Dispositioned

If your workspace contains demo "sample" leads (created by the Sample Data generator), those leads cannot be dispositioned. Attempting to do so returns a clear error, and the lead is left untouched. This keeps synthetic demo data out of your real sales-feedback history.

PUT /leads/{leadID}/disposition

Set or update the disposition on a single lead.

When to Use This

  • Log a sales outcome: A rep marks a lead as Qualified or Not ICP after working it.
  • Add or update a comment: Attach the rep's reasoning to the outcome.
  • Correct a mistake: Change a lead from one outcome to another.

Request

ParameterTypeRequiredDescription
leadIDstring (UUID)YesLead identifier (path parameter)

Request Body

FieldTypeRequiredDescription
dispositionstringYesOne of the six values in the table above
commentstringNoOptional rep note, up to 2000 characters

How the comment field behaves:

  • Omitted — the comment is preserved if you re-save the same outcome; it is cleared if you change to a different outcome.
  • Empty or whitespace — the comment is cleared.
  • Provided — the comment is set (leading and trailing spaces are trimmed).
{
"disposition": "accepted_sql",
"comment": "Booked a demo"
}

Response

200 OK

{
"status": "ok",
"disposition": {
"disposition": "accepted_sql",
"comment": "Booked a demo",
"set_at": "2026-06-10T14:03:12Z",
"set_by_label": "alice@acme.com",
"score": 74,
"ruleset_version_id": "8b2e0c1a-3d4f-4a5b-9c6d-7e8f9a0b1c2d"
},
"previous": {
"disposition": "rejected_not_icp",
"set_at": "2026-06-09T11:20:00Z",
"set_by_label": "bob@acme.com",
"comment": "competitor scraping pricing",
"score": 61,
"ruleset_version_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
},
"restamped": true
}
FieldTypeDescription
statusstringAlways "ok" on success
disposition.dispositionstringThe outcome now recorded on the lead
disposition.commentstring or nullThe current comment, or null if none
disposition.set_attimestampWhen this outcome was first chosen
disposition.set_by_labelstring or nullWho set the outcome (for example, an email address)
disposition.scoreinteger or nullLead score stamped at set time, or null
disposition.ruleset_version_idstring (UUID) or nullScoring rules version stamped at set time, or null
previousobject or nullThe outcome this one replaced, or null if the lead had none. Same fields as disposition, plus comment.
restampedbooleantrue when the outcome value actually changed (and was re-stamped); false for a same-outcome re-save

Example

curl -X PUT "https://api.kenbun.io/leads/3d4f4a5b-9c6d-7e8f-9a0b-1c2d3e4f5a6b/disposition" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"disposition": "accepted_sql", "comment": "Booked a demo"}'

Common Errors

StatusErrorMeaningSolution
400invalid_dispositionThe disposition value is not one of the six allowed valuesSend one of the values listed above; the response includes the full allowed list
400comment_too_longThe comment exceeds 2000 charactersShorten the comment to 2000 characters or fewer
404lead_not_found_or_mergedThe lead does not exist in your active Organizational Unit, or it was merged into another leadVerify the lead ID; merged leads are dispositioned on the surviving lead
409sample_leadThe lead is a demo sample leadSample leads cannot be dispositioned

An invalid disposition returns the allowed list so you can correct the request:

{
"error": "invalid_disposition",
"allowed": ["accepted_sql", "rejected_not_icp", "rejected_no_response", "nurture", "closed_won", "closed_lost"]
}

DELETE /leads/{leadID}/disposition

Clear the disposition on a lead, returning it to the "no disposition" state. The response includes the outcome that was removed, so your app can confirm what was cleared.

Request

ParameterTypeRequiredDescription
leadIDstring (UUID)YesLead identifier (path parameter)

Response

200 OK — when a disposition was cleared:

{
"status": "ok",
"previous": {
"disposition": "accepted_sql",
"set_at": "2026-06-10T14:03:12Z",
"set_by_label": "bob@acme.com",
"comment": "Booked a demo",
"score": 74,
"ruleset_version_id": "8b2e0c1a-3d4f-4a5b-9c6d-7e8f9a0b1c2d"
}
}

200 OK — when the lead had no disposition (nothing to clear):

{
"status": "ok",
"previous": null
}
FieldTypeDescription
statusstringAlways "ok" on success
previousobject or nullThe outcome that was cleared, or null if there was nothing to clear

Example

curl -X DELETE "https://api.kenbun.io/leads/3d4f4a5b-9c6d-7e8f-9a0b-1c2d3e4f5a6b/disposition" \
-H "Authorization: Bearer <token>"

Common Errors

StatusErrorMeaningSolution
404lead_not_found_or_mergedThe lead does not exist in your active Organizational Unit, or it was mergedVerify the lead ID
409sample_leadThe lead is a demo sample leadSample leads cannot be dispositioned

GET /leads/disposition-stats

Summarize disposition activity across your active Organizational Unit. This powers the Sales feedback card your marketing operations team sees, including how many outcomes were logged, how concentrated they are among reps, the average score per outcome under your current rules, and the most recent rep notes.

Request

ParameterTypeRequiredDescription
since_daysintegerNoLook-back window in days. Default 90. Clamped to the range 1–365.

Response

200 OK

{
"since_days": 90,
"total": 18,
"distinct_actors": 5,
"top_actor_share": 0.44,
"current_ruleset_version_id": "8b2e0c1a-3d4f-4a5b-9c6d-7e8f9a0b1c2d",
"by_disposition": [
{
"disposition": "accepted_sql",
"count": 9,
"count_current_version": 7,
"avg_score_current_version": 74.2
}
],
"recent_comments": [
{
"lead_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"lead_label": "John Doe",
"disposition": "rejected_not_icp",
"set_by_label": "rep@acme.com",
"set_at": "2026-06-09T11:20:00Z",
"comment": "competitor scraping pricing",
"comment_truncated": false
}
]
}
FieldTypeDescription
since_daysintegerThe look-back window applied to this response
totalintegerTotal dispositions set within the window
distinct_actorsintegerHow many distinct people set dispositions
top_actor_sharenumber or nullThe fraction of dispositions set by the single most active person (for example, 0.44 means 44%)
current_ruleset_version_idstring (UUID) or nullThe scoring rules version currently in effect. The per-outcome average scores are restricted to dispositions stamped with this version. null when there is no active primary ruleset.
by_dispositionarrayOne entry per outcome present in the window (always an array)
by_disposition[].dispositionstringThe outcome value
by_disposition[].countintegerTotal dispositions of this outcome in the window
by_disposition[].count_current_versionintegerHow many of those were stamped with the current rules version
by_disposition[].avg_score_current_versionnumber or nullAverage stamped score among current-version dispositions, or null
recent_commentsarrayUp to 10 most recent rep notes, newest first (always an array)
recent_comments[].lead_idstring (UUID)The lead the note belongs to
recent_comments[].lead_labelstringA friendly display name for the lead
recent_comments[].dispositionstringThe outcome recorded with the note
recent_comments[].set_by_labelstring or nullWho set the disposition
recent_comments[].set_attimestampWhen the disposition was set
recent_comments[].commentstringThe note text (long notes are shortened to a preview)
recent_comments[].comment_truncatedbooleantrue when the note was shortened; open the lead to read the full note

Example

curl -X GET "https://api.kenbun.io/leads/disposition-stats?since_days=90" \
-H "Authorization: Bearer <token>"

Common Errors

StatusMeaningSolution
400since_days is not a valid numberSend a whole number (it is clamped to 1–365)
401UnauthorizedCheck authentication credentials

GET /leads/disposition-quality

Compare the score kenbun gave each lead against the outcome sales actually logged. This powers the Score vs. Outcome view on the calibration review page, and is useful any time you want to find where scoring and sales disagreed.

The report has four parts:

  • False positives — leads that scored high yet were rejected.
  • False negatives — leads that scored low yet were qualified or won.
  • Distribution — the spread of stamped scores per outcome.
  • Time to disposition — how long leads waited between owner assignment and an outcome.

Every figure uses the score stamped at the moment each outcome was logged. Merged-away leads and sample (demo) leads are excluded.

Request

ParameterTypeRequiredDescription
since_daysintegerNoLook-back window in days. Default 90. Clamped to 1–365.
high_scoreintegerNoThe score at or above which a rejected lead counts as a false positive. When omitted, derived from your highest engagement level threshold, falling back to 75.
low_scoreintegerNoThe score below which a qualified or won lead counts as a false negative. Default 40.
limitintegerNoMaximum leads in each of the false-positive and false-negative lists. Default 25. Clamped to 100.

Response

200 OK

{
"since_days": 90,
"high_score": 75,
"low_score": 40,
"current_ruleset_version_id": "8b2e0c1a-3d4f-4a5b-9c6d-7e8f9a0b1c2d",
"false_positives": [
{
"lead_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"lead_label": "Jane Doe",
"disposition": "rejected_not_icp",
"disposition_score": 82,
"set_by_label": "rep@acme.com",
"comment_excerpt": "Student researching, not a buyer",
"disposition_set_at": "2026-06-09T11:20:00Z"
}
],
"false_negatives": [
{
"lead_id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
"lead_label": "john@bigco.com",
"disposition": "closed_won",
"disposition_score": 31,
"set_by_label": "rep@acme.com",
"comment_excerpt": "Inbound referral, fast close",
"disposition_set_at": "2026-06-08T09:00:00Z"
}
],
"distribution": [
{
"disposition": "accepted_sql",
"count": 12,
"min": 41,
"q1": 58,
"median": 67,
"q3": 74,
"max": 88
}
],
"time_to_disposition": [
{
"disposition": "accepted_sql",
"count": 12,
"avg_days": 4.2,
"median_days": 3.0
}
]
}
FieldTypeDescription
since_daysintegerThe look-back window applied to this response
high_scoreintegerThe false-positive score floor applied
low_scoreintegerThe false-negative score ceiling applied
current_ruleset_version_idstring (UUID) or nullThe scoring rules version the distribution is restricted to. null when there is no active primary ruleset.
false_positivesarrayHigh-scored leads that were rejected (always an array)
false_negativesarrayLow-scored leads that were qualified or won (always an array)
false_positives[].disposition_scoreinteger or nullScore stamped when the outcome was logged
false_positives[].set_by_labelstring or nullWho logged the outcome
false_positives[].comment_excerptstring or nullFirst 150 characters of the rep's note, if any
distributionarrayPer-outcome score spread: min, q1, median, q3, max, and count (always an array)
time_to_dispositionarrayPer-outcome avg_days and median_days from owner assignment to outcome (always an array)

Example

curl -X GET "https://api.kenbun.io/leads/disposition-quality?since_days=90" \
-H "Authorization: Bearer <token>"

CSV Export

GET /leads/disposition-quality/export.csv returns the false-positive and false-negative leads as a single CSV. A section column labels each row as false_positive or false_negative. It accepts the same query parameters as the JSON endpoint.

curl -X GET "https://api.kenbun.io/leads/disposition-quality/export.csv" \
-H "Authorization: Bearer <token>" -o disposition_quality.csv

Filtering Leads by Disposition

The lead list and its CSV export accept a disposition query parameter so you can pull just the leads with a given outcome. Pass a comma-separated list of one or more of the six values, plus the sentinel none for leads with no disposition set:

# Leads marked Qualified or Nurture
curl -X GET "https://api.kenbun.io/leads?disposition=accepted_sql,nurture" \
-H "Authorization: Bearer <token>"

# Leads with no disposition yet
curl -X GET "https://api.kenbun.io/leads?disposition=none" \
-H "Authorization: Bearer <token>"

The same filter is available as checkboxes under Analyze > Leads and carries through to the on-screen CSV export. See Leads for the full parameter list.


How Dispositions Feed Calibration

Once your team has logged enough outcomes, kenbun can suggest improved scoring weights based on which scores actually converted. To enable this:

  1. Go to Configure > Scoring and set your conversion definition to Sales Disposition. This tells kenbun to learn from outcomes marked Qualified (SQL) or Closed Won.
  2. Keep logging dispositions as reps work leads. Calibration unlocks once at least 30 qualifying dispositions have been recorded on scored leads within your look-back window.
  3. When suggestions are ready, review them under Configure > Scoring and apply the ones you agree with. Nothing changes automatically.

Until 30 qualifying dispositions exist, the calibration panel shows your progress (for example, "Only 12 of 30 sales dispositions so far") rather than an error. This same minimum applies whether your conversion definition is Sales Disposition or Manually Labeled outcomes.

Notes

  • All disposition data is scoped to your active Organizational Unit, set automatically by your account context.
  • Setting or clearing a disposition is recorded in your audit log, including the comment and the previous outcome it replaced.
  • Merged leads are dispositioned on the surviving lead; the merged-away lead returns a "not found or merged" error.
  • Re-confirming a disposition that is already set does not change its timestamp and does not affect calibration counts.