The address book for AI agents.
Give any agent a memory for the people it works with. One install. No forms. No data entry.
If this is useful, star it — it helps other developers find it.
npx nondual init # store your API key
npx nondual get-contact-info dario@anthropic.com # get contact informationGet a key:
curl -s -X POST https://api.nondual.cloud/v1/keys \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com", "requested_by":"agent"}'Response:
{
"api_key": "nd_xxxxxxxxxxxxxxxxxxxx",
"status": "unclaimed",
"message": "Key issued. A claim code was sent to you@example.com."
}Or call get-contact-info without a key — 3 free lookups per day, no signup.
Add to your MCP config:
{
"mcpServers": {
"nondual": {
"url": "https://mcp.nondual.cloud/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}Both https://mcp.nondual.cloud/mcp and https://mcp.nondual.cloud are accepted.
Install once or use via npx:
npm install -g nondual
# or: npx nondual <command>Store your API key locally (~/.nondual).
npx nondual initGet the full contact profile: name, role, company, relationship summary, recent interactions, open followups, and recommended next action.
npx nondual get-contact-info dario@anthropic.com
# Name: Dario Amodei
# Role: Co-Founder & CEO
# Company: Anthropic
# LinkedIn: https://linkedin.com/in/darioamodei
# GitHub: darioamodei
#
# Co-founder and CEO of Anthropic...
#
# Relationship: Sent intro 6 days ago. Follow-up proposal is due.
# Recommended: Follow up with a concrete proposal.Options:
npx nondual get-contact-info dario@anthropic.com --json # raw JSON
npx nondual get-contact-info dario@anthropic.com --enrich false # workspace-only, no enrichmentRecord an interaction. Optionally create a followup or complete existing ones in the same call.
npx nondual record-contact-interaction dario@anthropic.com \
--channel email \
--direction outbound \
--summary "Sent intro about partnership opportunities"
# With full body inline:
npx nondual record-contact-interaction dario@anthropic.com \
--channel email \
--direction outbound \
--summary "Sent intro about partnership opportunities" \
--details "Hi Dario, I wanted to reach out about..."
# Load body from a file:
npx nondual record-contact-interaction dario@anthropic.com \
--channel email \
--direction outbound \
--summary "Sent intro about partnership opportunities" \
--details-file ./email-body.txt
# Create a followup in the same call:
npx nondual record-contact-interaction dario@anthropic.com \
--channel call \
--summary "Discussed partnership" \
--followup "Send proposal deck" \
--due 2026-08-01
# Complete all open followups at the same time:
npx nondual record-contact-interaction dario@anthropic.com \
--channel email \
--summary "Sent the proposal" \
--complete all| Flag | Required | Values | Description |
|---|---|---|---|
--channel |
yes | email call linkedin slack meeting sms other |
Communication channel |
--direction |
no | inbound outbound unknown |
Who initiated (default: outbound) |
--summary |
yes | string | One-sentence description |
--details |
no | string | Full content — email body, transcript, notes. No length limit. |
--details-file |
no | path | File whose contents become details |
--followup |
no | string | Create a followup with this action text |
--due |
no | ISO date | Due date for the followup |
--complete |
no | all or comma-separated IDs |
Complete followups in the same call |
List open followups with contact snippets. Filter by due date, owner, or company.
npx nondual list-open-followups
npx nondual list-open-followups --due-before 2026-08-01
npx nondual list-open-followups --company anthropic.com
npx nondual list-open-followups --jsonAll contacts and recent interactions for a company domain.
npx nondual get-company-activity anthropic.com
npx nondual get-company-activity anthropic.com --jsonSearch contacts by name, company, or email.
npx nondual search "anthropic"Show your current API key and the source it came from.
npx nondual whoami
# Email: you@example.com
# Key: nd_12cb94...
# Source: ~/.nondual/config.jsonnpm install nondualimport { Nondual } from 'nondual';
const nd = new Nondual({ apiKey: process.env.NONDUAL_API_KEY });const res = await nd.getContactInfo({ contact: 'dario@anthropic.com' });
console.log(res.contact.name); // "Dario Amodei"
console.log(res.contact.profile.role); // "Co-Founder & CEO"
console.log(res.contact.company.name); // "Anthropic"
console.log(res.contact.do_not_disturb); // false
console.log(res.relationship_summary); // "2 interactions from your agents"
console.log(res.recent_interactions); // [{ channel, occurred_at, summary }, ...]
console.log(res.open_followups); // [{ id, action, due }, ...]
console.log(res.recommended_next_action); // "Follow up with a concrete proposal."Pass also when you have both an email and LinkedIn URL for the same person — guarantees a single merged contact regardless of enrichment data:
const res = await nd.getContactInfo({
contact: 'kai@company.com',
also: ['https://linkedin.com/in/kuhlig'],
});Set enrich: false for a fast workspace-only lookup (no external enrichment):
const res = await nd.getContactInfo({ contact: 'dario@anthropic.com', enrich: false });const res = await nd.recordContactInteraction({
contact: 'dario@anthropic.com',
channel: 'email',
direction: 'outbound',
summary: 'Sent intro about partnership opportunities',
details: 'Hi Dario, I wanted to reach out...', // optional
followup_action: 'Send proposal deck', // optional: create followup in same call
followup_due: '2026-08-01',
complete_followups: 'all', // optional: close open followups
}, { agent: 'sales-bot' });
console.log(res.interaction.id); // "interaction_..."
console.log(res.followup_created); // { id, action, due } or null
console.log(res.followups_completed); // ["followup_..."]
console.log(res.contact_created); // true if this was a new contactconst res = await nd.listOpenFollowups({
due_before: '2026-08-01', // optional
owner: 'sales-bot', // optional
company: 'anthropic.com', // optional — exact domain match
});
for (const f of res.followups) {
console.log(f.action, f.due, f.contact.name, f.contact.do_not_disturb);
}const res = await nd.getCompanyActivity({ domain: 'anthropic.com' });
console.log(res.domain); // "anthropic.com"
console.log(res.total_contacts); // 3
for (const c of res.contacts) {
console.log(c.name, c.interactions.length);
}Bulk-upsert contacts, interactions, and follow-ups in one call. Up to 5000 rows.
const res = await nd.imports({ rows: [
{
email: 'dario@anthropic.com',
linkedin_url: 'https://linkedin.com/in/darioamodei',
name: 'Dario Amodei',
company: 'Anthropic',
company_domain: 'anthropic.com',
summary: 'Met at AI Summit 2026',
channel: 'meeting',
occurred_at: '2026-06-15T09:00:00Z',
action: 'Send follow-up email',
due: '2026-08-01',
}
]});
console.log(res.imported.contacts_created); // 1
console.log(res.imported.interactions_created); // 1
console.log(res.imported.followups_created); // 1Base URL: https://api.nondual.cloud/v1
Auth: Authorization: Bearer YOUR_API_KEY
curl -s -X POST https://api.nondual.cloud/v1/keys \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com", "requested_by":"agent"}'{
"api_key": "nd_xxxxxxxxxxxxxxxxxxxx",
"status": "unclaimed",
"message": "Key issued. A claim code was sent to you@example.com."
}Claim the key (removes workspace restrictions):
curl -s -X POST https://api.nondual.cloud/v1/keys/claim \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com", "otp":"123456"}'curl -s -X POST https://api.nondual.cloud/v1/get-contact-info \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact": "dario@anthropic.com"}'Response:
{
"contact": {
"id": "contact_...",
"name": "Dario Amodei",
"do_not_disturb": false,
"identifiers": {
"emails": ["dario@anthropic.com"],
"linkedin_url": "linkedin.com/in/darioamodei",
"phones": [],
"handles": []
},
"profile": { "role": "Co-Founder & CEO", "about": "...", "location": "..." },
"company": { "name": "Anthropic", "domain": "anthropic.com" },
"next": { "action": "Follow up with a concrete proposal." }
},
"enriched": true,
"relationship_summary": "2 interactions. Follow-up proposal is due.",
"recommended_next_action": "Follow up with a concrete proposal.",
"recent_interactions": [],
"open_followups": []
}Pass also to merge an email and LinkedIn URL into one contact:
curl -s -X POST https://api.nondual.cloud/v1/get-contact-info \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contact": "kai@company.com", "also": ["https://linkedin.com/in/kuhlig"]}'curl -s -X POST https://api.nondual.cloud/v1/record-contact-interaction \
-H "Authorization: Bearer ***" \
-H "X-Nondual-Agent: my-agent" \
-H "Content-Type: application/json" \
-d '{
"contact": "dario@anthropic.com",
"channel": "email",
"direction": "outbound",
"summary": "Sent intro about partnership opportunities",
"details": "Hi Dario, I wanted to reach out about a potential partnership. Happy to share more about what we are building.",
"followup_action": "Send proposal deck",
"followup_due": "2026-08-01"
}'curl -s "https://api.nondual.cloud/v1/list-open-followups?due_before=2026-08-01" \
-H "Authorization: Bearer YOUR_API_KEY"curl -s "https://api.nondual.cloud/v1/get-company-activity?domain=anthropic.com" \
-H "Authorization: Bearer YOUR_API_KEY"curl -s -X POST https://api.nondual.cloud/v1/imports \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"email": "dario@anthropic.com",
"linkedin_url": "https://linkedin.com/in/darioamodei",
"name": "Dario Amodei",
"company": "Anthropic",
"company_domain": "anthropic.com",
"summary": "Met at AI Summit 2026",
"channel": "meeting",
"action": "Send follow-up email",
"due": "2026-08-01"
}
]
}'| Tool | Input | What it does |
|---|---|---|
get_contact_info |
contact, enrich?, also? |
Full profile + relationship context |
record_contact_interaction |
contact, channel, summary, followup_action?, complete_followups? |
Log interaction, create/close followups |
list_open_followups |
due_before?, owner?, company? |
All open followups with contact snippets |
get_company_activity |
domain |
All contacts + interactions for a company |
The value of Nondual is the memory loop, not just enrichment:
agent_sales → get_contact_info("dario@anthropic.com") # who is this? any history?
agent_sales → record_contact_interaction(channel=email, …) # log the outreach + create followup
agent_exec → get_contact_info("dario@anthropic.com") # weeks later, different agent
← sees: who reached out, when, what was said, open followup, recommended next step
Every agent on your key reads and writes the same relationship.
Runnable examples live in examples/:
examples/typescript/full-loop.ts— the four-call memory loopexamples/typescript/csv-import.ts— import a LinkedIn connections exportexamples/python/rest-loop.py— the same loop in Python via RESTexamples/cursor-rules/— drop-in rules for Cursor agents
docs/api.md— REST API referencedocs/mcp.md— MCP setup (Cursor, Claude, Hermes)docs/cli.md— CLI commands and flagsdocs/sdk.md— TypeScript SDKdocs/agents.md— Agent onboarding and prompt templates- Live: nondual.cloud/docs
MIT