Two-way CRM sync
Push CRM contacts and first-party signals into ReplySparrow, and mirror rejected leads back so nobody gets contacted twice.
Why sync both ways
Your CRM knows who your customers, open deals, and opt-outs are. ReplySparrow knows who your agents found, who replied, and who a teammate rejected. When the two disagree, the same person gets two first messages, or a customer gets a cold invitation.
A two-way sync fixes that:
- CRM to ReplySparrow. Push the contacts you want to work, and the actions they take in your product (trial signup, demo booked), as signals next to the ones ReplySparrow detects.
- ReplySparrow to CRM. Mark rejected leads in the CRM, with the reason, so no rep picks them up again.
- Both ways. Reject in ReplySparrow anyone your sales team already works, so campaigns skip them.
Send Authorization: Bearer YOUR_API_KEY. The base URL is https://app.replysparrow.com/api/v1. The key needs the contacts:read, contacts:write, and signals:write scopes. With the MCP server, your assistant runs the tools named in each step for you.
1. Push a CRM contact into ReplySparrow
POST /contacts creates the contact. first_name, last_name, and linkedin_url (the LinkedIn profile) are required. Pass list_id to drop the contact straight into a list a campaign reaches out to, or agent_id to have that source agent score it against its ICP.
MCP tool: contact_create, which takes full_name, linkedin_url, and an optional list_id.
curl --request POST \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"first_name": "John",
"last_name": "Doe",
"linkedin_url": "https://www.linkedin.com/in/johndoe",
"email": "john@techcorp.com",
"company": "Tech Corp",
"title": "Head of Sales",
"list_id": "9f8e7d6c-0000-4000-8000-000000000002"
}' \
"https://app.replysparrow.com/api/v1/contacts"The response (201) is the new contact. Store its id on the CRM record, in a field such as "ReplySparrow ID": every following call needs it.
ReplySparrow keeps one contact per LinkedIn profile in a workspace, so look the person up first with GET /contacts?search=john@techcorp.com (the search matches name, email, company, website, location, and job title) and reuse the id you find instead of creating a second contact.
When the person is in your excluded contacts, for example because their contact was deleted earlier, no contact is created and the call fails:
{
"error": "contact_already_excluded",
"detail": { "excluded_contact_id": "0192c4a1-7d2e-7f3a-9b10-5e6f7a8b9c0d", "kind": "linkedin", "value": "linkedin.com/in/johndoe" }
}The person stays blocked until you remove the exclusion with Delete an excluded contact.
2. Push first-party signals
POST /contacts/{id}/signals adds what the lead did in your product or stack, next to the signals ReplySparrow agents detect. Describe the event in intent, add a short label in intent_keyword, and leave intent_type empty.
MCP tool: signals_manage, with action: "ingest", the contact as target_id, type: "custom", and the event as title. In a CRM workflow, backend, Zapier, Make, or n8n, call the API.
curl --request POST \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "Content-Type: application/json" \
--data '{ "intent": "Booked a demo from the pricing page", "intent_keyword": "demo_booked" }' \
"https://app.replysparrow.com/api/v1/contacts/8f1c2c3e-5b7a-4a51-9d0e-2b6f8f6d9c11/signals"The response is { "created": true, "contact": { ... } }, with the contact's signals most recent first. A signal without intent_type is recorded on every call, so send each event once: a retry adds a duplicate.
3. Keep campaigns away from people sales already works
When a CRM contact becomes a customer, gets an open deal, or opts out, reject them in ReplySparrow. POST /contacts/{id}/reject works like the Reject button in the app: the contact's agent stops working it, pending first-touch drafts are rejected, and any campaign still running for it stops sending. contact_known_or_duplicate is the reason for "already known".
MCP tool: lead_action, with action: "skip" and the reason as text.
curl --request POST \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "Content-Type: application/json" \
--data '{ "reason": "contact_known_or_duplicate" }' \
"https://app.replysparrow.com/api/v1/contacts/8f1c2c3e-5b7a-4a51-9d0e-2b6f8f6d9c11/reject"Other reason codes cover a competitor, a wrong company industry or type, size, or location, a wrong title or seniority, outdated data, or an irrelevant signal, and other takes a free-text reason_text. A rejection is reversible with POST /contacts/{id}/unreject (API only), which puts the contact back in its agent's pipeline to be scored again, not in a campaign: stopped campaign steps are not restarted.
For people who must never be imported again, DELETE /contacts/{id} removes the contact and adds its LinkedIn profile and email to your excluded contacts, so agents skip them and POST /contacts refuses them. Deleting cannot be undone: removing the exclusion later does not restore the contact.
4. Mirror rejected leads back to the CRM
Rejections made in ReplySparrow, by a teammate in the app or through the API, carry rejected_at, rejected_by_user_id, rejection_reason, and rejection_reason_text on the contact, and its state is rejected. Read them with GET /contacts, a list at a time with list_id, and copy every contact whose rejected_at is set onto its CRM record.
MCP tool: leads_list with status: "skipped", or contact_get for one contact. Try asking: "Which leads did my team reject this week, and why?"
curl --header "Authorization: Bearer YOUR_API_KEY" \
"https://app.replysparrow.com/api/v1/contacts?list_id=9f8e7d6c-0000-4000-8000-000000000002&page=1&limit=100"{
"contacts": [
{
"id": "8f1c2c3e-5b7a-4a51-9d0e-2b6f8f6d9c11",
"full_name": "Ada Lovelace",
"email": "ada@example.com",
"state": "rejected",
"rejected_at": "2026-10-13 09:12:44",
"rejected_by_user_id": "usr_17",
"rejection_reason": "company_wrong_size",
"rejection_reason_text": null
}
],
"total": 240,
"page": 1,
"limit": 100,
"totalPages": 3
}Set the CRM record to "Disqualified" or "Do not contact", and keep the reason. Walk page through totalPages, and run the scan on a schedule, for example every night.
The webhooks tell you when an agent qualifies a new lead (On Lead Created) and when a lead replies (On Lead Reply), signed with x-kairo-signature, which keeps the rest of the CRM record current. A rejection does not send one, so the scheduled scan is what catches rejections.
Every field is listed on Create a contact, Add a signal, Reject a lead, and List contacts.