Resources
Foundations
Read bulk-operations/SKILL.md first — JSONL piping, batch read, pagination, and the dry-run/digest/confirm flow for destructive ops live there. Reshape recipes (read → write payload) are in bulk-operations/resources/json-patterns.md.
hubspot <command> --help is the source of truth. Object types are plural (products, line_items, quotes, invoices, subscriptions). Never hardcode property tables — hubspot properties list --type <type> is one call away. Verify any enum value the agent is about to write with hubspot properties options-list --type <type> <property> | jq -r '.value'.
Portal note: invoices, subscriptions, orders, carts show an empty objectTypeId in hubspot objects types. They work through objects search/list when the token has the matching scope (invoices-read, subscriptions-read, etc.) and 403 otherwise. CLI-created quotes are always DRAFT; approval routing, share links, PDF generation, and invoice creation usually require the HubSpot UI.
1. Create a product
For a recurring product set recurringbillingfrequency; check the API enum values first with hubspot properties options-list --type products recurringbillingfrequency | jq -r '.value'. Bulk-import a catalog by piping JSONL of {"properties":{...}} to hubspot objects create --type products --dry-run.
2. Build a quote: line items → quote → associations
objects create emits one result line per stdin line, in input order. That lets you build line items, capture their IDs, and associate them to the new quote in three pipes — no per-record shell loop.
Discount handling — discount is the writable percentage (10 = 10% off). hs_total_discount is HubSpot-computed; do not set it. Verify with hubspot properties get --type line_items hs_total_discount (property name is positional; look for modificationMetadata.readOnlyValue:true) before relying on this in a portal you don't own.
Promote a quote out of DRAFT when ready to share. objects update is irreversible — dry-run first, then re-run with the digest and --confirm <quote_id>:
Verify hs_status enum values for your portal: hubspot properties options-list --type quotes hs_status | jq -r '.value'.
3. Track invoices
The CLI reads invoice data and updates status; creation usually needs HubSpot Commerce + UI. Filter by hs_invoice_status and date.
Verify the status enum the same way: hubspot properties options-list --type invoices hs_invoice_status | jq -r '.value'.
4. Track subscriptions
Same shape, filter on hs_subscription_status. Verify the enum values before writing the filter — do not hardcode ACTIVE/CANCELLED/PAST_DUE:
Known constraints
invoices,subscriptions,orders,carts,paymentsneed the matching read scope on the active token; 403 means the user OAuth login or private-app token is missing the scope.objects deleteon products/quotes/line_items works under both user OAuth (hubspot auth login, with the object's write scope) and a service key (export HUBSPOT_ACCESS_TOKEN=<token>); a 403 means the active token is missing that write scope. The exception is the--gdprpermanent purge, which requires a service key — the GDPR endpoint does not accept user OAuth tokens. Seebulk-operations/SKILL.mdfor the dry-run → digest → confirm flow before bulk-deleting catalog records.- Quote share links, PDF generation, approval routing, and from-scratch invoice creation are UI-only — the CLI updates records but cannot send a quote to a customer.
hs_total_discounton line items is read-only — setdiscount(percentage) instead.

