You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(contacts): add via direct POST /leads/{id}/contacts, not the import pipeline
Live testing through Claude Desktop's actual server binary revealed the
import-pipeline add path (leadbay_import_and_qualify) returns 401 on the
test account — POST /imports is rejected even with a valid token, and the
agent surfaced it as a misleading "LEADBAY_TOKEN is no longer valid" error.
The direct endpoint the web UI uses — POST /leads/{id}/contacts — works with
the SAME token (verified 200, contact created with its id). So:
- New leadbay_add_contact composite write tool wrapping POST
/leads/{id}/contacts. One call, no import/qualify quota, accepts
first/last name + optional job_title/linkedin_page/email/phone. Pairs with
leadbay_remove_contact (POST /contacts/{id}/archive).
- Revert the import_and_qualify routing/recipe changes — the add-contact
intent now routes to leadbay_add_contact instead. (Kept one incidental fix:
a stale leadbay_research_lead -> leadbay_research_lead_by_id reference.)
- research_lead_by_id anti-trigger now points at leadbay_add_contact.
- index / _composite-file-names / TOOLS_WITH_ROUTING registration,
WORKFLOWS row #15 + contract updated, new unit test.
Verified live through Desktop's bin.js + token: add -> contact created (no
401); remove -> archived; company back to 0 contacts. Suite green
(core 324, promptforge 16, mcp 366).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: WORKFLOWS.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -26,7 +26,7 @@ The table is the human-readable index. The `yaml expected` + `yaml scenario` blo
26
26
| 12 |**Lens extension — on-demand fill for bigger appetite** — "I want more leads on this lens / I need a bigger batch today" |`leadbay_extend_my_lens`| "I want more leads on this lens — bigger batch today" |
27
27
| 13 |**Lens management — list / switch audiences** — "show me my lenses", "which audiences do I have", "switch to my Joinery lens" |`leadbay_my_lenses`| "Show me my lenses and switch to the Joinery one" |
28
28
| 14 |**Lens creation — make a named audience** — "create a lens called X for sector Y", "set up a new audience" |`leadbay_new_lens`| "Create a lens called Joinery for the fintech sector" |
29
-
| 15 |**Add a contact to a known company** — "this company has no contacts — add Jane Doe, here's her LinkedIn", "add this person I found to that lead" |`leadbay_import_and_qualify` (one `records[]` row, `CONTACT_*` fields keyed by parent `LEADBAY_ID`) | "Acme has no contacts — add Jane Doe, VP Eng, here's her LinkedIn" |
29
+
| 15 |**Add a contact to a known company** — "this company has no contacts — add Jane Doe, here's her LinkedIn", "add this person I found to that lead" |`leadbay_add_contact` (direct `POST /leads/{id}/contacts`; pass `lead_id` + name + optional linkedin/title/email/phone) | "Acme has no contacts — add Jane Doe, VP Eng, here's her LinkedIn" |
30
30
| 16 |**Remove a contact from a company** — "remove this contact", "delete that person, wrong one", "undo the contact I just added" |`leadbay_remove_contact` (archives by the contact's own `contact_id`) | "Remove Jane Doe from that company — I added her by mistake" |
31
31
32
32
---
@@ -241,11 +241,11 @@ prompt: "Set up a prospecting campaign for my team"
241
241
workflow_name: Add a contact to a known company
242
242
prompt_name: ~
243
243
required_calls:
244
-
- leadbay_import_and_qualify
244
+
- leadbay_add_contact
245
245
forbidden_calls:
246
246
- leadbay_report_outreach
247
247
success_criteria:
248
-
- "called leadbay_import_and_qualify with one records[] row carrying CONTACT_* fields keyed by the parent company (LEADBAY_ID / LEAD_NAME / LEAD_WEBSITE)"
248
+
- "called leadbay_add_contact with the parent company lead_id plus the person's name (and any linkedin/title given)"
249
249
- "did NOT switch to an external CRM or claim Leadbay can't add contacts"
Copy file name to clipboardExpand all lines: packages/core/src/tool-descriptions.generated.ts
+45-48Lines changed: 45 additions & 48 deletions
Original file line number
Diff line number
Diff line change
@@ -46,6 +46,48 @@ WHEN NOT TO USE: as a pre-flight gate before bulk ops — operations themselves
46
46
`;
47
47
// endregion: leadbay_account_status
48
48
49
+
// region: leadbay_add_contact
50
+
exportconstleadbay_add_contact: string=`## WHEN TO USE
51
+
52
+
Trigger phrases: "add a contact to this company", "add this person to <company>", "create a contact from this LinkedIn URL", "this company has no contacts — add one", "I found someone on LinkedIn, add them to <lead>".
53
+
54
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
55
+
56
+
Do NOT use for: "import these companies / a CSV of leads and qualify them" → \`leadbay_import_and_qualify\`; "get email/phone for a contact already on the company" → \`leadbay_enrich_titles\`; "remove / delete this contact" → \`leadbay_remove_contact\`.
57
+
58
+
Prefer when: user wants to attach ONE known person to an already-identified company — pass the company's \`lead_id\` plus the person's name (+ optional linkedin_page/title/email/phone)
59
+
60
+
Examples that SHOULD invoke this tool:
61
+
- "Acme has no suggested contacts — add Jane Doe, VP Eng, here's her LinkedIn."
62
+
- "Add this person I found on LinkedIn to that company."
63
+
- "Create a contact for John Smith, CFO, on this lead."
64
+
65
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
66
+
- "Import these 40 domains from my CRM and qualify them."
67
+
- "Get me the email for the contact already on this company."
68
+
- "Remove that contact, it's the wrong person."
69
+
70
+
## RENDER (quick)
71
+
72
+
One-line confirmation: the contact's name + title now sits on the company.
73
+
No table. If the contact has no email/phone yet, note it can be enriched.
74
+
75
+
---
76
+
77
+
Add a single contact (a person) to a known company — the in-conversation "create a contact" path. Use it when a rep has found someone (often just a LinkedIn URL) and wants them on an already-identified Leadbay company without leaving the chat.
78
+
79
+
Pass the parent company's \`lead_id\` plus the person's \`first_name\` + \`last_name\`. Everything else is optional: \`job_title\`, \`linkedin_page\`, \`email\`, \`phone_number\`.
80
+
81
+
Backend: \`POST /leads/{lead_id}/contacts\` → returns the created contact with its new \`id\`. This is the same direct endpoint the Leadbay web UI uses — one call, no import/qualify quota. (Distinct from \`leadbay_import_and_qualify\`, which is for importing *lists of companies*, not attaching a single person.)
82
+
83
+
The created contact starts unenriched — if it has no email/phone, enrich it via \`leadbay_enrich_titles\`. The undo is \`leadbay_remove_contact\` (pass the returned \`contact.id\`).
Requires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw).
88
+
`;
89
+
// endregion: leadbay_add_contact
90
+
49
91
// region: leadbay_add_leads_to_campaign
50
92
exportconstleadbay_add_leads_to_campaign: string=`## WHEN TO USE
51
93
@@ -1132,53 +1174,7 @@ WHEN NOT TO USE: as the first read on a lead — the leadbay_research_lead_by_id
1132
1174
// endregion: leadbay_get_web_fetch
1133
1175
1134
1176
// region: leadbay_import_and_qualify
1135
-
exportconstleadbay_import_and_qualify: string=`## WHEN TO USE
1136
-
1137
-
Trigger phrases: "add a contact to this company", "add this person to <company>", "create a contact from this LinkedIn URL", "this company has no contacts — add one", "I found someone on LinkedIn, add them to <lead>", "import these companies and qualify them", "import this list and run AI qualification".
1138
-
1139
-
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
1140
-
1141
-
Do NOT use for: "show me new leads / discovery" → \`leadbay_pull_leads\`; "deep dive on a single lead I already picked" → \`leadbay_research_lead_by_id\`; "get email/phone for a contact already on the company" → \`leadbay_enrich_titles\`.
1142
-
1143
-
Prefer when: user wants to attach a known person (esp. a LinkedIn URL) to an already-identified company — pass one \`records[]\` row with \`CONTACT_*\` fields keyed by the parent \`LEADBAY_ID\` (else \`LEAD_NAME\`/\`LEAD_WEBSITE\`)
1144
-
1145
-
Examples that SHOULD invoke this tool:
1146
-
- "Acme has no suggested contacts — add Jane Doe, VP Eng, here's her LinkedIn."
1147
-
- "Add this person I found on LinkedIn to that company."
1148
-
- "Import these 40 domains from my CRM and qualify them."
1149
-
1150
-
Examples that should NOT invoke this tool (sound similar, route elsewhere):
1151
-
- "Show me today's leads."
1152
-
- "Tell me everything about that lead I just picked."
1153
-
- "Get me the email for the contact already on this company."
1154
-
1155
-
## RENDER (quick)
1156
-
1157
-
Use the import-result layout: summarize imported/qualified vs not-imported
1158
-
counts, then per-lead qualification answers. For a single added contact,
1159
-
confirm the person now sits on the parent company.
1160
-
1161
-
---
1162
-
1163
-
Import + qualify leads in one call. Pass either \`domains: [{domain, name?}]\` (Mode A) OR \`records[]\` with \`mappings\` (Mode B). At least one mapped field must be LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE. Discover the org's mappable surface via \`leadbay_list_mappable_fields\`. For messy files, prefer the \`leadbay_import_file\` prompt which walks an agent through scan → resolve → preserve → commit phases.
1164
-
1165
-
**Add a single contact to a known company** (the "create_contact" path): this is just Mode B with one record. Map the parent company + \`CONTACT_*\` fields — the import **creates an org contact** on that company. **\`LEAD_NAME\` is required even when you pass \`LEADBAY_ID\`** — the import rejects the row with \`missing LEAD_NAME field\` otherwise. So always send both: \`LEADBAY_ID\` (the exact match key) AND \`LEAD_NAME\` (the company's display name). Minimal call:
1166
-
1167
-
\`\`\`
1168
-
records: [{
1169
-
LEADBAY_ID: "<lead uuid>", // exact match key for the existing company
If you don't have the \`LEADBAY_ID\`, key on \`LEAD_NAME\` (+ optional \`LEAD_WEBSITE\`) alone and the import fuzzy-matches to the existing company. Repeating the same parent across rows adds multiple contacts to that one company. After the contact lands, enrich its email/phone via \`leadbay_enrich_titles\`. Use this when a rep found a person (often just a LinkedIn URL) and wants them on an already-identified Leadbay company without leaving the conversation.
1177
+
exportconstleadbay_import_and_qualify: string=`Import + qualify leads in one call. Pass either \`domains: [{domain, name?}]\` (Mode A) OR \`records[]\` with \`mappings\` (Mode B). At least one mapped field must be LEADBAY_ID, CRM_ID, SIREN, LEAD_NAME, or LEAD_WEBSITE. Discover the org's mappable surface via \`leadbay_list_mappable_fields\`. For messy files, prefer the \`leadbay_import_file\` prompt which walks an agent through scan → resolve → preserve → commit phases.
1182
1178
1183
1179
WHEN TO USE: agent has a list of companies (domains, or CSV-shaped rows from the user's CRM) and wants the full AI qualification — qualification answers, web-research signals — without orchestrating import + bulk_qualify_leads + lead_profile chains by hand.
1184
1180
@@ -2687,7 +2683,7 @@ Trigger phrases: "tell me about this lead", "deep dive on the lead I just picked
2687
2683
2688
2684
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
2689
2685
2690
-
Do NOT use for: "company name without lead id" → \`leadbay_research_lead_by_name_fuzzy\`; "draft outreach for <Contact>" → \`leadbay_prepare_outreach\`; "add a contact to this company" → \`leadbay_import_and_qualify\`.
2686
+
Do NOT use for: "company name without lead id" → \`leadbay_research_lead_by_name_fuzzy\`; "draft outreach for <Contact>" → \`leadbay_prepare_outreach\`; "add a contact to this company" → \`leadbay_add_contact\`.
2691
2687
2692
2688
Prefer when: user picked a row and you have its UUID; pass \`leadId\`
2693
2689
@@ -3361,6 +3357,7 @@ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible
3361
3357
// Map for legacy callers; prefer importing the named constant directly.
0 commit comments