关于 bulk-operations
bulk-operations 是 HubSpot 命令行工具的数据批量操作技能,面向需要读取、整理并写入大量 CRM 记录的任务。它说明 JSONL 管道、分页和数据重塑流程,并要求不可逆操作先预览,再通过摘要和确认执行。
主要功能
- 01以 JSONL 管道处理批量读写
- 02使用脚本分页收集完整结果
- 03通过 jq 重塑读写数据
- 04不可逆操作采用预览与摘要确认
- 05查看操作历史并检查失败结果
适用场景
- 01批量更新或删除筛选出的 CRM 记录
- 02将外部 JSONL 数据批量新增或更新
- 03分页导出记录后生成写入载荷
- 04核查批量操作记录及失败行
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hubspot/ agent-cli-skills --skill bulk-operations安装后在 Claude Code 中输入/bulk-operations确认已加载。
附带文件resources/json-patterns.mdresources/pagination-loop.sh
详细介绍
资源
| 文件 | 适用场景 |
|---|---|
resources/json-patterns.md | 转换模式:将读取结果转换为更新载荷、将搜索结果转换为删除列表、将 CSV 转换为 upsert 流。 |
信息来源
这是 hubspot Agent 命令行工具;开发者 CLI hs(@hubspot/cli)是另一种工具,不管理 CRM 数据或工作流。hubspot <command> --help 是权威依据。如果本文档中的任何内容与 --help 矛盾,请以 --help 为准,并告知用户。每次会话开始时运行一次 hubspot objects types,查看此门户中有哪些对象类型(标准类型 + 自定义类型)。
提交反馈
使用 hubspot feedback 命令向此 CLI 工具的所有者发送消息。传入 --source agent,以便将反馈归因于 Agent 流量(默认值为 user):
hubspot feedback "batch upsert timed out on 5k rows" --source agent
反馈内容可以是:
- 遇到的具体错误和问题
- 使用 CLI 前希望了解的信息
- 用户感到困惑、沮丧或不满的内容
- 用户提出但你无法完成的请求
- 希望提供的工具、功能或技能,以便让今后的任务更轻松
该命令只需一行简短内容,会关联到当前 HubSpot 账户,也不会阻塞任务——发送后继续操作。
输出格式
每个读取命令(list、search、get)都会输出 JSONL,每行一个 JSON 对象:
{"id":"123","properties":{"email":"[email protected]","firstname":"Jane"},"createdAt":"...","updatedAt":"...","archived":false,"url":"..."}
--properties email,firstname 会限制服务在 .properties 下返回的字段。下游 jq 应使用 .properties.email,而不是 .prop_email。
写入命令(create、update、upsert、delete、merge、associations create)接受 stdin 中的 JSONL,并输出 JSONL,每行输入对应一个结果:{"id":"123","ok":true,"data":{...}} 或 {"id":"123","ok":false,"error":{"status":...,"message":"..."}}。结果顺序与输入顺序一致——可以安全地按行位置关联。
批量读取——切勿逐条读取
CLI 原生支持多个 ID。切勿将 ID 管道传给 xargs -I{} hubspot objects get ...——这会为每条记录启动一个 CLI 进程。
# Positional args (small, known list)
hubspot objects get --type contacts 12345 67890 23456 --properties email,firstname
# Stdin from another command — one CLI call total
hubspot associations list --from companies:67890 --to contacts \
| jq -c '{id}' \
| hubspot objects get --type contacts --properties email,firstname,jobtitle
# Bare IDs on stdin also work
printf '12345\n67890\n23456\n' | hubspot objects get --type contacts --properties email
单次 hubspot objects get 调用通过批量端点最多可读取约 100 个 ID。超过此数量时,每次按 100 个分批读取。
批量处理流程:先分页,再转换,然后写入
处理某类对象的所有记录(或某个筛选条件匹配的所有记录)时,始终从 pagination-loop.sh 开始——切勿直接运行 list 或 search 来“查看有多少条记录”。直接调用最多返回 100 条记录,而你之后仍得重新获取这些记录。若要先估算任务规模,请使用 hubspot objects count --type <t> [--filter "..."];该命令会返回匹配记录总数(例如 {"object_type":"contacts","total":42}),不会进行分页。
标准批量处理模式如下:
- 分页获取所有记录并保存到 JSONL 文件
- 使用
jq转换为写入载荷 - 管道传入写入命令(
update、delete等),并先使用--dry-run
分页
list 和 search 每次调用最多返回 100 条记录。使用 resources/pagination-loop.sh 将所有页面收集到一个 JSONL 文件中:
bash resources/pagination-loop.sh <object_type> <output_file> [properties] [extra_flags...]
示例:
# All contacts with specific properties
bash resources/pagination-loop.sh contacts /tmp/contacts.jsonl email,firstname,lastname
# Search with a filter (passes extra flags through to the CLI)
bash resources/pagination-loop.sh contacts /tmp/leads.jsonl email,firstname '--filter' 'lifecyclestage=lead'
# All deals, default properties
bash resources/pagination-loop.sh deals /tmp/deals.jsonl
该脚本会自动通过 --after 游标翻页,在 stderr 中显示进度,并将 JSONL 写入输出文件。请将其作为单个前台命令运行——不要在后台运行,也不要在内联脚本中重建循环。
结果排序
search 默认按最新记录优先排序——它会根据对象类型定义的创建日期属性按降序排序(联系人使用 createdate,活动使用 hs_createdate,依此类推)——因此,第一页包含的是最近的记录,而不是门户中 2017 年左右的记录。使用 --sort <property> 按其他可排序属性排序(hs_lastmodifieddate、amount 等),并使用 --sort-dir asc 切换排序方向。list 不支持排序控制——需要关注顺序时,请使用 search。
associations list 也支持分页(--limit / --after,默认限制为 100):在 --format json 下,下一页游标位于 .meta.next;到达最后一页后,该值为 null。一个记录关联的记录数量可能远超单页容量,因此只要游标存在,就表示“还有更多记录”,请使用 --after 翻页,直到游标为 null。
批量写入——始终通过管道输入
写入命令接受来自标准输入的 JSONL。读取格式与写入格式之间的转换通过 jq 重塑完成:
| 写入命令 | 每行所需格式 |
|---|---|
objects create | {"properties":{"field":"value"}} |
objects update | {"id":"123","properties":{"field":"value"}} |
objects upsert | {"idProperty":"email","id":"[email protected]","properties":{...}}(或使用一次 --id-property email) |
objects delete | {"id":"123"} |
objects merge | {"primary":"123","secondary":"456"} |
associations create | {"from":"contacts:123","to":"companies:456"} |
from/to 中使用复数对象名称(contacts:,而不是 contact:)。
安全的破坏性操作工作流
不可逆写入——objects delete、merge、update、upsert、associations delete / limits-update,以及元数据删除(工作流、细分、视图、报告、架构、管道、属性)——始终需要经过两步的 dry-run → digest → confirm 流程,不论行数多少(删除 1 条记录和删除 10k 条记录同样需要)。软写入(objects create、views create/update/replace-field、仅更新标签的 properties update、segments members-add)无需 digest;其 --dry-run 只是普通预览。
1. Dry-run — 每次调用只输出一行预览(而非每条记录一行),无论数据量大小:
{"ok":true,"dry_run":true,"executed":false,"mutation_kind":"RecordMutation","command":"objects delete --type contacts","target":{"kind":"contacts_record","id":"123","name":"123"},"impact":{"records_affected":1,"note":"Delete 1 contacts record(s)","reversible":false},"portal":"123456","digest":"blast-29cfdd48b583","expires_in_seconds":300,"apply_command_hint":"hubspot objects delete --type contacts --digest blast-29cfdd48b583 --confirm '123'"}
mutation_kind 在 ≤100 行时为 RecordMutation,超过批量阈值时为 BulkData——两种情况下都会提供 digest,因此绝不要按 mutation_kind 过滤。确认值因命令而异:objects delete/update → 记录 ID(单条)或行数(批量 2 条及以上);objects upsert → 行数(始终如此);objects merge → 次要 ID(单对)或行数(批量);元数据删除 → 目标名称(工作流/视图/报告/架构/管道)、属性/选项名称,或行数(批量归档、关联批量操作)。不要自行构造确认值——请从预览行的 apply_command_hint(或 .target.id / .target.name)中复制。
2. 执行 在 5 分钟内完成(digest TTL 为 300s):重新通过管道传入相同的输入,并附上 --digest 和 --confirm:
# 1. Preview
hubspot objects search --type contacts --filter "lifecyclestage=subscriber" \
| jq -c '{id}' \
| hubspot objects delete --type contacts --dry-run \
| tee /tmp/preview.jsonl
# 2. Lift the digest + confirm value (present at EVERY row count)
digest=$(jq -r 'select(.digest != null) | .digest' /tmp/preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /tmp/preview.jsonl)
# 3. Execute — re-pipe the SAME inputs
hubspot objects search --type contacts --filter "lifecyclestage=subscriber" \
| jq -c '{id}' \
| hubspot objects delete --type contacts --digest "$digest" --confirm "$confirm"
未使用 --digest 执行会失败并返回 digest_required(错误中的 next_step 会指出 dry-run 命令);确认值错误会返回 confirm_mismatch;digest 过期会返回 digest_expired。--force 已弃用且会被忽略。
通过 hubspot history 恢复
每次破坏性操作(及其试运行)都会在本地记录。查看过去一小时发生了什么,以及哪些操作可以撤销:
hubspot history --since 1h --format table
hubspot history --since 24h --kind BulkData # only bulk ops
hubspot history --since 7d --kind MetadataDestroy # schema deletes
history 目前不会恢复记录——它是审计日志。如果误删了内容,请截取相应的历史记录行,并告知用户通过 UI 恢复。
如需查看 CRM 属性的 来源 历史记录(谁或什么更改了属性——WORKFLOW、INTEGRATION、IMPORT、CRM_UI),请使用 hubspot objects history --type <t> --properties <p>。此命令会将每个属性的版本历史记录展开为每次更改一行;默认排除由 UI 驱动的更改(CRM_UI),使用 --include-ui 可保留这些更改。添加 --id <recordId> 可读取单条记录;省略该参数则扫描一个页面。这与上文的本地 hubspot history 审计日志不同——在批量操作后调查属性为何发生更改时,请使用此命令。
Upsert 优于先搜索再创建
对于“缺失时创建,存在时更新”(即数据补全模式),请使用 upsert——每条记录只需调用一次 CLI,不会发生竞态条件:
cat external.jsonl \
| jq -c '{idProperty:"email", id:.email, properties:{firstname:.first, lastname:.last, company:.company}}' \
| hubspot objects upsert --type contacts --dry-run
# Or set idProperty once (upsert is irreversible — dry-run first, then execute):
cat external.jsonl \
| jq -c '{id:.email, properties:{firstname:.first}}' \
| hubspot objects upsert --type contacts --id-property email --dry-run \
| tee /tmp/upsert.preview.jsonl
digest=$(jq -r 'select(.digest != null) | .digest' /tmp/upsert.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /tmp/upsert.preview.jsonl) # upsert confirm = the row count, even for one row
cat external.jsonl \
| jq -c '{id:.email, properties:{firstname:.first}}' \
| hubspot objects upsert --type contacts --id-property email --digest "$digest" --confirm "$confirm"
速率限制注意事项
create、update 和 upsert 会将标准输入分批,每批最多 100 条,并调用实际的 batch/create / batch/update / batch/upsert 端点——每 100 行调用一次 API,而不是每行调用一次。delete 则为标准输入中的每一行调用一次 API。将 5 万行文件通过管道传入之前,先用 head -n 50 测试;或者使用专为批量导入设计的 hubspot imports。如果 API 开始返回 429 错误,每行(或每批)的输出都会显示 {"ok":false,"error":{"status":429,...}}——请拆分输入文件,并重试失败的行。
对于大型 CSV 导入,专为此设计的方案是 hubspot imports:imports start(使用 CSV 文件和 import-request JSON;支持 --dry-run)、imports list、imports get <id>、imports cancel <id>(不可撤销——先试运行,再使用导入 ID 执行 --digest/--confirm),以及 imports errors <id>。运行 hubspot imports --help 查看请求格式。
常见重塑方式
完整列表请参阅 resources/json-patterns.md。你 90% 的情况下只需要下面两种:
# Read → update payload (update is irreversible — dry-run, then re-pipe with the digest/confirm from the preview)
hubspot objects search --type contacts --filter "industry=Tech" \
| jq -c '{id, properties:{lifecyclestage:"marketingqualifiedlead"}}' \
| hubspot objects update --type contacts --dry-run \
| tee /tmp/update.preview.jsonl
digest=$(jq -r 'select(.digest != null) | .digest' /tmp/update.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /tmp/update.preview.jsonl) # single: record ID; batch of 2+: row count
hubspot objects search --type contacts --filter "industry=Tech" \
| jq -c '{id, properties:{lifecyclestage:"marketingqualifiedlead"}}' \
| hubspot objects update --type contacts --digest "$digest" --confirm "$confirm"
# Search → delete list (delete is irreversible — dry-run, lift, then re-pipe with --digest/--confirm)
hubspot objects search --type contacts --filter "!email" \
| jq -c '{id}' \
| hubspot objects delete --type contacts --dry-run \
| tee /tmp/delete.preview.jsonl
digest=$(jq -r 'select(.digest != null) | .digest' /tmp/delete.preview.jsonl)
confirm=$(jq -r 'select(.digest != null) | .target.id' /tmp/delete.preview.jsonl) # single: record ID; batch of 2+: row count
hubspot objects search --type contacts --filter "!email" \
| jq -c '{id}' \
| hubspot objects delete --type contacts --digest "$digest" --confirm "$confirm"
已知限制
objects delete支持使用用户 OAuth(浏览器登录,且具有对象的写入权限范围)和服务密钥;403 表示当前令牌缺少该写入权限范围。例外是--gdpr永久清除,该操作需要服务密钥(HUBSPOT_ACCESS_TOKEN)——GDPR 端点不接受用户 OAuth 令牌。objects update/merge/upsert也接受这两种令牌类型。某些破坏性操作(例如associations的 create/delete/batch/labels/limits、schemas delete)仍仅支持服务密钥,并由服务端强制执行,因此HUBSPOT_SKIP_AUTH_CHECK无法让用户令牌绕过限制。hubspot owners list返回 CRM 用户;没有teams对象。对于团队级操作,请在客户端按hubspot_owner_id分组。hubspot segments提供 CRM 列表(Lists API):list、get、create、update(元数据)、update-filters、delete、restore,以及members-list/members-add/members-remove。hubspot sequences提供对 Sales Hub 序列的只读访问:list --user-id <id>(分页,支持--name筛选)、get <id> --user-id <id>(步骤和设置),以及enrollments <contact_id>(联系人的注册历史)。序列属于产品 API 范围(Sales Hub Professional+,automation.sequences.read权限范围),而不是 CRM 对象类型——objects list --type sequences不起作用,也没有 create/update/delete/enroll 操作。- **维护本节:**命令范围会不断扩展——不要因为编写本文时某个 API 不存在,就认定它现在也不存在。发布新的命令系列时(参见
CHANGELOG.md),请重新检查这些限制。hubspot --help是当前可用功能的权威信息来源。