联系人
搜索、管理、导入、导出和补全联系人信息,以及相关备注、活动记录、时间线和商机。
联系人是您发送邮件的对象。本组端点涵盖完整生命周期:搜索和筛选、创建和编辑(单个及批量)、CSV/XLSX/JSON 导入和导出、包含完整关联数据的联系人 360 视图、各联系人的邮件和活动动态,以及关联到联系人的 CRM 备注、活动和商机。列表端点返回 data 数组及 pagination 分页结构;错误采用标准的 {error, message, code, request_id} 格式(参见 错误码)。
搜索联系人
POST /contacts/search
在组织范围内进行服务端多维度联系人搜索。筛选条件放在请求体中;分页通过查询参数控制。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
cursor | query | string | 上一页 pagination.next_cursor 返回的不透明分页游标。它保存了按原排序规则继续读取下一页的精确位置,因此排序值相同的记录不会被跳过或重复。游标格式无效,或使用与签发时不同的 sort_by 或 reverse 重放游标,均返回 400。 |
limit | query | string | 每页条数(数字字符串)。 |
category | query | string | 按单个标签 ID 快速筛选。 |
请求体
所有字段均可选;空请求体匹配组织中的所有联系人。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
query | string | 否 | 在核心字段(姓名、邮箱、公司)中进行文本搜索。 |
custom_field_filters | array | 否 | 各自定义字段的筛选条件:{ "name", "value", "type" },其中 type 为 equal、starts_with、ends_with、contains 之一。 |
campaign_ids | string[] | 否 | 联系人必须同时属于列出的所有营销活动。 |
lead_status | string | 否 | 按单个派生潜在客户状态筛选:pending、active、completed、replied、bounced、failed、paused、undeliverable 或 unsubscribed。campaign_ids 必须恰好包含一项,否则请求以 lead_filter_requires_campaign 拒绝;未知值以 invalid_lead_status 拒绝。 |
engagement | string | 否 | 按该营销活动中的互动情况筛选:opened、not_opened、clicked、not_clicked、replied、not_replied 或 bounced。opened 表示真人打开(机器打开不计入);not_* 值仅匹配至少已发送一个步骤的潜在客户。与 lead_status 以 AND 逻辑组合。campaign_ids 必须恰好包含一项(否则返回 lead_filter_requires_campaign);未知值以 invalid_engagement 拒绝。 |
category_ids | string[] | 否 | 标签 ID。联系人必须同时具有列出的所有标签。 |
segment_ids | string[] | 否 | 联系人必须同时属于列出的所有分群(综合条件和手动覆盖)。ID 不是有效 UUID 时以 400 拒绝;未知分群不会匹配任何联系人。 |
verification_status | string | 否 | 按验证结论筛选:valid、risky、invalid 或 unknown。 |
mail_hosts | string[] | 否 | 收件箱由任一所列 mail_host 托管的联系人(参见 邮箱服务商)。"" 匹配服务商未知的联系人:尚未检查,或域名没有邮件服务器。未知值以 invalid_mail_host 拒绝。 |
min_campaigns | integer | 否 | 关联营销活动数量的最小值。 |
max_campaigns | integer | 否 | 关联营销活动数量的最大值。 |
subscribed | boolean | 否 | 按订阅状态筛选。 |
created_after | string (RFC 3339) | 否 | 创建时间不早于此时间。 |
created_before | string (RFC 3339) | 否 | 创建时间不晚于此时间。 |
updated_after | string (RFC 3339) | 否 | 更新时间不早于此时间。 |
updated_before | string (RFC 3339) | 否 | 更新时间不晚于此时间。 |
sort_by | string | 否 | 排序列:created_at(默认)、updated_at、first_name、last_name、email、company、phone、campaign_count 或 mail_host。mail_host 按服务商值排序,服务商未知的联系人在升序时排在最后,降序时排在最前。custom:<key> 将自定义字段按文本排序,缺少该字段或字段为空的联系人在升序时排在最后,降序时排在最前。不可能存在的自定义字段名称以 invalid_sort_by 拒绝;其他未知值则回退到 created_at。 |
reverse | boolean | 否 | 为 true 时升序排列。默认降序。 |
{
"query": "acme",
"subscribed": true,
"category_ids": ["6f1c0b1e-0c2a-4b3a-9c1e-2d3f4a5b6c7d"],
"min_campaigns": 1,
"sort_by": "first_name",
"reverse": false
}响应
返回联系人 data 数组及 pagination 分页结构。将 pagination.next_cursor 作为 cursor 传回以继续分页,直到 has_more 为 false;pagination.total 是完整筛选结果集的数量,仅在第一页返回。
{
"data": [
{
"id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"first_name": "Dana",
"last_name": "Reyes",
"email": "[email protected]",
"company": "Acme",
"phone": "+15551234567",
"custom_fields": { "title": "VP Sales" },
"subscribed": true,
"campaigns": [{ "id": "c1...", "name": "Q3 Outbound" }],
"categories": [{ "id": "6f1c...", "title": "VIP", "color": "#0ea5e9" }],
"verification_status": "valid",
"verification_reason": "recipient accepted",
"verification_sub_status": "",
"verification_source": "probe",
"verification_provider": "builtin",
"verification_checked_at": "2026-06-10T11:58:00Z",
"is_catch_all": false,
"mail_host": "gmail",
"esp_provider": "gmail",
"outreach": {
"sent": 3,
"opened": 2,
"clicked": 0,
"replied": 1,
"bounced": 0,
"last_sent_at": "2026-06-08T09:12:00Z",
"last_opened_at": "2026-06-08T10:40:00Z",
"last_replied_at": "2026-06-09T07:05:00Z"
},
"updated_at": "2026-06-10T12:00:00Z",
"created_at": "2026-05-01T09:30:00Z"
}
],
"pagination": {
"total": 1280,
"next_cursor": "s1_b3BhcXVlLWN1cnNvcg",
"has_more": true
}
}每位联系人都携带其地址验证信息:verification_status(valid、risky、invalid 或 unknown)、verification_sub_status(catch_all、disposable、role、spamtrap、mailbox_full、no_mx、syntax、undisclosed 或为空)、verification_source(内置检查为 probe,已连接的验证服务为 provider,随联系人提供的结论为 imported,成员手动设置的结论为 manual,从未检查时为空)、verification_provider(结论产生方)、verification_reason、verification_checked_at、verification_requested_at(成员请求的重新检查尚待执行时设置;新结果产生前,原结论继续有效),以及 verification_confidence(0 至 100,根据检查结果及向该地址发送真实邮件的情况评分;参见 真实邮件如何改进验证结果)。营销活动从不向 invalid 地址发送,且仅在启用了 risky_emails 设置时才向 risky 地址发送。
邮箱服务商
每位联系人都携带 mail_host(该地址收件箱的托管方)及 esp_provider(托管方所属类别)。Warmbly 在后台读取域名 DNS 来确定这些值,通常在添加联系人或更改地址后一分钟内完成:先检查 MX 记录;若真实托管方前有 Proofpoint 或 Mimecast 等过滤网关,则继续检查 SPF 记录。gmail.com 这类个人邮箱域名无需查询即可识别。无论多少联系人共用同一域名,每个域名只查询一次,也不会向第三方发送任何地址信息。
mail_host 为以下值之一:google_workspace、gmail、microsoft365、outlook、zoho、yahoo、aol、icloud、fastmail、godaddy、namecheap、ionos、hostinger、ovh、migadu、purelymail、rackspace、yandex、gmx、proton 或 other(域名可以收信,但 Warmbly 未具体识别其托管方);检查尚未执行或域名没有邮件服务器时,该值为空。esp_provider 对两种 Google 产品均为 gmail,对两种 Microsoft 产品均为 outlook,其他为 other,并在 mail_host 为空时为空。营销活动的 ESP 匹配 按 esp_provider 配对发件人和收件人。未能识别服务商的域名会在一周后重新检查;查询失败的域名则在一小时内重试。
搜索仅按一个营销活动筛选时,每位联系人还包含 campaign_lead 对象,记录其在该活动中的处理状态(status、sent、opened、machine_opened、clicked、replied、bounced、current_step、sender、last_activity_at,暂停时还包含 hold,该潜在客户有抄送对象时包含 cc,失败时包含 failure_reason)。cc 列出每封发给该潜在客户的邮件中都会抄送的联系人,结构与 获取潜在客户的抄送联系人 的返回值相同。sender 是该潜在客户整个邮件序列使用的发件邮箱地址,在首封邮件发出时固定,之前不出现。opened 统计真人打开过的步骤;由邮件客户端自动获取的步骤(Apple Mail 隐私保护等)则计入 machine_opened,与分析摘要报告的机器打开一致。status 的派生优先级从高到低为:unsubscribed(未订阅)、bounced、replied、failed(某个步骤在所有重试后仍无法发送;failure_reason 提供发送工作节点的原因)、completed(所有邮件步骤均已发送,未回复)、paused(潜在客户的流程因外出自动回复、手动操作,或因其被抄送此活动中另一位潜在客户的邮件而暂停;hold 对象包含 since、until、reason 和 source)、active(部分步骤已发送,仍有待发送步骤)、undeliverable(发送前验证拒绝该地址,因此活动跳过此潜在客户且不会向其发送),以及 pending(已排队,尚未发送)。只有发送工作节点将邮件交付给邮箱服务商后,步骤才计为已发送;工作节点未能完成的发送会在营销活动下一轮处理时重试,绝不会显示为已发送。lead_status 筛选条件可限定为其中一个状态类别。
搜索仅按一个营销活动筛选时,第一页(没有 cursor)还包含 lead_counts 对象:该活动各状态的潜在客户总数,不受 lead_status 和 engagement 筛选条件影响,因此可一次获取所有状态类别的总数。这些状态类别包括 paused。此外,该对象还提供与 engagement 筛选条件相对应的互动总数:contacted(至少已发送一个步骤的潜在客户)、opened(任一步骤出现真人打开)、clicked 和 replied_any(任一步骤有回复,不论派生状态是什么)。providers 按 邮件服务商 类别划分活动的潜在客户,即 ESP 匹配所用的分组:gmail、outlook、other(包括已经检查但服务商仍未知的域名,ESP 匹配同样这样处理),以及 undetected(尚未读取服务商的潜在客户)。
{
"lead_counts": {
"total": 3140,
"queued": 1960,
"processing": 910,
"completed": 27,
"replied": 180,
"bounced": 22,
"failed": 3,
"paused": 12,
"undeliverable": 8,
"unsubscribed": 18
}
}在第一页(没有 cursor)中,响应包含 counts 对象,提供组织范围内的各筛选维度总数(不受请求筛选条件影响),适合用于浏览侧栏。后续页面省略此对象:
{
"counts": {
"total": 12480,
"subscribed": 11902,
"unsubscribed": 578,
"in_campaign": 3140,
"not_contacted": 9340,
"categories": [
{ "category_id": "6f1c0b1e-0c2a-4b3a-9c1e-2d3f4a5b6c7d", "count": 420 }
]
}
}客户开发
每位联系人都携带 outreach,汇总其在所有营销活动中的互动情况:向其发送了多少封邮件(sent),以及其中有多少封被 opened、clicked、replied 或 bounced,并提供各行为最近发生的时间(last_sent_at、last_opened_at、last_clicked_at、last_replied_at、last_bounced_at,首次发生前不出现)。这些计数与 联系人 360 视图 中 engagement 提供的相同。打开仅统计真人操作;邮件客户端自动获取邮件不会计入。
创建联系人
POST /contacts
创建一个或多个联系人。请求体为联系人 JSON 数组,也可以是单个联系人对象,后者按仅含一个元素的数组处理。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
请求体
联系人对象的 JSON 数组(至少一个,不超过单次请求上限;空数组返回 400),或单个联系人对象。无论哪种形式,响应均为数组。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 联系人邮箱地址。以小写形式存储;带显示名称的值(Dana Reyes <[email protected]>)会提取其中的地址,非地址内容返回 400。 |
first_name | string | 否 | 名。 |
last_name | string | 否 | 姓。 |
company | string | 否 | 公司名称。 |
phone | string | 否 | 电话号码。 |
campaigns | string[] | 否 | 要将联系人加入的营销活动 ID。 |
categories | string[] | 否 | 要添加到联系人的标签 ID。 |
segments | string[] | 否 | 通过手动包含覆盖,将联系人固定加入这些分群 ID,无论其是否满足分群条件。未知 ID 会在写入任何联系人之前以 400 拒绝;覆盖记录与联系人在同一事务中写入,因此响应成功始终意味着成员关系已存在。 |
custom_fields | object | 否 | 字符串键值形式的自定义字段。键可使用字母、数字、下划线、空格和连字符。 |
subscribed | boolean | 否 | 营销同意标志。省略时,新联系人默认订阅,现有联系人保持原状态。 |
verification_status | string | 否 | 您已有的地址验证结论,可使用 Warmbly 的状态词(valid、risky、invalid、unknown),或任何已知服务的状态词(ok、catch-all、do_not_mail、deliverable、ok_for_all 等)。作为导入结论保存,后台检查不会改动它。若值不属于任何已知服务,则以 unknown_verification_status 拒绝。 |
verification_provider | string | 否 | verification_status 使用的服务商状态词:zerobounce、millionverifier、cleanmylist、neverbounce、bouncer、kickbox、emailable、debounce、clearout、emaillistverify 或 warmbly。省略时根据值本身自动识别。未知名称以 unknown_verification_provider 拒绝。 |
已有地址按小写匹配并补充信息,而不会重复创建:提交的字段替换已存储的值,省略或提交空值的字段保持不变,custom_fields 则按键合并。清除某个值请使用 PATCH /contacts/:id。
[
{
"email": "[email protected]",
"first_name": "Lee",
"last_name": "Ng",
"company": "Globex",
"categories": ["6f1c0b1e-0c2a-4b3a-9c1e-2d3f4a5b6c7d"],
"custom_fields": { "title": "Head of Ops" }
}
]响应
以无封装的 JSON 数组返回创建的联系人(联系人结构与搜索相同)。
[
{
"id": "2c3d4e5f-6071-4b2c-9d3e-4f5a6b7c8d9e",
"first_name": "Lee",
"last_name": "Ng",
"email": "[email protected]",
"company": "Globex",
"phone": "",
"custom_fields": { "title": "Head of Ops" },
"subscribed": true,
"campaigns": [],
"categories": [{ "id": "6f1c...", "title": "VIP", "color": "#0ea5e9" }],
"verification_status": "unknown",
"mail_host": "",
"esp_provider": "",
"updated_at": "2026-06-11T10:00:00Z",
"created_at": "2026-06-11T10:00:00Z"
}
]为批量操作选择联系人
所有对联系人集合执行操作的端点(PATCH /contacts、DELETE /contacts、POST /contacts/verification、POST /contacts/research/batch、POST /segments/:id/members 和 POST /integrations/connections/:id/push)均通过以下两种方式之一指定集合。
按 ID。 使用原有结构:contacts 数组,最多包含 10,000 个 ID。
按筛选条件。 将 all 设为 true,并以 filters 提供与 POST /contacts/search 接受的相同请求体。服务端执行该搜索,并对所有匹配联系人应用操作,因此一次调用可覆盖远超一页的结果。exclude 可从最终集合中排除指定 ID,控制台即通过它处理全选后取消勾选的行。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
contacts | string[] | 必填,除非使用 all | 联系人 ID,最多 10,000 个(超出时返回 too_many_contacts)。 |
all | boolean | 否 | 通过 filters 而非 contacts 确定所选联系人。 |
filters | object | all 启用时必填 | 一个 联系人搜索 请求体。操作作用于所有匹配项。 |
exclude | string[] | 否 | 要从最终集合中排除的联系人 ID,最多 250,000 个(超出时返回 too_many_contacts)。仅在 all 启用时有效。 |
{
"all": true,
"filters": { "query": "acme", "campaign_ids": ["7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"] },
"exclude": ["1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d"]
}按筛选条件选中的联系人超过 250,000 位时,会以 selection_too_large 拒绝,而不会截断;请缩小范围后重试。没有匹配项时返回 400。POST /integrations/connections/:id/push 和 POST /contacts/research/batch 接受相同结构,也采用相同响应方式,但最终集合上限仍为 500:推送操作会在请求中为每位联系人调用一次 CRM,而每次调研运行都会消耗 AI 积分。
批量更新联系人
PATCH /contacts
对 选定的联系人集合 应用同一组修改:添加/移除营销活动和标签(add_categories、remove_categories)、设置自定义字段操作,以及切换订阅状态。
身份验证:权限范围 BULK_CONTACTS · 组织权限 manage_contacts
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
contacts | string[] | 必填,除非使用 all | 要编辑的联系人 ID(1 至 10,000 个)。 |
all, filters, exclude | — | 否 | 也可按筛选条件选择;参见 选择联系人。 |
add_campaigns | string[] | 否 | 要添加的营销活动 ID。 |
remove_campaigns | string[] | 否 | 要移除的营销活动 ID。 |
add_categories | string[] | 否 | 要添加的标签 ID。 |
remove_categories | string[] | 否 | 要移除的标签 ID。 |
fields | array | 否 | 自定义字段操作:{ "type", "key", "value" },其中 type 为 ADD、EDIT、DELETE 或 RENAME。 |
subscribe | boolean | 否 | 为列出的所有联系人设置订阅状态。 |
{
"contacts": ["1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d", "2c3d4e5f-6071-4b2c-9d3e-4f5a6b7c8d9e"],
"add_categories": ["6f1c0b1e-0c2a-4b3a-9c1e-2d3f4a5b6c7d"],
"subscribe": false,
"fields": [{ "type": "EDIT", "key": "title", "value": "Decision Maker" }]
}响应
以无封装的 JSON 数组返回更新后的联系人(联系人结构同上)。使用 all 选择联系人时,改为返回空数组:它可能选中远超适合序列化返回数量的联系人,因此请重新读取列表,而非依赖响应。
[
{
"id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"email": "[email protected]",
"subscribed": false,
"categories": [{ "id": "6f1c...", "title": "VIP", "color": "#0ea5e9" }],
"updated_at": "2026-06-11T10:05:00Z",
"created_at": "2026-05-01T09:30:00Z"
}
]批量删除联系人
DELETE /contacts
删除 选定的联系人集合。
身份验证:权限范围 BULK_CONTACTS · 组织权限 manage_contacts
请求体
联系人 ID 字符串的 JSON 数组(1 至 1000 个;空数组返回 400)。
["1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d", "2c3d4e5f-6071-4b2c-9d3e-4f5a6b7c8d9e"]同一位置也接受 选择对象,以删除所有符合筛选条件的联系人。
{ "all": true, "filters": { "subscribed": false } }响应
204 No Content.
导出联系人
POST /contacts/export
将联系人导出为 CSV、XLSX 或 JSON。响应是文件本身,而非 JSON 响应对象。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
format | string | 是 | csv、xlsx 或 json。 |
scope | string | 是 | all、filtered 或 selected。 |
contact_ids | string[] | 否 | scope 为 selected 时的联系人 ID。 |
filters | object | 否 | scope 为 filtered 时的联系人搜索筛选请求体。 |
fields | string[] | 否 | 按显示顺序排列的列标识符(内置字段如 email、first_name,或 custom:<key>)。为空时使用默认列。lead_status、lead_opened、lead_clicked 和 lead_replied 是联系人在 filters.campaign_ids 指定的单个营销活动中的互动数据;筛选条件未恰好指定一个营销活动时,这些列为空。 |
filename | string | 否 | 不含扩展名的文件名。服务端会进行清理;为空时回退到 contacts-<YYYY-MM-DD>。 |
scope 设为 selected 时,filters 可选,并在 contact_ids 的基础上应用;在其中指定营销活动,即可为所选行填充 lead_* 列。
{
"format": "csv",
"scope": "filtered",
"filters": { "subscribed": true },
"fields": ["email", "first_name", "last_name", "company", "custom:title"],
"filename": "subscribed-contacts"
}响应
200 OK,文件以附件形式返回。相关响应头如下:
| 请求头 | 说明 |
|---|---|
Content-Type | 导出文件的 MIME 类型(CSV、XLSX 或 JSON)。 |
Content-Disposition | attachment; filename="...". |
X-Total-Rows | 写入的行数。 |
导出最多 50,000 行。更大的集合应通过筛选条件或选择范围拆分。
预览导入
POST /contacts/import/preview
上传 CSV 或 XLSX 文件,返回检测到的列及少量样本,供客户端在正式提交前建立列映射。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
使用 multipart/form-data,通过 file 表单字段发送文件。上传最大 50 MB。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
file | form-data | file | 上传的 CSV/XLSX 文件。 |
响应
{
"filename": "leads.csv",
"format": "csv",
"total_rows": 1243,
"columns": ["Email", "First", "Last", "Company", "industry", "Notes"],
"has_header": true,
"sample_rows": [
["[email protected]", "Dana", "Reyes", "Acme", "Real Estate", "Met at SaaStr"]
],
"suggested_mapping": [
{ "index": 0, "target": "email" },
{ "index": 1, "target": "first_name" },
{ "index": 2, "target": "last_name" },
{ "index": 3, "target": "company" },
{ "index": 4, "target": "custom", "custom_key": "Industry" },
{ "index": 5, "target": "ignore" }
]
}sample_rows 最多包含 20 行。suggested_mapping 是客户端可覆盖的默认映射。若列标题与工作区已有自定义字段名称匹配(忽略大小写、空格、下划线、连字符和点),则建议映射为 custom,并在 custom_key 中使用该字段已保存的拼写(上例中,industry 映射到现有的 Industry)。每个字段最多为一列提供映射建议,优先匹配标准字段;无法匹配的列标题设为 ignore。若没有列标题明确指向邮箱字段,则无论标题为何,包含邮箱地址的列都会被建议为 email。GET /contacts/custom-fields(端点)列出可供映射的目标字段。
在配置了 TypeSafe 的实例中,此后仍为 ignore 的列会根据标题含义确定映射,inferred_columns 列出其索引,便于客户端提示再次确认。仅发送列标题、每列的数据类型及工作区字段名称,绝不发送任何单元格值。未推断任何列时省略此字段。
提交导入
POST /contacts/import/commit
重新上传文件并附带映射及去重选项,执行导入后返回逐行结果。
身份验证:权限范围 BULK_CONTACTS · 组织权限 manage_contacts
使用 multipart/form-data,提供 file 字段,以及以字符串形式包含下方 JSON 的 options 字段。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
file | form-data | file | 上传的 CSV/XLSX 文件(最大 50 MB)。 |
options | form-data | string | 以 JSON 编码的提交选项(见下方)。 |
options 字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
mapping | array | 是 | 列映射:{ "index", "target", "custom_key", "verification_provider" }。target 为 ignore、email、first_name、last_name、company、phone、subscribed、categories(标签名称)、verification_status,或通过 custom_key 指定名称的 custom。旧写法 custom:<key> 仍然支持。必须恰好有一列映射到 email。verification_status 列按 verification_provider 指定的服务商状态词读取(参见 创建联系人);省略时逐值识别。无法识别的单元格会使该联系人保持未验证,而不会让整行失败。若列标题或值看起来像其他服务的验证结果,预览会自动建议此映射目标。 |
dedup | string | 是 | 邮箱与现有联系人匹配的行采用 skip、update 或 create_duplicate。 |
has_header | boolean | 是 | 第一行是否为表头。 |
category_ids | string[] | 否 | 要添加到导入联系人的标签 ID。 |
campaign_ids | string[] | 否 | 要将导入联系人加入的营销活动。 |
segment_ids | string[] | 否 | 通过手动包含覆盖,将导入联系人固定加入这些分群。同样适用于新导入、已更新及虽跳过但已关联的联系人。ID 不是有效 UUID,或不对应组织内的分群时,会在写入任何行之前以 400 拒绝。 |
subscribed_default | boolean | 否 | 未映射 subscribed 列时,新联系人的订阅状态。默认为 true。 |
{
"mapping": [
{ "index": 0, "target": "email" },
{ "index": 1, "target": "first_name" },
{ "index": 3, "target": "custom", "custom_key": "Company Mobile" }
],
"dedup": "update",
"has_header": true,
"category_ids": ["6f1c0b1e-0c2a-4b3a-9c1e-2d3f4a5b6c7d"],
"subscribed_default": true
}响应
{
"total": 1243,
"imported": 1180,
"updated": 41,
"skipped": 18,
"failed": 4,
"started_at": "2026-06-11T10:10:00Z",
"ended_at": "2026-06-11T10:10:07Z",
"errors": [
{ "line": 57, "email": "not-an-email", "reason": "invalid email" }
],
"segments_pinned": true,
"quality": {
"malformed": 4,
"disposable": 0,
"role": 62,
"bad_share_pct": 0.3,
"flagged": false
}
}quality 描述文件中的地址质量:malformed 完全不是有效地址,disposable 属于已知临时邮箱域名,role 则统计 info@ 等共享收件箱。bad_share_pct 是格式错误和临时地址之和占文件的百分比;角色地址有意不计入,因为向共享收件箱发信是一种选择,而非地址缺陷。对于至少 20 行的文件,该比例超过 25% 时会设置 flagged,并提供一句 summary。这只是提示:即使导入被标记,仍会保存所有可解析的行。质量差到足以产生影响的名单,会在营销活动启动时遭拒。
导入最多 50,000 行。errors 最多包含前 1,000 条;超过此数量时,errors_truncated 为 true,真实总数以计数器而非列表为准。每行恰好归入 imported、updated、skipped 或 failed 之一,因此这四项之和始终等于 total。
errors 也包含未失败行的备注,因此其中有记录并不一定意味着某行丢失。针对整个导入而非单行的备注带有 line: 0,并排在最前,以免文件中的大量无效地址使其被挤出截断后的列表。仅设置了 segment_ids 时才出现 segments_pinned:所有成员关系均写入成功时为 true,有任一写入失败时为 false,原因在备注中提供。
自定义字段名称可使用字母、数字、下划线、空格和连字符(Company Mobile、first-name、plan_tier)。其他字符会导致整个请求返回 400,且在写入任何行之前拒绝;映射未指定 email 列,或 custom 列没有 custom_key,也同样处理。逐行 errors 仅用于数据本身的问题。
现有联系人在整个组织范围内匹配,而不限于调用者自己的联系人,因此团队成员添加的地址也会像其他地址一样被 skipped 或 updated。若某行地址已被调用者作为联系人保存在另一组织中,该行会失败并说明原因,且不会更改另一组织的联系人。无法保存的行单独失败;同批已写入的其他行不受影响。
后台导入
上述提交在请求内执行,适合小文件和脚本。对于大文件,后台导入只需上传一次,先报告整个文件将产生的操作,再在服务端分块执行,因此连接中断也不影响运行,且可从任何位置跟踪。其映射及选项与提交操作相同。
POST /contacts/imports上传文件,并返回带有preview的草稿。POST /contacts/imports/:id/analyze报告按指定映射导入将执行哪些操作。可选,并可安全重复调用。POST /contacts/imports/:id/start将导入加入队列。GET /contacts/imports/:id报告进度,直到status为completed、failed或cancelled。进度变化时,控制台 socket 也会收到CONTACT_IMPORT_PROGRESS;参见 实时通信。
草稿保留预览及最后保存的映射和选项,以便客户端恢复操作。从未启动的草稿在 24 小时后删除;已完成的导入及其存储行在 30 天后删除。一个工作区可同时持有 10 个草稿和运行中的导入;继续创建会返回 409。
创建导入任务
POST /contacts/imports
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
使用 multipart/form-data,通过 file 字段发送文件(最大 50 MB、50,000 行)。返回 201 Created,包含状态为 draft 的导入,以及结构与 上方预览 相同的 preview,另加:
| 字段 | 类型 | 说明 |
|---|---|---|
preview.column_stats | array | 每列一项,对全部行进行统计:filled(非空单元格数)、distinct(不同值的数量,最多统计到 1,000)及 samples(最多三个不同值)。 |
preview.mapping_source | string | 若此组织先前曾以完全相同的列标题启动导入,则为 saved,此时 suggested_mapping 是当时确认的映射;否则为 suggested。 |
保存草稿
PATCH /contacts/imports/:id
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
保存草稿编辑中的映射和选项(请求体与 启动 相同),但不进行检查,使客户端可自动保存并在之后恢复;GET 会在 options 中返回这些设置,同时返回草稿的 preview。启动导入时才检查这些设置。返回导入对象;导入已启动时返回 409。再次保存相同请求体没有副作用。
分析导入内容
POST /contacts/imports/:id/analyze
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
按指定映射读取草稿,不写入任何内容。导入已启动时返回 409。
{ "mapping": [{ "index": 0, "target": "email" }], "has_header": true }{
"rows": 1243,
"new": 1102,
"existing": 118,
"duplicates_in_file": 12,
"invalid": 11,
"conflicts": 0,
"invalid_samples": [{ "line": 57, "email": "not-an-email", "reason": "missing or invalid email" }],
"quality": { "malformed": 11, "disposable": 0, "role": 62, "bad_share_pct": 0.9, "flagged": false }
}每行恰好归入 new、existing、duplicates_in_file、invalid 或 conflicts(调用者已在另一组织中保存为联系人的地址)之一。invalid_samples 列出最多 25 个无效行和冲突行。problem 出现时,说明整个启动操作将遭拒的原因:套餐联系人上限,或不同标签数量过多。
启动导入
POST /contacts/imports/:id/start
身份验证:权限范围 BULK_CONTACTS · 组织权限 manage_contacts
请求体是提交操作的 options 对象,格式为 JSON。返回状态为 queued 的导入。对已经启动的导入再次启动,会原样返回,不作更改,因此重试请求绝不会让文件运行两次。已取消的导入返回 409。has_header 为 true 时,会记住映射,供下一个列标题相同的文件使用。
获取导入任务
GET /contacts/imports/:id
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
{
"id": "8d6f7f2a-5f7e-4a55-9d0b-0c3f1d2e4b6a",
"filename": "leads.csv",
"format": "csv",
"status": "running",
"has_header": true,
"columns": ["Email", "First", "Company"],
"total": 1243,
"processed": 500,
"imported": 452,
"updated": 30,
"skipped": 16,
"failed": 2,
"notes": [],
"created_at": "2026-06-11T10:09:40Z",
"updated_at": "2026-06-11T10:10:02Z",
"started_at": "2026-06-11T10:10:00Z"
}processed 是迄今已处理完的行数,始终等于 imported + updated + skipped + failed。完成后设置 finished_at;quality 和 segments_pinned 的含义与提交操作相同;notes 提供整个导入的相关消息;error 说明 failed 导入停止的原因;failures 列出前 200 个失败行,包含 line、email、values(上传时的原始行)和 reason。
列出导入任务
GET /contacts/imports
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
返回组织的导入,最新在前,采用 data 加 pagination 的结构。limit 默认为 50,最多 100;将 pagination.next_cursor 作为 cursor 传回以获取下一页。游标或条数无效时返回 400。
取消导入
POST /contacts/imports/:id/cancel
身份验证:权限范围 BULK_CONTACTS · 组织权限 manage_contacts
停止处于草稿、排队或运行状态的导入,并返回该导入。已导入的行保持不变。取消已完成的导入时,原样返回,不作更改。
下载导入失败的行
GET /contacts/imports/:id/failed.csv
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
按文件原有列标题返回每个失败行的原始上传内容,并在末尾添加 Line 和 Error 列,便于修正后重新导入。电子表格可能解析为公式的单元格会加上 ' 前缀。
邮箱验证概览
GET /contacts/verification
报告此工作区使用哪个验证器检查地址,以及各验证结论的联系人数。权限范围 READ_CONTACTS · 组织权限 view_contacts。
响应
{
"provider": "millionverifier",
"connection_id": "9a1b...",
"credits": 48210,
"builtin_ready": true,
"counts": { "valid": 11240, "risky": 380, "invalid": 512, "unknown": 1890, "pending": 120 }
}provider 为 builtin、millionverifier 或 cleanmylist。credits 是已连接服务的剩余额度(可获取时提供,CleanMyList 省略);若服务已连接但不可用(密钥遭拒、额度不足),则改为设置 provider_error,并使用内置检查。builtin_ready 表示此实例是否能运行内置邮箱探测。pending 统计尚未经任何验证器检查的联系人,加上已排队等待重新检查的联系人。
验证联系人邮箱或覆盖验证结果
POST /contacts/verification
为列出的联系人排队执行新检查,或记录手动验证结论。权限范围 BULK_CONTACTS · 组织权限 manage_contacts。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | string | 是 | verify 将重新检查排在后台积压任务之前,无论联系人当前结论、结论时间或依据为何;新结论产生之前,旧结论继续有效,各联系人在结果产生时分别更新。mark_deliverable 记录 valid;mark_undeliverable 记录 invalid,两者都会完成仍在等待的重新检查。除非明确请求,否则不会重新检查手动结论。其他值以 invalid_action 拒绝。 |
contacts | string[] | 否 | 联系人 ID,数量不超过单次批量请求上限。 |
all, filters, exclude | — | 否 | 也可按筛选条件选择;参见 选择联系人。 |
campaign_id | string | 否 | 可替代所选集合,或与其同时使用:此营销活动中所有因验证而被拒绝的潜在客户。 |
必须至少选择一位联系人(否则返回 no_contacts)。将潜在客户标记为可投递,会恢复工作区内所有因验证而暂停的营销活动。
响应
{
"affected": 512,
"action": "verify",
"queued": true,
"verifier": "millionverifier",
"verifier_label": "MillionVerifier"
}对于 verify,verifier 指明执行检查的验证器(builtin、millionverifier 或 cleanmylist),verifier_label 为其显示名称。若验证器已连接但当前不可用,verifier 为 builtin,verifier_error 说明原因(与概览中 provider_error 的说明相同)。
按邮箱地址查找联系人
GET /contacts/lookup
将发件地址解析为组织内的联系人。无匹配项时返回 200 和 {"contact": null},因此未知发件人可显示正常的空状态,而非 404。接受带显示名称的地址(Name <[email protected]>),并提取其中的实际地址。
传入 thread_id 可按统一收件箱的方式解析回复。先尝试按地址匹配;若不是联系人,则使用会话中的营销活动发送记录,因此来自别名或转发地址的回复可以解析到该活动最初发信的潜在客户。match 表示匹配方式:email 或 thread。contact 为 null 时省略此字段。仅在 API 密钥邮箱允许列表许可的邮箱中读取会话;account_id 不在其中时返回 403。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts。提供 thread_id 时还需 权限范围 READ_UNIBOX · 组织权限 access_unibox。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
email | query | string | 要解析的邮箱地址。未提供 thread_id 时必填。 |
thread_id | query | string | 可选。统一收件箱会话 ID(统一收件箱邮件上的 thread_id)。 |
account_id | query | UUID | 可选。保存会话的邮箱,将会话匹配限定到该邮箱。ID 格式无效时返回 400。 |
响应
{
"contact": {
"id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"first_name": "Dana",
"last_name": "Reyes",
"email": "[email protected]",
"company": "Acme",
"subscribed": true,
"campaigns": [],
"categories": [],
"verification_status": "valid",
"mail_host": "gmail",
"esp_provider": "gmail",
"updated_at": "2026-06-10T12:00:00Z",
"created_at": "2026-05-01T09:30:00Z"
},
"match": "email"
}获取联系人
GET /contacts/:id
返回包含完整关联数据的联系人 360 载荷:联系人、互动摘要、存在时的禁止发送状态,以及解释验证结论的 verification 对象:status、confidence、reasons(说明句,最有力的原因在前)、decisive(状态由真实邮件而非检查结果决定时为 true)、source 和 provider(产生最近结论的来源,与联系人字段相同),以及 provider_label(验证器显示名称,如 MillionVerifier)、check_status(检查本身的结果;若真实邮件得出了不同结论,此值与 status 不同)、checked_at、requested_at(重新检查等待期间设置),以及 evidence(评分所依据的观测记录,最新在前)。每条观测为 { "kind", "detail", "observed_at" },其 kind 为 delivered、opened、clicked、replied、auto_replied、bounced_recipient、bounced_other 之一。互动及禁止发送计数按组织隔离,仅在选择了组织时返回。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
响应
{
"id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"first_name": "Dana",
"last_name": "Reyes",
"email": "[email protected]",
"company": "Acme",
"phone": "+15551234567",
"custom_fields": { "title": "VP Sales" },
"subscribed": true,
"campaigns": [{ "id": "c1...", "name": "Q3 Outbound" }],
"categories": [{ "id": "6f1c...", "title": "VIP", "color": "#0ea5e9" }],
"verification_status": "valid",
"mail_host": "gmail",
"esp_provider": "gmail",
"updated_at": "2026-06-10T12:00:00Z",
"created_at": "2026-05-01T09:30:00Z",
"source": "import",
"source_detail": "q3-leads.csv",
"first_seen_at": "2026-05-01T09:30:00Z",
"engagement": {
"total_sent": 4,
"total_opened": 3,
"total_clicked": 1,
"total_replied": 1,
"total_bounced": 0,
"total_complained": 0,
"last_sent_at": "2026-06-09T08:00:00Z",
"last_opened_at": "2026-06-09T08:14:00Z",
"last_replied_at": "2026-06-09T11:02:00Z",
"reads_on": [
{ "client": "Apple Mail", "client_type": "app", "device_type": "mobile", "os": "iOS", "opens": 2, "last_opened_at": "2026-06-09T08:14:00Z" },
{ "client": "Gmail", "device_hidden": true, "opens": 1, "last_opened_at": "2026-06-02T16:40:00Z" }
]
},
"suppression": null
}engagement.total_opened 和 engagement.last_opened_at 仅统计真人打开;与营销活动分析一样,邮件客户端或安全网关自动获取邮件不计入。真人点击也算作打开;自动点击不会增加任一字段的计数。
engagement.reads_on 表示联系人阅读邮件的方式:真人打开邮件所用的各客户端和设备,按最近使用顺序排列,最多四项,并提供 opens 数量及最近的 last_opened_at。各字段含义与时间线事件的 origin 相同(参见 时间线)。在有打开事件提供可识别信息之前,此字段省略。
联系人被禁止发送时,suppression 是一个对象:{ "id", "kind", "value", "reason", "source", "expires_at", "created_at" }。联系人自身地址在名单上时,kind 为 email;整个域名在名单上时为 domain。value 为匹配的条目,source 为 bounce、complaint、unsubscribe、manual 或 import。id 是禁止发送记录的 ID,可通过 DELETE /suppressions/:id 解除;参见 邮件送达能力与运维。
更新联系人
PATCH /contacts/:id
部分更新单个联系人。只更改请求中出现的字段。营销活动列表和标签列表(categories)可整体设置,也可通过差量添加/移除来调整。
email 替换联系人的地址。以小写形式存储;带显示名称的值(Dana Reyes <[email protected]>)会提取其中的地址,非地址内容返回 400。地址必须尚未被占用:若另一联系人已使用该地址,则返回 409,其中 code 为 contact_email_taken,而不会合并两位联系人。地址更改后,联系人的验证结论回退到 unknown,支撑结论的投递证据被清除,mail_host 和 esp_provider 也被清空,因为它们均属于旧邮箱;下一轮验证会检查新地址,并从新域名重新读取服务商。已经发送的步骤仍保留发往旧地址的历史记录。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
first_name | string | 否 | 名。 |
last_name | string | 否 | 姓。 |
email | string | 否 | 新邮箱地址。统一为小写;必须是有效地址,且未被其他联系人使用。 |
company | string | 否 | 公司。 |
phone | string | 否 | 电话。 |
custom_fields | object | 否 | 替换自定义字段映射。 |
subscribed | boolean | 否 | 订阅状态。 |
campaigns | string[] | 否 | 设置完整的营销活动成员关系(nil 表示保持不变)。 |
categories | string[] | 否 | 设置完整的标签 ID 列表(nil 表示保持不变)。 |
add_categories | string[] | 否 | 差量添加(设置了 categories 时忽略)。 |
remove_categories | string[] | 否 | 差量移除(设置了 categories 时忽略)。 |
{
"company": "Acme Corp",
"subscribed": true,
"add_categories": ["6f1c0b1e-0c2a-4b3a-9c1e-2d3f4a5b6c7d"]
}响应
以无封装对象返回更新后的联系人(联系人结构与搜索相同)。
{
"id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"first_name": "Dana",
"last_name": "Reyes",
"email": "[email protected]",
"company": "Acme Corp",
"subscribed": true,
"campaigns": [],
"categories": [{ "id": "6f1c...", "title": "VIP", "color": "#0ea5e9" }],
"updated_at": "2026-06-11T10:20:00Z",
"created_at": "2026-05-01T09:30:00Z"
}删除联系人
DELETE /contacts/:id
删除单个联系人。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
响应
204 No Content.
列出发给联系人的邮件
GET /contacts/:id/emails
为每封已向联系人发送(或尝试发送)的邮件返回一行,最新在前,包含发件人、营销活动、邮件序列和互动时间戳。分页以最后一行的 (created_at, task_id) 为依据。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
limit | query | integer | 每页数量,范围 1 至 200,默认 50。 |
before_at | query | string (RFC 3339 nano) | 上一页最后一行的 created_at。 |
before_id | query | UUID | 上一页最后一行的 task_id。 |
必须同时提供 before_at 和 before_id,否则忽略游标并返回第一页。
响应
返回 data 数组及 pagination 分页结构。
{
"data": [
{
"task_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"status": "sent",
"message_id": "<[email protected]>",
"subject": "Quick question",
"sent_at": "2026-06-09T08:00:00Z",
"email_account_id": "e1...",
"email_account_email": "[email protected]",
"email_account_name": "Rep One",
"campaign_id": "c1...",
"campaign_name": "Q3 Outbound",
"step_id": "s1...",
"step_name": "Email 1",
"opened_at": "2026-06-09T08:14:00Z",
"replied_at": "2026-06-09T11:02:00Z"
}
],
"pagination": {
"total": 4,
"next_cursor": null,
"has_more": false
}
}email_account_id、email_account_email 和 email_account_name 表示邮件实际发出的邮箱,这正是跨多个邮箱轮换的营销活动所需的信息:该行指明发送此封邮件的邮箱,而不是营销活动邮箱池或发送前一步邮件的邮箱。
opened_at 表示真人打开,与 engagement 和营销活动分析中的含义相同。邮件客户端预取或安全网关获取邮件则报告为 machine_opened_at,因此仅有机器访问时,记录绝不会显示为收件人已读。两者最多出现一个。
列出联系人的时间线记录
GET /contacts/:id/timeline
返回所选组织内某位联系人的合并活动动态:发送、打开、点击(每个链接一项,并标明链接)、回复、退信、送达能力及禁止发送事件、备注、会议预约和生命周期事件(联系人创建及其首次接触来源,以及每次加入/离开营销活动、添加/移除标签)。所有拥有联系人查看权限的成员都会看到相同时间线,不论联系人或其营销活动由谁创建。未选择组织的请求返回 400,联系人不属于所选组织时返回 404。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
limit | query | integer | 每页条数,1 至 200(默认为 50)。其他值返回 400。 |
cursor | query | string | 来自 pagination.next_cursor 的不透明分页游标。游标格式无效时返回 400。 |
before | query | string (RFC 3339 nano) | 已弃用。返回严格早于此时间戳的事件,可能跳过与分页边界处于同一时刻的事件;请使用 cursor。值不是 RFC 3339 时间戳时返回 400。设置了 cursor 时忽略。 |
响应
返回 data 数组和标准 pagination 分页结构。将 pagination.next_cursor 作为 cursor 传回以继续分页,直到 has_more 为 false。游标是本页最后一个事件的精确位置(时间、来源和记录),因此具有相同时间戳的事件不会在跨页时被跳过或重复;发送、打开及备注落在同一秒时就常见这种情况。顶层 has_more 与 pagination.has_more 相同,为兼容在引入分页封装前编写的客户端而保留。pagination.total 始终为 null:动态由多个表合并而成,不统计总数。
{
"data": [
{
"type": "email_clicked",
"at": "2026-06-09T11:42:00Z",
"email_account_id": "e1...",
"email_account_email": "[email protected]",
"campaign_id": "c1...",
"campaign_name": "Q3 Outbound",
"step_id": "s1...",
"step_name": "Intro",
"subject": "Quick question",
"machine": false,
"link": {
"id": "7c0f...",
"url": "https://yourco.com/pricing?utm_source=warmbly&utm_medium=email&utm_campaign=q3_outbound&utm_content=pricing",
"label": "Pricing",
"utm_source": "warmbly",
"utm_medium": "email",
"utm_campaign": "q3_outbound",
"utm_content": "pricing",
"user_agent": "Mozilla/5.0 ..."
}
},
{
"type": "email_replied",
"at": "2026-06-09T11:02:00Z",
"email_account_id": "e1...",
"email_account_email": "[email protected]",
"campaign_id": "c1...",
"campaign_name": "Q3 Outbound",
"task_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"subject": "Quick question"
},
{
"type": "note",
"at": "2026-06-08T16:30:00Z",
"content": "Met at the conference, wants a follow-up in July.",
"user_id": "u1..."
},
{
"type": "campaign_added",
"at": "2026-05-01T09:31:00Z",
"campaign_id": "c1...",
"campaign_name": "Q3 Outbound",
"user_id": "u1..."
},
{
"type": "contact_created",
"at": "2026-05-01T09:30:00Z",
"source": "import",
"source_detail": "q3-leads.csv",
"user_id": "u1..."
}
],
"has_more": false,
"pagination": { "total": null, "next_cursor": null, "has_more": false }
}type 为以下值之一:email_sent、email_opened、email_clicked、email_replied、email_bounced、reply_received、deliverability、suppressed、note、meeting_booked、meeting_rescheduled、meeting_canceled、contact_created、campaign_added、campaign_removed、category_added、category_removed、form_submitted 或 page_hit。与事件类型无关的字段会省略。
email_opened 和 email_clicked 携带 machine:若由自动抓取程序而非真人触发,则为 true(例如邮件隐私代理、发生在实例自动互动判定窗口内的获取——该窗口从步骤被分发到工作节点时开始——或数秒内访问多个链接)。逐链接的 email_clicked 事件携带 machine_reason(prefetch、instant、scanner 或 burst),逐事件的 email_opened 记录也携带此字段(prefetch、instant 或 scanner);仅根据潜在客户记录汇总的打开则只带该标志。两类事件在有日志记录时都携带 origin 对象:用户代理可识别邮件客户端时包含 client(Gmail、Apple Mail、Outlook、Yahoo Mail、Thunderbird 等);client_type 对已安装的邮件应用为 app,对浏览器为 webmail(点击事件中,仅当邮件应用自身发出请求时才为 app,绝不会为 webmail);由邮箱服务商图片代理获取邮件时,包含 device_hidden: true(Gmail、Yahoo Mail、HEY、Fastmail、Seznam,以及用户代理仅为 Mozilla/5.0 的 Apple Mail 隐私保护);还包括 device_type(desktop、mobile 或 tablet)、os、browser、browser_version;消费者进程能够解析时还包括 country_code、region、city。设备被隐藏时,会省略设备、操作系统和浏览器,因为这些信息描述的是代理;位置也会省略,但 Apple 中继保留的国家和地区除外。Mac 上的 Mail 和新版 Outlook for Windows 都会发送简化的 WebKit 用户代理,这种情况报告为 client: "Apple Mail or Outlook"、client_type: "app",不含设备。点击事件中的 browser 和 device_type 是链接打开时的浏览器和设备类型;只有邮件应用自身发出请求时才设置 client。每次打开都单独记录,因此从两个设备打开邮件的联系人会有两行。自动点击保留在动态中供查阅,但绝不将步骤计为已点击。email_clicked 事件携带 link,其中包含链接的 id、url、label(锚文本)、URL 中的 utm_* 参数,以及 user_agent;邮件中的每个链接独立跟踪,因此每个被点击的链接都有自己的事件。在引入逐链接归因之前记录的点击没有 link。
form_submitted 事件携带 form_id 和 form_name。page_hit 事件表示您自己网站上的一次页面浏览,其浏览器通过邮件链接票据与联系人关联(参见 网站跟踪);subject 为页面标题,无标题时则为路径;page_hit 包含完整浏览信息:url、path、title、referrer、referrer_domain、landing(会话的首次浏览)、utm_* 参数、device_type、os、browser、browser_version、device_brand、language、timezone、screen_width、screen_height,以及已知时的 country_code、region、city。
生命周期事件保留变更对象在当时的名称(campaign_name;标签则为 category_id 加 category_title),因此后续重命名或删除不会改写历史。contact_created 事件携带 source(manual、campaign、import、sheet_sync、api、form、automation、ai_assistant,或对早于归因功能创建的联系人使用 unknown)及 source_detail(文件、营销活动、表格、表单、自动化流程或 API 密钥的名称)。相同信息也保存在联系人自身的 source、source_detail 和 first_seen_at 上,创建后不会改变。
获取联系人在营销活动中的状态
GET /contacts/:id/campaigns
对联系人作为潜在客户参与的每个营销活动,返回流程及其在各步骤的进度、派生潜在客户状态、最近发生的事件,以及接下来会发生什么。要求已选择组织。
下一操作在读取时由调度器推导,经过与实际发送相同的约束(步骤等待时间、营销活动开始日期及发送时段、邮箱上限和发送间隔、新潜在客户限制);营销活动是一个持续自我调度的任务,因此不为每位联系人单独存储计划。next.state 表示时间的确定程度:due 携带 scheduled_at,它是营销活动任务链下一次处理队列的时间,并非为此联系人预留的时段(队列中靠前的潜在客户仍可能将此步骤推迟到后续轮次;任务链正在重新建立时省略该字段);waiting 携带步骤不可早于的时间 not_before 及一个 constraint;paused 和 blocked 仅携带原因。这两个时间均来自约束,而不是发送间隔,因此在状态不变时重复调用会返回相同值。联系人的流程结束后,next 省略,ended_reason 说明原因。replied 潜在客户不会仅因回复而结束流程:只要营销活动仍对其进行路由(关闭了回复后停止,或存在回复分支),next 就会保留。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
响应
{
"data": [
{
"campaign_id": "c1...",
"campaign_name": "Q3 Outbound",
"campaign_status": "active",
"lead_status": "active",
"sender_id": "m1...",
"sender_email": "[email protected]",
"steps": [
{
"id": "s1...",
"label": "Email 1",
"kind": "email",
"position": 0,
"subject": "Quick question",
"sent_at": "2026-06-09T08:00:00Z",
"opened_at": "2026-06-09T08:14:00Z"
},
{ "id": "s2...", "label": "Email 2", "kind": "email", "position": 1, "subject": "Following up" }
],
"completed_steps": 1,
"total_steps": 2,
"current_step": { "id": "s1...", "label": "Email 1", "kind": "email", "position": 0, "subject": "Quick question", "sent_at": "2026-06-09T08:00:00Z", "opened_at": "2026-06-09T08:14:00Z" },
"last_action": "Opened",
"last_action_at": "2026-06-09T08:14:00Z",
"next": {
"step_id": "s2...",
"step_label": "Email 2",
"kind": "email",
"subject": "Following up",
"state": "waiting",
"not_before": "2026-06-12T08:00:00Z",
"constraint": "Waiting 3 days after Email 1"
}
}
]
}sender_id 和 sender_email 表示此潜在客户整个邮件序列使用的发件邮箱。首封邮件发出时由轮换机制选定,所有跟进邮件都保留该邮箱,因此联系人始终只收到来自一个地址的邮件;首封邮件发送前两个字段均省略。只有该邮箱无法再为营销活动发送时,它们才会更改。
lead_status 使用与营销活动潜在客户视图相同的值:pending、active、completed、replied、bounced、failed、paused、unsubscribed 或 undeliverable。暂停中的潜在客户还携带 hold 对象(since、until、reason、source),并保留 next 操作,以暂停作为等待原因;参见 暂停潜在客户。cc 列出此营销活动每封发给该联系人的邮件中都会抄送的联系人,无抄送时为空;参见 获取潜在客户的抄送联系人。各步骤包含适用的 sent_at、opened_at、clicked_at、replied_at、bounced_at、failed_at,以及 attempts 和 in_flight(已为工作节点预留,结果尚未返回)。opened_at 表示真人打开,与潜在客户视图一致:被邮件客户端预取或安全网关扫描的步骤不会携带 opened_at。分支条件尚未确定时,next.step_id 省略,next.step_label 说明该步骤取决于联系人的响应。
列出联系人的活动记录
GET /contacts/:id/activities
返回联系人的结构化 CRM 活动日志(记录在 CRM 活动表中的备注、商机、任务、营销活动和互动事件)。要求已选择组织。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
limit | query | integer | 每页数量,范围 1 至 100,默认 50。 |
cursor | query | UUID | 上一页 pagination.next_cursor 中的不透明游标。 |
响应
{
"data": [
{
"id": "ac1...",
"contact_id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"organization_id": "org1...",
"user_id": "u1...",
"activity_type": "note_added",
"metadata": { "note_id": "n1..." },
"created_at": "2026-06-08T16:30:00Z"
}
],
"pagination": {
"total": 12,
"next_cursor": "ac0...",
"has_more": true
}
}activity_type 为封闭枚举,包括 email_sent、email_opened、email_clicked、email_replied、email_bounced、note_added、note_updated、deal_created、deal_stage_changed、deal_won、deal_lost、task_created、task_completed、contact_created、contact_updated、campaign_added、campaign_removed、category_added 和 category_removed。
列出联系人备注
GET /contacts/:id/notes
返回附加到联系人的 CRM 备注,最新在前。要求已选择组织。
身份验证:权限范围 READ_CONTACTS · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
limit | query | integer | 每页数量,范围 1 至 100,默认 50。 |
cursor | query | UUID | 上一页 pagination.next_cursor 中的不透明游标。 |
响应
{
"data": [
{
"id": "n1b2c3d4-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"contact_id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"organization_id": "org1...",
"user_id": "u1...",
"content": "Met at the conference, wants a follow-up in July.",
"created_at": "2026-06-08T16:30:00Z",
"updated_at": "2026-06-08T16:30:00Z",
"user": { "id": "u1...", "name": "Sam Rep" }
}
],
"pagination": {
"total": 3,
"next_cursor": null,
"has_more": false
}
}创建联系人备注
POST /contacts/:id/notes
向联系人添加备注。要求已选择组织。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
content | string | 是 | 备注正文(1 至 10,000 个字符)。 |
{ "content": "Sent the proposal, following up Monday." }响应
201 Created,包含创建的备注(结构与列表中的备注相同)。
{
"id": "n2c3d4e5-6071-4b2c-9d3e-4f5a6b7c8d9e",
"contact_id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"organization_id": "org1...",
"user_id": "u1...",
"content": "Sent the proposal, following up Monday.",
"created_at": "2026-06-11T10:30:00Z",
"updated_at": "2026-06-11T10:30:00Z"
}更新联系人备注
PATCH /contacts/:id/notes/:noteId
编辑备注内容。要求已选择组织。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
noteId | path | UUID | 备注 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
content | string | 否 | 新的备注正文。 |
{ "content": "Sent the proposal, following up Tuesday." }响应
返回更新后的备注。
{
"id": "n2c3d4e5-6071-4b2c-9d3e-4f5a6b7c8d9e",
"contact_id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"organization_id": "org1...",
"user_id": "u1...",
"content": "Sent the proposal, following up Tuesday.",
"created_at": "2026-06-11T10:30:00Z",
"updated_at": "2026-06-11T10:35:00Z"
}删除联系人备注
DELETE /contacts/:id/notes/:noteId
删除备注。要求已选择组织。
身份验证:权限范围 WRITE_CONTACTS · 组织权限 manage_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
noteId | path | UUID | 备注 ID。 |
响应
204 No Content.
列出联系人的商机
GET /contacts/:id/deals
以无封装的 JSON 数组返回与联系人关联的 CRM 商机。
身份验证:权限范围 READ_CRM · 组织权限 view_contacts
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | UUID | 联系人 ID。 |
响应
[
{
"id": "d1b2c3d4-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"organization_id": "org1...",
"pipeline_id": "p1...",
"stage_id": "st1...",
"contact_id": "1b2c3d4e-5f60-4a1b-8c2d-3e4f5a6b7c8d",
"name": "Acme expansion",
"value": 12000,
"currency": "USD",
"status": "open",
"expected_close_date": "2026-07-15T00:00:00Z",
"campaign_id": "c1...",
"created_at": "2026-06-05T09:00:00Z",
"updated_at": "2026-06-10T12:00:00Z"
}
]status 为 open、won 或 lost。value、expected_close_date、won_at、lost_at、lost_reason、assigned_to、campaign_id 和 source_mailbox_id 可为空,未设置时省略。
联系人分群
分群是保存的联系人受众:由一组条件及逐联系人的手动覆盖组成。每次读取都会实时计算成员关系,因此无需重新构建分群。所有分群端点均使用联系人权限范围,只有将成员加入营销活动的端点例外,因为它会写入潜在客户,需要 WRITE_CAMPAIGNS。操作现有分群的端点通过其 id 定位;除 GET /segments 外,控制台也在分群页面标题处(点击复制)及分群选项卡的行菜单中显示该 ID。
分群对象:
{
"id": "0b6f9c3e-2f7a-4c0e-9d8e-1a2b3c4d5e6f",
"organization_id": "…",
"name": "Warm fintech leads",
"description": "Opened in the last 30 days, not yet replied",
"color": "#0284c7",
"match": "all",
"conditions": [
{ "field": "custom.industry", "operator": "equals", "value": "fintech" },
{ "field": "last_opened_at", "operator": "within_days", "value": "30" },
{ "field": "emails_replied", "operator": "equals", "value": "0" }
],
"contact_count": 412,
"included_count": 3,
"excluded_count": 1,
"created_at": "2026-08-01T09:12:00Z",
"updated_at": "2026-08-20T14:03:00Z"
}match 为 all 或 any。联系人满足条件或被手动包含,且未被手动排除时,即为成员。没有条件的分群只包含手动加入的联系人。
条件
每个条件指定一个 field、一个 operator,以及 value(标量运算符)或 values(列表运算符)。GET /segments/fields 返回字段及其类型,包括以 custom.<key> 表示的工作区自定义字段(将尖括号占位符替换为实际键,例如 custom.industry)。
| 类型 | 字段 | 运算符 | 值 |
|---|---|---|---|
| text | first_name, last_name, email, email_domain, phone, company, custom.* | equals, not_equals, contains, not_contains, starts_with, ends_with, is_empty, is_not_empty | value 字符串;比较时忽略大小写 |
| enum | source, verification_status, mail_host, esp_provider | in, not_in | values,取自字段的 options;option_labels 提供用于显示的名称 |
| bool | subscribed, suppressed, is_catch_all | is_true, is_false | 无 |
| date | created_at, updated_at, last_sent_at, last_opened_at, last_clicked_at, last_replied_at | within_days、not_within_days(value 为天数,1 至 3650);before、after(value 为 YYYY-MM-DD 或 RFC 3339);is_empty、is_not_empty | 参见运算符 |
| number | campaign_count, emails_sent, emails_opened, emails_clicked, emails_replied, emails_bounced | equals, not_equals, gt, gte, lt, lte | value,整数 |
| category | category | in, not_in, is_empty, is_not_empty | values,标签 ID |
| campaign | campaign | in, not_in, is_empty, is_not_empty | values,营销活动 ID |
| segment | segment | in, not_in | values,分群 ID;最多嵌套五层,不允许循环引用 |
互动计数累加联系人参与过的所有营销活动,打开仅统计真人打开。限制:每个分群 50 个条件,每个列表条件 200 个值,每个工作区 200 个分群。未通过验证的条件会以 400 拒绝,并通过消息指出具体条件。
列出、创建、读取、更新和删除
GET /segments 在 data 下返回所有分群及实时计数。POST /segments 根据 name(必填)、description、color(#rrggbb)、match 和 conditions 创建分群;名称重复时返回 409。GET /segments/:id 返回单个分群。PATCH /segments/:id 接受相同字段,全部可选。DELETE /segments/:id 返回 204;若其他分群的条件引用了它,或某个营销活动将其 关联为实时受众,则返回 409,须先解除关联。
身份验证:权限范围 读取需要 READ_CONTACTS,写入需要 WRITE_CONTACTS · 组织权限 view_contacts / manage_contacts
预览分群定义
POST /segments/preview
统计尚未保存的定义会匹配多少联系人。发送 match 和 conditions;编辑现有分群时,可包含 id,以在计数中保留该分群的手动覆盖。
{ "contact_count": 412 }人工覆盖规则
POST /segments/:id/members
{ "contacts": ["…", "…"], "mode": "include" }mode 为 include(固定包含)、exclude(固定排除)或 auto(清除覆盖)。每次调用最多 10,000 个联系人 ID,或通过筛选选择最多 250,000 位联系人;忽略组织外的 ID。返回 { "updated": n }。请求体也可以用 筛选选择(all、filters、exclude)替代 contacts,对所有搜索匹配项设置固定成员关系。
POST /segments/:id/members/lookup 接受 { "contacts": [...] },并为具有覆盖设置的联系人返回 { "data": { "<contact id>": "include" | "exclude" } }。
GET /segments/:id/overrides 在 data 下列出所有固定包含或排除的联系人(contact_id、first_name、last_name、email、company、mode、created_at),包含项在前,各组最新在前,最多 500 项。
GET /contacts/:id/segments 是联系人侧视图:在 data 下列出组织的所有分群,以及 member(联系人当前是否属于该分群)和 mode(存在时的覆盖设置)。
邮件序列操作步骤 add_to_segment 和 remove_from_segment 接受 segment_id,并在步骤运行时为联系人应用包含或排除覆盖;参见 营销活动。
加入营销活动
POST /segments/:id/add-to-campaign
{ "campaign_id": "…" }将当前所有成员作为潜在客户加入。已在营销活动中的联系人会被跳过;每位新潜在客户获得一条 campaign_added 活动记录,运行中的营销活动会被唤醒以安排这些潜在客户。返回 { "campaign_id", "added", "members" }。这是一次快照操作:之后新增的成员只有再次调用才会加入。可安全重试。此操作视为手动选择所加入的潜在客户,因此会清除手动移除记录,且它写入的记录不会因 解除分群关联 而撤回。已经是潜在客户的成员保持原样,包括来源信息。若需要实时关联,以持续加入之后进入分群的成员,参见 关联分群。
身份验证:权限范围 WRITE_CAMPAIGNS · 组织权限 manage_campaigns