Lead qualification – disqualified, lost and reasons
With lead qualification switched on (project setting, default on for B2B, clinic, real-estate and coaching templates, off for shops – see Project mode) a lead can be Disqualifiziert: the person fundamentally does not fit. That is different from Verloren: the person fitted but did not decide.
| Status | Reasons (optional) |
|---|---|
disqualified | spam_fake, wrong_audience, no_budget, size_mismatch, no_need, competitor_agency, job_application, duplicate (needs duplicateOf – the original lead), unreachable, other (needs a note) |
lost | price, timing, competitor, no_response, other; older codes too_expensive, not_reached, no_interest stay valid |
A reason is never required: save without one („ohne Grund“) and add it later by sending the same status
with reasonCode. Organizations add their own reasons; built-ins are read-only.
Setting a status
PATCH /v1/projects/{project}/leads/{id}/pipeline (scope leads:status) with
{"status": "disqualified", "reasonCode": "wrong_audience", "reasonNote": "Sucht Festanstellung"};
MCP leads_update_status. reasonNote is personal free text (≤ 500 characters): readers without
contact rights (no leads:read) see the code and label, never the note.
Disqualifying never creates or uploads a conversion. A lead that was reported before stays reported –
the answer lists those platforms in alreadyReported, so the dialog can say so.
History and undo
GET /v1/projects/{project}/leads/{id}/status-history– who changed what when, from which status to which, with reason (leads:summaryorleads:read).POST /v1/projects/{project}/leads/{id}/status/undo– restores the previous status with its reason and writes a new history entry (leads:status).POST /v1/projects/{project}/leads/{id}/merge– merges a duplicate into the original (leads:write).
Reasons of the organization
| Call | Scope |
|---|---|
GET /v1/projects/{project}/lead-reasons (kind, include_archived) | analytics:read |
POST /v1/projects/{project}/lead-reasons | config:write |
PATCH /v1/projects/{project}/lead-reasons/{id} | config:write |
POST /v1/projects/{project}/lead-reasons/{id}/archive | config:write – unused reasons are deleted, used ones archived (leads keep them) |
MCP: lead_reasons_list. Every change is audited.
Qualified leads
Qualified leads = every lead that is not marked disqualified. The quiz grade (A–D, „Passt gut“) is
only a hint. Reports offer „Qualifizierte Leads“ and „Kosten pro qualifiziertem Lead“ next to leads,
and the share of disqualified leads per campaign and ad (qualified_leads,
cost_per_qualified_lead, disqualified_share in the ads report).
Merging duplicates
POST /v1/projects/{project}/leads/{id}/merge with {"into": "<original lead id>"} is the same as setting
{"status": "disqualified", "reasonCode": "duplicate", "duplicateOf": "<original>"} and needs the same
scope as every status change, **leads:status** (the scope leads:write named in the list above is
outdated). Nothing is moved:
- the duplicate keeps its own row, notes and conversions and becomes „Disqualifiziert · Doppelt“ with a link to the original; it never counts as a lead again (also when the original is erased later – the link is then cleared, the duplicate stays a duplicate);
- the original's profile shows the duplicate's visits, notes and reports merged at read time
(
GET …/leads/{id}/mergedlists the merge group with display names only, never contact data); - conversions already reported for the duplicate stay reported, disqualifying reports nothing new;
- a merged original resolves to its root (C → B while B → A becomes C → A); a merge that would close a
cycle, the lead itself, or a lead of another project or organization is refused with
422; dryRun: trueanswers what the original would show additionally and writes nothing;- undo (
POST …/status/undoon the duplicate) separates them again.
Conflicts, double clicks and undo
Send expectedChangedAt (the pipeline.changedAt you showed) with a status change or a merge: when
somebody else changed the status meanwhile the API answers 409 conflict with details.changedBy,
details.changedAt and details.status („Inzwischen von Alex geändert“). Sending the state that is
already stored again is idempotent (unchanged: true, no history entry), so a double click never
writes twice.
Every change answers with historyId. POST …/status/undo?history_id=<id> undoes exactly that entry
and refuses with 409 when a later change by somebody else exists or the entry was undone already.
Undo restores the earlier status as it was – also an own reason that was archived meanwhile, and
„Disqualifiziert“ when qualification was switched off meanwhile.
Revenue import as a background run
POST /v1/projects/{project}/leads/revenue (MCP leads_revenue_import) runs as background run
leads.revenue_import (see Background runs): up to 50 rows finish within
the request and come back with every row; larger imports answer at once with run (queued/running) –
follow it with runs.get/runs.events or MCP runs_get. The finished run's result holds the report
per row (updated, unchanged, not_found, ambiguous, invalid, value_too_high with a message).
The same rows sent twice while the import runs return the running import. A row for a merged duplicate
books on its original. The leads inbox shows the newest import with live progress and the rows that
could not be booked.
Lists, filters and exports
leads.list and portfolio.leads filter and sort on status (including disqualified) and on
reason (f.reason=in:no_budget,price, f.reason=isEmpty, sort=reason:asc, index-backed). The CSV
export has a „Grund“ column with the reason's label („ohne Grund“ when none was given) – never the
note. Without fields the export puts „Grund“ right after „Status“ (in a project with lead
qualification off only when asked for with fields). The dashboard's default view „Offen“ leaves disqualified leads out; „Alle“ includes them.
MCP
leads_update_status–status, optionalreason_code,reason_note(hidden from readers withoutleads:read),duplicate_of(merges into the original) andexpected_changed_at(conflict guard).lead_reasons_list– built-in and own reasons per kind with code, labels, usage and whether a note or the original lead is needed;include_archivedadds archived own reasons (old leads keep them).
With lead qualification off there is no „Disqualifiziert“ status and no qualification column; leads disqualified earlier keep status and reason (shown with „Qualifizierung aus“), nothing is deleted.