MCP tool reference
Arguments and behaviour of every tool on Pairly's CRM MCP server.
Generated from the server's own tools/list response; the stamp under each tool names the api commit it was taken from. Connection requirements are on the MCP server page.
Contacts
upsert_person · Create or update a CRM contactCreate a contact, or fill in gaps on an existing one matched by email. Never overwrites a field a human already set. Args: email (str): The contact's email address. Required. first_name (Optional[str]): First name. last_name (Optional[str]): Last name. phone (Optional[str]): Phone number, any format. company_domain (Optional[str]): Links to an existing company by domain if one is already known; never creates a new company (use upsert_company for that). tags (Optional[list[str]]): Tags to merge onto the contact. Existing tags are kept; nothing is ever removed by omitting a tag here. email_marketing_consent (Optional[EmailMarketingConsent]): Explicit email marketing consent your app captured: {"granted": bool, "granted_at": ISO datetime, "method": str (max 100), "source_url": URL}; only "granted" is required. true records it with this call as evidence, false revokes it; omit it to leave consent unchanged. Returns: dict: {"status": "ok", "person_id": str}
| Argument | Type | Required | Default |
|---|---|---|---|
email | string | yes | |
company_domain | string | no | |
email_marketing_consent | EmailMarketingConsent | no | |
first_name | string | no | |
last_name | string | no | |
phone | string | no | |
tags | string[] | no |
From tools/list at api 3ab3ffb0, 2026-09-29.
delete_person · Archive a CRM contactArchive the contact matching this email. Idempotent -- calling this again for an already-archived (or never-existing) address still returns ok. Args: email (str): The contact's email address. Required. Returns: dict: {"status": "ok"}
| Argument | Type | Required | Default |
|---|---|---|---|
email | string | yes |
From tools/list at api 3ab3ffb0, 2026-09-29.
Companies
upsert_company · Create or update a CRM companyCreate a company, or match an existing one by domain. Args: name (str): The company's name. Required. domain (Optional[str]): The company's domain, used to match an existing company and avoid creating a duplicate. Returns: dict: {"status": "ok", "company_id": str}
| Argument | Type | Required | Default |
|---|---|---|---|
name | string | yes | |
domain | string | no |
From tools/list at api 3ab3ffb0, 2026-09-29.
Account events
record_user_created · Record that a user account was createdLike upsert_person, plus a timestamped lifecycle note ("Account created") visible in Pairly's activity view. Call this instead of upsert_person the moment an account is created in your app. Args: email (str): The new user's email address. Required. first_name (Optional[str]): First name. last_name (Optional[str]): Last name. phone (Optional[str]): Phone number, any format. company_domain (Optional[str]): Links to an existing company by domain. tags (Optional[list[str]]): Tags to merge onto the contact. email_marketing_consent (Optional[EmailMarketingConsent]): Explicit email marketing consent your app captured: {"granted": bool, "granted_at": ISO datetime, "method": str (max 100), "source_url": URL}; only "granted" is required. true records it with this call as evidence, false revokes it; omit it to leave consent unchanged. Returns: dict: {"status": "ok", "person_id": str}
| Argument | Type | Required | Default |
|---|---|---|---|
email | string | yes | |
company_domain | string | no | |
email_marketing_consent | EmailMarketingConsent | no | |
first_name | string | no | |
last_name | string | no | |
phone | string | no | |
tags | string[] | no |
From tools/list at api 3ab3ffb0, 2026-09-29.
record_email_confirmed · Record that a user confirmed their emailMark a user's email as confirmed. If Pairly has never heard of this email, the contact is created on the spot -- there is no need to call record_user_created first. Args: email (str): The confirmed email address. Required. tags (Optional[list[str]]): Tags to merge onto the contact -- typically something like ["email_confirmed"] so the business can build a campaign audience from it. email_marketing_consent (Optional[EmailMarketingConsent]): Explicit email marketing consent your app captured: {"granted": bool, "granted_at": ISO datetime, "method": str (max 100), "source_url": URL}; only "granted" is required. true records it with this call as evidence, false revokes it; omit it to leave consent unchanged. Returns: dict: {"status": "ok", "person_id": str}
| Argument | Type | Required | Default |
|---|---|---|---|
email | string | yes | |
email_marketing_consent | EmailMarketingConsent | no | |
tags | string[] | no |
From tools/list at api 3ab3ffb0, 2026-09-29.
record_event · Record a custom lifecycle eventRecord any named moment worth logging -- an upgrade, a completed onboarding step, a churn signal -- with your own structured data attached. Pairly stores properties verbatim and does not interpret event_name against a fixed list. Args: email (str): The contact's email address. Required. event_name (str): A short name for the event, e.g. "completed_onboarding". Required. properties (Optional[dict]): Arbitrary structured data about the event. tags (Optional[list[str]]): Tags to merge onto the contact. Returns: dict: {"status": "ok", "person_id": str}
| Argument | Type | Required | Default |
|---|---|---|---|
email | string | yes | |
event_name | string | yes | |
properties | object | no | |
tags | string[] | no |
From tools/list at api 3ab3ffb0, 2026-09-29.
send_transactional_email · Send a transactional email as the businessSend one transactional email (a receipt, a password reset) from the business's verified domain, or from Pairly's address with a "Powered by PairlyHQ" footer when it has none. Transactional only: no marketing. Sent exactly once per idempotency_key -- retry with the same key and the same content. Args: to (str): The recipient's email address. Required. subject (str): 1-300 characters. Required. idempotency_key (str): 1-200 characters, unique per message. Required. html (Optional[str]): HTML body, up to 512,000 characters. One of html/text is required. text (Optional[str]): Plain-text body, up to 512,000 characters. reply_to (Optional[str]): Where replies go. Returns: dict: {"send_id": str, "status": "sent" | "queued" | "suppressed" | "failed", "error"?: "no_verified_sender" | "provider_rejected" | "provider_unavailable" | "outcome_unknown" | "expired" | "send_failed"}
| Argument | Type | Required | Default |
|---|---|---|---|
idempotency_key | string | yes | |
subject | string | yes | |
to | string | yes | |
html | string | no | |
reply_to | string | no | |
text | string | no |
From tools/list at api 3ab3ffb0, 2026-09-29.