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.

StatusReasons (optional)
disqualifiedspam_fake, wrong_audience, no_budget, size_mismatch, no_need, competitor_agency, job_application, duplicate (needs duplicateOf – the original lead), unreachable, other (needs a note)
lostprice, 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:summary or leads: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

CallScope
GET /v1/projects/{project}/lead-reasons (kind, include_archived)analytics:read
POST /v1/projects/{project}/lead-reasonsconfig:write
PATCH /v1/projects/{project}/lead-reasons/{id}config:write
POST /v1/projects/{project}/lead-reasons/{id}/archiveconfig: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}/merged lists 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: true answers what the original would show additionally and writes nothing;
  • undo (POST …/status/undo on 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, optional reason_code, reason_note (hidden from readers without leads:read), duplicate_of (merges into the original) and expected_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_archived adds 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.