@revfleet/hscli 0.8.12 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +197 -1
  2. package/README.md +243 -10
  3. package/dist/cli.js +27 -3
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/agent/contracts.d.ts +3 -0
  6. package/dist/commands/agent/contracts.js +139 -0
  7. package/dist/commands/agent/contracts.js.map +1 -0
  8. package/dist/commands/agent/evidence.d.ts +68 -0
  9. package/dist/commands/agent/evidence.js +358 -0
  10. package/dist/commands/agent/evidence.js.map +1 -0
  11. package/dist/commands/agent/index.d.ts +3 -0
  12. package/dist/commands/agent/index.js +223 -0
  13. package/dist/commands/agent/index.js.map +1 -0
  14. package/dist/commands/agent/recipes.d.ts +13 -0
  15. package/dist/commands/agent/recipes.js +108 -0
  16. package/dist/commands/agent/recipes.js.map +1 -0
  17. package/dist/commands/agent/runner.d.ts +16 -0
  18. package/dist/commands/agent/runner.js +165 -0
  19. package/dist/commands/agent/runner.js.map +1 -0
  20. package/dist/commands/audit/index.js +4 -0
  21. package/dist/commands/audit/index.js.map +1 -1
  22. package/dist/commands/auth/index.js +64 -7
  23. package/dist/commands/auth/index.js.map +1 -1
  24. package/dist/commands/automation/index.js +30 -0
  25. package/dist/commands/automation/index.js.map +1 -1
  26. package/dist/commands/backup/index.d.ts +12 -0
  27. package/dist/commands/backup/index.js +459 -0
  28. package/dist/commands/backup/index.js.map +1 -0
  29. package/dist/commands/cms/content-ops.js +1 -1
  30. package/dist/commands/cms/content-ops.js.map +1 -1
  31. package/dist/commands/communication-preferences/index.js +207 -7
  32. package/dist/commands/communication-preferences/index.js.map +1 -1
  33. package/dist/commands/content-permissions/index.d.ts +22 -0
  34. package/dist/commands/content-permissions/index.js +141 -0
  35. package/dist/commands/content-permissions/index.js.map +1 -0
  36. package/dist/commands/context/index.d.ts +3 -0
  37. package/dist/commands/context/index.js +182 -0
  38. package/dist/commands/context/index.js.map +1 -0
  39. package/dist/commands/crm/associations.js +38 -0
  40. package/dist/commands/crm/associations.js.map +1 -1
  41. package/dist/commands/crm/custom-objects.js +39 -2
  42. package/dist/commands/crm/custom-objects.js.map +1 -1
  43. package/dist/commands/crm/dated.js +3 -3
  44. package/dist/commands/crm/dated.js.map +1 -1
  45. package/dist/commands/crm/migration.js +210 -2
  46. package/dist/commands/crm/migration.js.map +1 -1
  47. package/dist/commands/crm/properties.js +2 -1
  48. package/dist/commands/crm/properties.js.map +1 -1
  49. package/dist/commands/crm/property-batch.d.ts +1 -0
  50. package/dist/commands/crm/property-batch.js +44 -5
  51. package/dist/commands/crm/property-batch.js.map +1 -1
  52. package/dist/commands/crm/shared.js +21 -2
  53. package/dist/commands/crm/shared.js.map +1 -1
  54. package/dist/commands/crm/sync.js +1 -1
  55. package/dist/commands/crm/sync.js.map +1 -1
  56. package/dist/commands/doctor/index.js +193 -0
  57. package/dist/commands/doctor/index.js.map +1 -1
  58. package/dist/commands/email-tracking/index.d.ts +16 -0
  59. package/dist/commands/email-tracking/index.js +314 -0
  60. package/dist/commands/email-tracking/index.js.map +1 -0
  61. package/dist/commands/extensions/index.js +1 -1
  62. package/dist/commands/extensions/index.js.map +1 -1
  63. package/dist/commands/forms/index.js +15 -2
  64. package/dist/commands/forms/index.js.map +1 -1
  65. package/dist/commands/forms/legacy-v2.d.ts +7 -2
  66. package/dist/commands/forms/legacy-v2.js +103 -7
  67. package/dist/commands/forms/legacy-v2.js.map +1 -1
  68. package/dist/commands/guide/index.d.ts +1 -1
  69. package/dist/commands/guide/index.js +37 -4
  70. package/dist/commands/guide/index.js.map +1 -1
  71. package/dist/commands/internal/catalog.d.ts +1 -0
  72. package/dist/commands/internal/catalog.js +2 -0
  73. package/dist/commands/internal/catalog.js.map +1 -0
  74. package/dist/commands/internal/index.d.ts +26 -0
  75. package/dist/commands/internal/index.js +823 -0
  76. package/dist/commands/internal/index.js.map +1 -0
  77. package/dist/commands/internal-session.d.ts +15 -0
  78. package/dist/commands/internal-session.js +152 -0
  79. package/dist/commands/internal-session.js.map +1 -0
  80. package/dist/commands/lists/index.js +1 -1
  81. package/dist/commands/lists/index.js.map +1 -1
  82. package/dist/commands/marketing/email-audit.d.ts +32 -0
  83. package/dist/commands/marketing/email-audit.js +121 -0
  84. package/dist/commands/marketing/email-audit.js.map +1 -0
  85. package/dist/commands/marketing/index.js +2 -0
  86. package/dist/commands/marketing/index.js.map +1 -1
  87. package/dist/commands/mcp.js +5 -3
  88. package/dist/commands/mcp.js.map +1 -1
  89. package/dist/commands/playbooks/index.d.ts +3 -0
  90. package/dist/commands/playbooks/index.js +228 -0
  91. package/dist/commands/playbooks/index.js.map +1 -0
  92. package/dist/commands/portals/index.d.ts +3 -0
  93. package/dist/commands/portals/index.js +132 -0
  94. package/dist/commands/portals/index.js.map +1 -0
  95. package/dist/commands/reports/index.d.ts +3 -0
  96. package/dist/commands/reports/index.js +479 -0
  97. package/dist/commands/reports/index.js.map +1 -0
  98. package/dist/commands/sales-content/index.d.ts +13 -0
  99. package/dist/commands/sales-content/index.js +59 -0
  100. package/dist/commands/sales-content/index.js.map +1 -0
  101. package/dist/commands/sales-templates/index.d.ts +3 -0
  102. package/dist/commands/sales-templates/index.js +88 -0
  103. package/dist/commands/sales-templates/index.js.map +1 -0
  104. package/dist/commands/seed/cms.js +28 -45
  105. package/dist/commands/seed/cms.js.map +1 -1
  106. package/dist/commands/sequences/index.d.ts +18 -0
  107. package/dist/commands/sequences/index.js +263 -0
  108. package/dist/commands/sequences/index.js.map +1 -0
  109. package/dist/commands/settings/index.js +450 -102
  110. package/dist/commands/settings/index.js.map +1 -1
  111. package/dist/commands/settings/site-settings.d.ts +3 -0
  112. package/dist/commands/settings/site-settings.js +106 -0
  113. package/dist/commands/settings/site-settings.js.map +1 -0
  114. package/dist/commands/ui/index.js +14 -4
  115. package/dist/commands/ui/index.js.map +1 -1
  116. package/dist/commands/workflows/index.js +2 -0
  117. package/dist/commands/workflows/index.js.map +1 -1
  118. package/dist/commands/workflows/preflight.d.ts +23 -0
  119. package/dist/commands/workflows/preflight.js +295 -0
  120. package/dist/commands/workflows/preflight.js.map +1 -0
  121. package/dist/core/action-contract.d.ts +43 -0
  122. package/dist/core/action-contract.js +207 -0
  123. package/dist/core/action-contract.js.map +1 -0
  124. package/dist/core/auth.d.ts +6 -0
  125. package/dist/core/auth.js +85 -0
  126. package/dist/core/auth.js.map +1 -1
  127. package/dist/core/http.d.ts +8 -0
  128. package/dist/core/http.js +37 -5
  129. package/dist/core/http.js.map +1 -1
  130. package/dist/core/id-maps.d.ts +27 -0
  131. package/dist/core/id-maps.js +204 -0
  132. package/dist/core/id-maps.js.map +1 -0
  133. package/dist/core/output.d.ts +2 -1
  134. package/dist/core/output.js +34 -0
  135. package/dist/core/output.js.map +1 -1
  136. package/dist/mcp/profiles.d.ts +14 -0
  137. package/dist/mcp/profiles.js +68 -0
  138. package/dist/mcp/profiles.js.map +1 -0
  139. package/dist/mcp/server.d.ts +5 -3
  140. package/dist/mcp/server.js +22 -5
  141. package/dist/mcp/server.js.map +1 -1
  142. package/docs/CAPABILITY_LIBRARY.md +29 -8
  143. package/docs/COMMAND_TREE.md +99 -17
  144. package/docs/COMPARISON.md +8 -5
  145. package/docs/COOKBOOK.md +124 -5
  146. package/docs/INTERNAL-ENDPOINTS-DISCOVERY.md +813 -0
  147. package/docs/INTERNAL-ENDPOINTS.md +371 -0
  148. package/docs/INTERNAL_ENDPOINTS.md +243 -0
  149. package/docs/MARKETING_SETUP.md +21 -1
  150. package/docs/MCP.md +28 -89
  151. package/docs/MCP_TOOL_CATALOG.md +192 -0
  152. package/docs/MIGRATION_ADAPTERS.md +115 -0
  153. package/docs/PORTAL_BACKUP.md +212 -0
  154. package/docs/PUBLISHING.md +1 -1
  155. package/docs/SALES_CONTENT_INTERNAL_PUNCH_LIST.md +504 -0
  156. package/docs/TUTORIALS/secure-agent-writes.md +49 -0
  157. package/docs/WHY_HOW_WHAT.md +10 -4
  158. package/package.json +4 -2
@@ -0,0 +1,813 @@
1
+ # HubSpot Internal Endpoints — Discovery Log
2
+
3
+ Live capture of internal API surfaces for gap areas where hscli adapters are missing or incomplete. READ-ONLY on live portal <portalId>. No POST/PUT/PATCH/DELETE.
4
+
5
+ Each iteration appends a new ## YYYY-MM-DD — {area} section. Confirmed endpoints get merged into INTERNAL-ENDPOINTS.md.
6
+
7
+ 2026-06-19 — Iteration 1: Reports / Dashboards / Datasets
8
+ Captured by opening live report <id> then "Edit in report builder".
9
+
10
+ Reports surface (new)
11
+ Method Path Note
12
+ GET /api/dashboard/v2/favorite/all All starred reports/dashboards.
13
+ GET /api/dashboard/v2/reports/{id}/record-view Telemetry: increments "Total views".
14
+ GET /api/dashboard/v2/reports/<id> Full report definition incl. displayParams, aggregations, dimensions, filters. Source of truth for fidelity-preserving export/import.
15
+ GET /api/external-options/v3/pagedFetch/OWNER Owner typeahead for filter widgets.
16
+ GET /api/external-options/v3/fetch/{ENTITY} Generic entity option fetch.
17
+ GET /api/multi-currency/v2/currencies/information Currency list (needed for revenue reports).
18
+ GET /api/associations/v1/definitions/{typeId} Association definitions for the report's primary type.
19
+ GET /api/inbounddb-meta/v1/object-types/{typeId} Object-type metadata.
20
+ GET /api/properties/v4/groups/{typeId} + /properties Property catalogue + groups for the report's object.
21
+ Datasets / resolve (the actual data fetch)
22
+ Method Path Note
23
+ POST /api/reporting/v3/dataset/resolve/async Submits a dataset query. Body = report definition payload. Returns requestId.
24
+ GET /api/reporting/v3/dataset/resolve/async/{requestId} Poll for the result. Returns resolved aggregations + rows.
25
+ GET /api/reporting/v3/journey/{reportId}/resolve/async Variant for journey reports.
26
+ GET /api/reporting-usage-service/v1/ingestion/report Resolve telemetry.
27
+ Report templates / discovery
28
+ Method Path Note
29
+ GET /api/reporting-discovery/v1/report-template List built-in templates.
30
+ GET /api/reporting-discovery/v1/report-template/paged Paged.
31
+ GET /api/reporting-discovery/v1/report-template/template-key/{slug} Single template definition (e.g. sales.team).
32
+ GET /api/reporting-frontend/v1/reports/get-portal-reports Portal-wide report list (alt to dashboard/v2/reports).
33
+ POST /api/reporting-migration/v2/reports/migrate Internal cross-portal report migration endpoint! (Surfaced when opening a report — confirms HubSpot has its own migration tooling under the hood.)
34
+ AI copilot (insights generation)
35
+ Method Path Note
36
+ GET /api/reporting-insights/copilot/v1/abtest/options Copilot AB test config.
37
+ POST /api/reporting-insights/copilot/v2/generate Generate insights. Returns request ID.
38
+ GET /api/reporting-insights/copilot/v2/generate/status/{id} Poll for insights result.
39
+ Framework / schema metadata
40
+ Method Path Note
41
+ GET /api/framework-builder/v1/read/metadata/types/crmSegmentsUI CRM Segments UI type metadata.
42
+ GET /api/framework-builder/v1/read/metadata/types/crm All CRM types.
43
+ GET /api/framework-builder/v1/read/metadata/property/{typeId}/{aspect} Aspect ∈ crm, property, gibberishdetectionmetadata.
44
+ GET /api/inbounddb-meta/v1/object-types/{typeId} Object type metadata.
45
+ Chirp RPCs observed
46
+ com.hubspot.reporting.api.metadata.rpc.ReportingApiMetadataRpc/getPropertyMetadata (slow gateway)
47
+ com.hubspot.reporting.api.metadata.rpc.ReportingApiMetadataRpc/getPropertyMetadataGroups (slow gateway)
48
+ com.hubspot.blueprint.customer.object.type.rpc.CustomerObjectTypeRpc/getAllEnabledAndDisabledObjectTypeDefinitionsForPortalSorted
49
+ Telemetry / infra (skip for migration purposes)
50
+ metrics-fe-na1.hubspot.com/metrics/v1/frontend/send, metrics-fe-na1.hubspot.com/metrics/v1/frontend/custom/send, exceptions.hubspot.com/frontend/observability/page-tracking/store/, exceptions.hubspot.com/v2/api/js/store/, /api/cartographer/v1/rhumb, /api/cartographer/v1/performance, /api/usage-logging/v1/log/hublytics-multi/no-auth, /api/usage-logging/v1/log/session-replay
51
+
52
+ Fidelity assessment for migration
53
+ Reports: GET /api/dashboard/v2/reports/{id} returns enough to recreate (filters, dimensions, aggregations, viz). For adapter: serialize this JSON, remap property names + object IDs in destination portal, POST back.
54
+ Datasets: /api/reporting/v3/dataset/resolve/async is read-only data fetch — not part of the asset definition. Don't migrate; it's computed.
55
+ Journey reports: separate endpoint family. Need to test fidelity separately.
56
+ Report templates: template-key references stay valid across portals (built-in catalogue). Custom-template-based reports need template body migration first.
57
+ Adapter gap: HubSpot has POST /api/reporting-migration/v2/reports/migrate — worth a separate investigation iteration to see if this can be reused for cross-portal report copy.
58
+
59
+ 2026-06-19 — Iteration 2: Workflows v4 full fidelity (automationplatform/v1)
60
+ Captured by opening live workflow <id> (USA | <asset prefix> | Lead Status to Lead Pipeline) in the editor.
61
+
62
+ 🔑 CSRF unlock pattern (works for ALL these endpoints)
63
+ Previously got 401 on internal endpoints. Solution: read cookie hubspotapi-csrf and send as header X-HubSpot-CSRF-hubspotapi.
64
+
65
+ const csrf = document.cookie.split('; ').find(c => c.startsWith('hubspotapi-csrf='))?.split('=')[1];
66
+ const r = await fetch(url, { credentials: 'include', headers: { 'X-HubSpot-CSRF-hubspotapi': csrf } });
67
+ This is the key that unlocks every internal endpoint that was previously 401. Apply throughout the adapter layer.
68
+
69
+ Workflows surface (the real source of truth — NOT v3/v4 public)
70
+ Method Path Status Note
71
+ GET /api/automationplatform/v1/hybrid/{flowId} ✅ 200 THE full workflow definition. Returns: flowType, isEnabled, name, firstActionId, timeWindow, enrollmentSchedule, shouldReenroll, flowObjectType, objectTypeId, enrolledType, enrollmentDataset, triggers, triggerTypeId, portalId, flowId, creator*, actions[], branches, goals, etc. Source of truth for fidelity-preserving migration.
72
+ GET /api/automationplatform/v1/validation/{flowId} ✅ 200 valid: bool, errors[], warnings[].
73
+ GET /api/automationplatform/v1/output-fields/flow/{flowId}/allOutputs ✅ 200 enrolledObjectOutputs, actionTypeOutputs, instanceOutputs — outputs available for downstream action references.
74
+ GET /api/automationplatform/v1/access/object-enrolled-type-access ✅ Which object types user can enroll.
75
+ GET /api/automationplatform/v1/access/permission-check/flow/{flowId} ✅ Per-flow access check.
76
+ POST /api/automationplatform/v1/access/permission-check/flow/batch/read ✅ Batch access check.
77
+ POST /api/automationplatform/v1/filter-descriptions/generate ✅ Human-readable filter description.
78
+ GET /api/automation/v4/actions/internal/access/definitions/filter ✅ Action catalogue — all available action types + their config schemas. Critical for building the adapter since actions[] in the hybrid response references these definitions.
79
+ POST /api/automationmanagement/v1/revision/revert ⚠️ WRITE Revision rollback. DO NOT call in read-only mode.
80
+ Workflow apps / CRM integration
81
+ Method Path Status Note
82
+ GET /api/automationapps/v1/flow-crm-objects/{flowId} ✅ 200 Returns enrolled CRM object snapshot incl. properties, associations, state, archival history. ~28KB per flow.
83
+ GET /api/automationapps/v1/insights/canvas/errors/{flowId} ✅ 200 executionErrors, enrollmentErrors.
84
+ GET /api/automation-platform-bulk-processing/v1/bulk-processing/flow/{flowId} ✅ 200 Bulk processing jobs (currently []).
85
+ Workflow chirp RPCs
86
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.workflow.canvas.rpc.FlowCardPositionRpc/getCardPositions — canvas layout coordinates per card. POST /api/chirp-frontend-app/v1/gateway/com.hubspot.automation.access.rpc.AutomationAccessRpc/getFeatureAccess (slow gateway). POST /api/chirp-frontend-app/v1/gateway/com.hubspot.automationplatform.chirp.rpc.DelayedActionsService/getDelayActionEnrollmentCounts. POST /api/chirp-frontend-app/v1/gateway/com.hubspot.automation.actions.rpc.RenderableActionDefinitionService/search — search action types by query (used by editor's "+ add action" UI). POST /api/chirp-frontend-app/v1/gateway/com.hubspot.automationplatform.chirp.rpc.FlowBillingRpc/getFlowBillingInfo — flow billing/usage. POST /api/chirp-frontend-app/v1/gateway/com.hubspot.workflows.copilot.rpc.WorkflowsCopilot/fetchInteractiveModeContextForFlow — Copilot context.
87
+
88
+ Supporting endpoints (CRM/object plumbing for workflow editor)
89
+ Path Note
90
+ /api/inbounddb-meta/v1/object-types/for-portal All object types for portal.
91
+ /api/inbounddb-meta/v1/object-types/{typeId} Single.
92
+ /api/properties/v4/groups/{typeId} + /properties Property catalogue.
93
+ /api/associations/v1/definitions/{typeId} Assoc definitions.
94
+ /api/owners/v2/owners/batch-v2 Batch owner read.
95
+ /api/integrators/timeline/internal/v4/event-type-installations/by-user Timeline event types per user.
96
+ /api/framework-builder/v1/read/metadata/type/all/batch Batch type metadata.
97
+ /api/multi-currency/v2/currencies/information Currencies.
98
+ /api/crm-search/search Generic CRM search (used by triggers/filters).
99
+ Workflow chirp RPCs (more)
100
+ com.hubspot.unified.events.rpc.UnifiedEventsEventTypeRpc/getAllEventTypeCategories
101
+ com.hubspot.ubb.component.status.service.rpc.UbbComponentStatusServiceAppRpc/getComponentStatusForPortal
102
+ com.hubspot.usagebasedbilling.experience.rpc.UsageBasedBillingExperienceRpc/getUsagePeriodUsage
103
+ Fidelity adapter design implications
104
+ Full workflow definition is reachable via GET /api/automationplatform/v1/hybrid/{flowId} with CSRF — includes the full actions[] array (previously thought internal-only via v4 public API).
105
+ Action type catalogue is at GET /api/automation/v4/actions/internal/access/definitions/filter — needed to validate and remap action types between portals.
106
+ Output references (/output-fields/.../allOutputs) tell you which prior actions an action depends on — critical for remapping action IDs in destination portal.
107
+ Adapter strategy:
108
+ GET hybrid → serialize
109
+ Remap objectTypeId, owner IDs, property internal names, list IDs, action references between portals
110
+ Force isEnabled: false, shouldReenroll: false on import
111
+ POST to destination's hybrid endpoint (POST verb not yet confirmed — needs separate exploration in a sandbox portal, NOT live)
112
+ 2026-06-19 — Iteration 3: Service Hub (chatflows, help desk, SLA, inbox)
113
+ Captured by opening /chatflows/<portalId> and /help-desk/<portalId>.
114
+
115
+ Chatflows (entirely behind chirp)
116
+ Method Path Note
117
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.chatflows.rpc.ChannelInstanceRpc/getUserChatflowsSortedByPriority List chatflows + priority order.
118
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.chatflows.rpc.ChatflowsRpc/getSearchMeta Search metadata.
119
+ No public CRUD REST API found in this iteration. Service surface is RPC-only. Chatflows list shows web/mobile/Facebook channels. Confirmed ~30 chatflows in portal across DE/FR/NL/EN/ES geos.
120
+
121
+ Help Desk (workspace + custom views)
122
+ Method Path Note
123
+ GET /api/conversations-custom-views/v1/settings/users Workspace user settings.
124
+ GET /api/conversations-custom-views/v1/spaces-and-views/workspace/{workspaceId} Spaces + views in a workspace (workspaceId from URL).
125
+ GET /api/conversations-custom-views/v1/settings/views/{viewId} Single view definition.
126
+ GET /api/conversations-email/v1/connected-accounts/inbox/{inboxId} Connected email accounts per inbox.
127
+ GET /api/conversations-channels/v1/channel-instances/inbox/{inboxId}/paged Channel instances (chat/email/etc) per inbox.
128
+ GET /api/cv/inbox/settings/v1/user-prompts UI prompts.
129
+ GET /api/email-mta-sending/v1/auth-domains/active-domains-with-state Active sending domains.
130
+ GET /api/customer-object-types/v1/for-portal Custom object types.
131
+ GET /api/properties/v4/groups/0-5/properties Ticket properties (object type 0-5).
132
+ GET /api/associations/v1/definitions/0-5 Ticket associations.
133
+ POST /api/graphql/crm GraphQL endpoint for CRM queries.
134
+ POST /api/crm-search/search Tickets search.
135
+ Help Desk chirp services
136
+ com.hubspot.cv.custom.views.member.rpc.CvCustomViewsMemberRpc/getPagedHelpDeskTickets — list tickets per view
137
+ com.hubspot.cv.tickets.notification.preferences.rpc.TicketNotificationPreferenceFrontendRpc/getUserNotificationPreferences
138
+ com.hubspot.cv.search.rpc.HelpdeskSearchHistoryRpc/getSearchHistory
139
+ com.hubspot.cv.assignments.rpc.UserCapacityLimitsV2Service/getCurrentUserCapacityData — assignment capacity
140
+ com.hubspot.helpdesk.migration.rpc.HelpdeskMigrationRpc/getMigrationStatusesForPortal — HubSpot's own help desk migration RPC
141
+ com.hubspot.blueprint.object.library.rpc.ObjectLibraryRpc/getEnablementStatusForObjectTypes
142
+ com.hubspot.upgrade.management.rpc.UpgradeConfigInAppRpc/getUpgradeConfigWithPurchaseMotions
143
+ Onboarding tasks (used in Service Hub UI)
144
+ GET /api/usercontext/v1/onboarding/tasks/{guideKey} — e.g. hd_in_workspace_onboarding_guide
145
+ GET /api/usercontext/v1/onboarding/progress/{guideKey}
146
+ Not found in this portal
147
+ /settings/<portalId>/inbox/sla → 404 (no SLA configured on this portal — Service Hub not provisioned)
148
+ /settings/<portalId>/inbox/ → 404
149
+ /help-desk/<portalId>/settings/sla → redirects to help-desk list
150
+ Adapter gap remains: SLA policies, customer portal, and KB articles need a separate portal where these are enabled. Conversations-custom-views + conversations-channels endpoints are the discovery surface for inbox routing config.
151
+
152
+ 2026-06-19 — Iteration 4: Permission set assignment (admin/access)
153
+ All confirmed working with CSRF (X-HubSpot-CSRF-hubspotapi header).
154
+
155
+ Permission sets
156
+ Method Path Status Response Note
157
+ GET /api/app-users/v1/permission-sets?portalId={p} ✅ 200 Array[72] All permission sets on portal incl. names, capability flags, scopes. ~160KB.
158
+ GET /api/app-users/v1/permission-sets/{psId} ✅ object Single permission set + full capability matrix.
159
+ GET /api/app-users/v1/permission-sets/permission-set-user-assignments ✅ 200 {hubId, userPermissionSetIds} THE assignment endpoint — returns map of userId → permissionSetIds[]. 56KB for 2,977 users.
160
+ GET /api/app-users/v1/roles?portalId={p} ✅ 200 Array[1124] Roles (more granular than permission sets — 1124 in this portal).
161
+ GET /api/permission/templates/v1/permission-templates/hub/{portal} ✅ 200 Array[12] Permission templates (Super Admin, Marketing, Sales, etc.).
162
+ Seats
163
+ Method Path Status Response
164
+ GET /api/app-users/v1/seat-assignments/{portal} ✅ 200 Array[2977] — user→seat assignments
165
+ GET /api/app-users/v1/seat-assignments/{portal}/seat-info ✅ 200 Array[4] — seat types + counts + limits
166
+ GET /api/app-users/v1/seat-purchase/pending-downgrades ✅ Pending seat downgrades
167
+ GET /api/app-users/v1/sales-products ✅ Sales products (seat-related)
168
+ GET /api/app-users/v1/sales-products/product-info ✅ Product info
169
+ Users / teams
170
+ Method Path Status Note
171
+ GET /api/app-users/v1/users/{userId} ✅ Single user incl. permission set + role assignment.
172
+ POST /api/app-users/v1/users/partial/batch ✅ Batch read users.
173
+ GET /api/app-users/v1/teams ✅ Teams.
174
+ GET /api/teams/v1/team-hub-settings?portalId={p} ✅ 200 {defaultContentAssignmentOn, hubId}
175
+ Other access plumbing
176
+ Path Note
177
+ /api/external-options/v3/fetch/PERMISSION_SET Typeahead-friendly permission set fetch.
178
+ /api/external-options/v3/fetch/TEAM Same for teams.
179
+ /api/vetting/public/v1/user-verification/bannerinfo/scope/users-write Identity verification gate (banner shown before writes).
180
+ /api/user-email-updates/v1/domain-change/current Current domain-change request (admin context).
181
+ /api/bizops-services-customers/v3/hub-contact-roles/external External hub contact roles.
182
+ Chirp RPCs (users service)
183
+ com.hubspot.appusers.rpc.UsersRpc/getUsersByIds
184
+ com.hubspot.appusers.rpc.UsersRpc/getUserIdsOnHubByRoleIdAndPendingStatus
185
+ com.hubspot.appusers.rpc.UsersRpc/filterOutInvisibleUsers
186
+ Adapter design — permission set migration
187
+ The full loop is now reachable read-only:
188
+
189
+ Source: GET /api/app-users/v1/permission-sets → all 72 PS definitions
190
+ Source: GET /api/app-users/v1/permission-sets/permission-set-user-assignments → user→PS map
191
+ Map between portals by name (HubSpot uses name+capability fingerprint, not ID) OR by capability shape
192
+ Destination write (NOT in scope here — read-only mode):
193
+ POST /api/app-users/v1/permission-sets to create
194
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.appusers.rpc.PermissionSetAssignmentsRpc/assignUserToPermissionSets?portalId={portalId} to assign one or more permission sets to one or more users.
195
+ Confirmed body: `{ "assignments": [{ "userId": "<id>", "permissionSetIds": ["<permissionSetId>"] }] }`.
196
+
197
+ 2026-06-19 — Iteration 5: Field-level permissions replay
198
+ Captured by opening /property-settings/<portalId>/properties?type=0-1 and probing endpoints with CSRF.
199
+
200
+ Blocklist (global + per-object)
201
+ GET /api/crm-permissions/v1/field-level-permissions/blocklist/including-global?portalId={p} → ✅ 200
202
+
203
+ {
204
+ "blocklistedPropertiesByObjectTypeId": {
205
+ "0-1": ["email","firstname","hs_pipeline","lastname","lifecyclestage", ...],
206
+ "0-2": ["domain","hs_is_target_account","hs_pipeline","lifecyclestage","name"],
207
+ "0-3": ["closedate","deal_currency_code","dealname","dealstage","notes_last_updated","pipeline"],
208
+ "0-5": [...],
209
+ "0-101": ["hs_currency_code"],
210
+ "0-69": ["hs_currency_code"],
211
+ "2-<id>": [...] // custom objects
212
+ },
213
+ "globallyBlocklistedProperties": ["hs_object_id", "this_is_a_test_globally_blocklisted_property"]
214
+ }
215
+ Full blocklist state is reachable read-only. This is what FLP "cannot restrict access to this property" boils down to.
216
+
217
+ Allowlist access-state (per object + property)
218
+ POST /api/crm-permissions/v1/field-level-permissions/allowlistings/access-state/multi-get?portalId={p} → ✅ 200
219
+
220
+ Request body shape (figured out by probing):
221
+
222
+ { "objectTypeIds": ["0-1", "0-2"] }
223
+ Optional refinement: { "objectTypeIds": ["0-1"], "propertyNames": ["usa___asen___sales_owner"] }.
224
+
225
+ Response:
226
+
227
+ {
228
+ "0-1": {
229
+ "br___mail_tecnico": "LIMITED_TO_SUPER_ADMINS",
230
+ "eu___example_brand___c_sat___customer_care": "LIMITED_TO_SUPER_ADMINS",
231
+ "subscribe_to_example_blog_checkbox_version": "LIMITED_TO_SUPER_ADMINS",
232
+ ...
233
+ }
234
+ }
235
+ Access states observed: LIMITED_TO_SUPER_ADMINS. Other expected values per HubSpot UI: READ_ONLY_FOR_NON_SUPER_ADMINS, HIDDEN_FROM_NON_SUPER_ADMINS, EDITABLE (default — not returned).
236
+
237
+ Other FLP endpoints
238
+ Method Path Status Note
239
+ GET /api/crm-permissions/v1/field-level-permissions/allowlistings/{typeId} ❌ 404 No bulk fetch by object — use multi-get.
240
+ GET /api/crm-permissions/v1/field-level-permissions/allowlistings/{typeId}/{property} ❌ 404 if no allowlist exists Returns 404 when property has default access.
241
+ GET /api/permissions-skynet/v1/scopes/app/field-level-permissions-bypass/user-ids ✅ Users who bypass FLP.
242
+ Adapter design — FLP replay
243
+ Read source:
244
+
245
+ GET blocklist/including-global → full blocklist (mostly read-only HubSpot defaults; rarely custom).
246
+ POST allowlistings/access-state/multi-get with {objectTypeIds: [all custom + standard]} → full allowlist state map.
247
+ Write to destination (NOT in scope here — sandbox required): 3. PUT /api/crm-permissions/v1/field-level-permissions/allowlistings/{typeId}/{property} (assumed) with body like { accessState: "LIMITED_TO_SUPER_ADMINS" }.
248
+
249
+ Gap: write endpoint not yet confirmed — needs sandbox.
250
+
251
+ Other endpoints surfaced from property settings page
252
+ Path Note
253
+ /api/framework-builder/v1/read/metadata/types/crmPropertySettingsUI UI metadata.
254
+ /api/schemas-metadata/v1/portal-metadata/type/PROPERTY/paged Paged property metadata.
255
+ /api/property-settings/v1/property-fill-rate/{typeId}/batch/read Fill rates per property.
256
+ Chirp RPCs (custom enrichment / smart properties)
257
+ com.hubspot.custom.enrichment.rpc.client.PropertyConfigService/getProperties
258
+ com.hubspot.custom.enrichment.rpc.client.PropertyConfigService/getSmartPropertyMetrics
259
+ com.hubspot.custom.enrichment.rpc.client.SmartPropertyWorkflowService/getWorkflowsByPropertyConfigIds
260
+ 2026-06-19 — Iteration 6: Subscription types (Email/Chat/Call/Post)
261
+ Captured by opening /settings/<portalId>/marketing/email/types and probing endpoints.
262
+
263
+ Full subscription type list
264
+ GET /api/email/public/v1/subscriptions?portalId={p} → ✅ 200, 66KB
265
+
266
+ Despite the /email/ prefix, returns ALL subscription types across every channel — Email, Chat, Call, Post, and unspecified.
267
+
268
+ In live portal <portalId>:
269
+
270
+ Channel Count
271
+ Email 146
272
+ Chat 19
273
+ Call 3
274
+ Post 3
275
+ (empty/legacy) 74
276
+ Total 245
277
+ Each subscription type object:
278
+
279
+ {
280
+ "id": <id>,
281
+ "portalId": <portalId>,
282
+ "businessUnitId": <id>,
283
+ "name": "Active Pest Marketing Communications",
284
+ "internalName": "MARKETING_INFORMATION",
285
+ "description": "I want to receive marketing offers and updates.",
286
+ "category": "Marketing", // or "Consent" (chat tracking) or empty
287
+ "channel": "Email", // Email / Chat / Call / Post / empty
288
+ "active": true,
289
+ "internal": false
290
+ }
291
+ Visibility rules / subscription RPCs
292
+ Method Path Note
293
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.subscriptions.settings.rpc.service.RulesRpc/getAllRules Visibility rules (which BU/language combos see which subs).
294
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.subscriptions.settings.rpc.service.RulesRpc/getAllDefinitionRules Rule definitions.
295
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.data.privacy.hub.settings.rpc.PrivacySettingsRpc/getPrivacySettings Privacy / GDPR config.
296
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.business.units.rpc.BusinessUnitsDisplaysService/getBusinessUnitDisplaysForUser BU display map (used by subscription editor).
297
+ Supporting endpoints
298
+ Method Path Note
299
+ GET /api/content/api/v4/can-spam-settings?portalId={p} CAN-SPAM footer addresses list.
300
+ POST /api/subscriptions-settings/v1/settings/multi-get Body: string[] of setting keys.
301
+ GET /api/navconfig/v2/navconfig/settings-preferences Settings nav config.
302
+ GET /api/cosemail/v1/subscription-emails/{type}/get Subscription email template (e.g. RESUBSCRIBE_EMAIL).
303
+ Adapter design — subscription types
304
+ Read source:
305
+
306
+ GET /api/email/public/v1/subscriptions → all 245 subs across all channels.
307
+ RulesRpc getAllRules → visibility rules (BU + language + sub mapping).
308
+ Write to destination (NOT in scope here): 3. POST /api/email/public/v1/subscriptions (assumed) per subscription type. 4. Visibility rules likely via a separate RPC write — sandbox required.
309
+
310
+ Gap closed: Non-email subscription types (Chat/Call/Post) DO have a public/internal endpoint after all. They were previously thought adapter-incomplete because marketing/v3/subscriptions/{}/... only deals with email contact subscriptions, not the type definitions themselves.
311
+
312
+ 2026-06-19 — Iteration 7: Brand kits
313
+ Captured by opening /settings/<portalId>/account-defaults/branding (redirects to /brand-identity/<portalId>/brand/{buId}/brand-kit) and probing.
314
+
315
+ Brand kits surface (CSRF required)
316
+ Method Path Status Response Note
317
+ GET /api/branding/v1/brand-kit?portalId={p} ✅ 200 Array[21] All brand kits on portal. ID, name, BU id, activeThemePath, primaryBrandKit flag.
318
+ GET /api/branding/v1/brand-kit/all?portalId={p} ⚠️ 400 error Likely needs different query (e.g. ?buId=).
319
+ GET /api/branding/v1/brand-kit/{kitId} ✅ single Single kit detail.
320
+ GET /api/branding/v1/colors/brand-kit?portalId={p} ✅ 200 Array[9] All brand colors.
321
+ GET /api/branding/v1/logos/brand-kit?portalId={p} ✅ 200 Array[10] All logos.
322
+ GET /api/branding/v1/favicons/brand-kit?portalId={p} ✅ 200 Array[24] All favicons.
323
+ GET /api/branding/v1/fonts-with-variants?portalId={p} ✅ 200 Array[] Custom fonts (empty in this portal).
324
+ GET /api/branding/v1/font-meta?portalId={p} ❌ 403 Not enabled on this portal.
325
+ GET /api/branding/v1/brand-voice/by-bu-id/{buId} ✅ Brand voice (used by AI copy generation).
326
+ GET /api/branding/v1/brand-kit/business-unit/{buId} ✅ Kit per BU.
327
+ Brand kit object shape
328
+ {
329
+ "id": <id>,
330
+ "portalId": <portalId>,
331
+ "buId": 0,
332
+ "businessUnitId": null,
333
+ "name": "<brand name>",
334
+ "activeThemePath": "<client>_November2022",
335
+ "activeThemePresetId": null,
336
+ "activeThemePresetPath": null,
337
+ "activeThemePresetType": null,
338
+ "primaryBrandKit": true,
339
+ "createdAt": <id>,
340
+ "createdBy": 100,
341
+ "modifiedAt": <id>,
342
+ "deletedAt": null
343
+ }
344
+ Business units (needed for brand kit context)
345
+ Method Path Status Response
346
+ GET /api/business-units/v1/business-units?portalId={p} ✅ 200 Array[21]
347
+ Adapter design — brand kits
348
+ Read source (all CSRF):
349
+
350
+ GET /api/business-units/v1/business-units → BU list
351
+ GET /api/branding/v1/brand-kit → all brand kits
352
+ GET /api/branding/v1/colors/brand-kit, /logos/brand-kit, /favicons/brand-kit → assets
353
+ Per kit: 4. GET /api/branding/v1/brand-kit/business-unit/{buId} → detailed BU-scoped kit 5. GET /api/branding/v1/brand-voice/by-bu-id/{buId} → brand voice
354
+
355
+ Write to destination (NOT in scope — needs sandbox): 6. Create BUs first; remap buId → new BU IDs 7. POST brand kit → POST colors/logos/favicons referencing new kit ID
356
+
357
+ Gap closed: brand kits ARE accessible via REST (not just chirp). Logos/favicons are URLs to HubSpot file storage — adapter needs to download from source CDN and re-upload via Files API.
358
+
359
+ 2026-06-19 — Iteration 8: Marketing email (cosemail surface — corrects prior 404 finding)
360
+ Earlier sessions concluded marketing email APIs were all 404 from in-tab. That was WRONG. The correct internal surface is /api/cosemail/v1/... — confirmed working with CSRF in this iteration.
361
+
362
+ Marketing emails (cosemail)
363
+ Method Path Status Response Note
364
+ GET /api/cosemail/v1/emails?portalId={p}&limit={n}&businessUnitId={buId} ✅ 200 {objects[], total, totalCount, limit, offset} 18,722 emails in this portal. Total ~198KB for limit=5.
365
+ GET /api/cosemail/v1/emails/{id} ✅ 200 15KB Full email definition — see shape below.
366
+ GET /api/cosemail/v1/subscription-emails/{type}/get ✅ Type-specific email template (e.g. RESUBSCRIBE_EMAIL).
367
+ Email object shape (~196 keys per email)
368
+ Includes everything needed for fidelity-preserving migration:
369
+
370
+ Identity: id, name, subject, previewText, slug, language, businessUnitId, campaign, campaignName, campaignUtm, category, categoryId, folderId, folderIdV2
371
+
372
+ Sender: fromName, replyTo, customReplyTo, customReplyToUserId
373
+
374
+ Design fidelity (the gap):
375
+
376
+ dragAndDropLayoutId — D&D layout reference
377
+ dragAndDropThemeId — theme reference
378
+ flexAreas — flexible content areas
379
+ headerFooterModules — header/footer module config
380
+ attachedStylesheets
381
+ css, cssText
382
+ emailBody, emailbodyPlaintext
383
+ editorVersion, emailTemplateMode, emailType (DRAG_AND_DROP_EMAIL etc.)
384
+ featuredImage, featuredImageHeight, featuredImageLength, featuredImageWidth
385
+ A/B: ab, abVariation, abTestPercentage, abTestType, abHoursToWait, abSamplingDefault, abSampleSizeDefault, abSuccessMetric
386
+
387
+ Publishing: currentState, currentlyPublished, isDraft, isPublished, archived, archivedAt, archivedInDashboard, freezeDate, created, createdById, createdByAgent, audienceAccess
388
+
389
+ Tracking: disableAllTracking, analyticsPageId, analyticsPageType, canSpamSettingsId, feedbackFormId, feedbackSurveyId, formId
390
+
391
+ Lifecycle: crmObjectId, errors, errorDate, fixupHistory, clonedFrom, emailRenderSwimlane, emailNote, emailPublishTag, allEmailCampaignIds, hasUserChanges
392
+
393
+ What's NOT in cosemail (tried, all 404)
394
+ /api/cosemail/v1/email-themes
395
+ /api/cosemail/v1/email-modules
396
+ /api/cosemail/v1/saved-sections
397
+ /api/cosemail/v1/global-modules
398
+ /api/cosemail/v1/email-templates
399
+ /api/email-saved-content-app/v1/saved-content
400
+ /api/email-themes/v3/themes
401
+ /api/design-manager/v3/themes
402
+ /api/design-manager/v3/modules
403
+ /api/saved-content/v1/saved-content
404
+ Themes, modules, and saved sections are under a different surface. Need to navigate to Marketing > Files & Templates > Design Manager to find them. Defer to iteration #11 (CMS).
405
+
406
+ Adapter design — marketing email
407
+ Read source:
408
+
409
+ GET /api/cosemail/v1/emails?limit=100&offset={n} — paginate through 18k+ emails.
410
+ GET /api/cosemail/v1/emails/{id} per email — full definition (rarely needed; list already gives most fields).
411
+ Resolve dragAndDropThemeId + dragAndDropLayoutId via design-manager (TBD).
412
+ Resolve featuredImage URLs — re-host on dest via Files API.
413
+ Write to dest (NOT in scope — sandbox required): POST /api/cosemail/v1/emails with the same shape.
414
+
415
+ Gap closed (partial): email body + design payload reachable. Themes/modules still need iteration #11.
416
+
417
+ 2026-06-19 — Iteration 9: CTAs (legacy + new)
418
+ Captured by opening /cta/<portalId>/.
419
+
420
+ CTAs (placements = new + legacy)
421
+ Method Path Status Response
422
+ GET /api/ctas/v3/placements?portalId={p}&limit={n}&offset={n} ✅ 200 {results[], hasMore, offset, total} — 2,244 CTAs in this portal.
423
+ GET /api/ctas/v3/placements/{guid}?portalId={p} ⚠️ Direct ID lookup returned error (404-ish wrapper); the editor loaded individual placements via list+filter.
424
+ CTA placement object shape
425
+ {
426
+ "guid": "088d78df-da57-4a53-9b0e-7f4aae17dc25",
427
+ "portalId": <portalId>,
428
+ "name": "...",
429
+ "type": "...",
430
+ "createdAt": ..., "updatedAt": ...,
431
+ "createdBy": ..., "updatedBy": ...,
432
+ "deletedAt": null, "deletedBy": null,
433
+ "definitionId": "...",
434
+ "campaignId": "...",
435
+ "supportedEnv": "...",
436
+ "isMigrated": true|false, // legacy→new CTA migration flag (HubSpot's own)
437
+ "visibleToAll": true|false
438
+ }
439
+ isMigrated is HubSpot's flag tracking the Legacy CTA → New CTA Builder migration. Source for our adapter to know which CTAs to migrate vs. which are already on the new builder.
440
+
441
+ What's NOT a separate endpoint
442
+ /api/ctas/v3/cta, /api/ctas/v1/ctas, /api/ctas/v2/ctas, /api/cta/v1/..., /api/cta-api/..., /api/ctas-legacy/... — all 404.
443
+ Single endpoint /api/ctas/v3/placements/{guid} is the source for individual CTAs but UI uses the list endpoint for bulk read.
444
+ Adapter design — CTAs
445
+ Read source:
446
+
447
+ GET /api/ctas/v3/placements?limit=200&offset={n} — paginate (hasMore + offset).
448
+ Per CTA: full payload is in the list response (no per-CTA detail call needed for migration).
449
+ Filter:
450
+
451
+ isMigrated: true = on new CTA Builder (modern)
452
+ isMigrated: false + deletedAt: null = still on legacy
453
+ Write to dest (NOT in scope — sandbox required):
454
+
455
+ POST /api/ctas/v3/placements per CTA.
456
+ 2026-06-19 — Iteration 10: Social + Ads
457
+ Social
458
+ Method Path Status Response
459
+ GET /api/broadcast/v2/broadcasts?portalId={p}&limit={n} ✅ 200 Array[n] — 466KB for limit=100
460
+ GET /api/broadcast/v2/accounts/with-channels?portalId={p} ✅ Connected social accounts + channels (FB, IG, LinkedIn, TikTok, X, etc.).
461
+ GET /api/vetting/public/v1/user-verification/bannerinfo/scope/social-trusted ✅ Identity vetting banner.
462
+ broadcast is HubSpot's term for "social post" — the system is named after the original "broadcast" composer.
463
+
464
+ Ads (chirp-only)
465
+ Method Path Note
466
+ POST /api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.AdAccountRpc/getAdsAccountsOverviewForUser Ad accounts + overview metrics.
467
+ POST /api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.AdAccountRpc/getPortalMetaData Portal-wide ads metadata.
468
+ POST /api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.AuthRpc/getUserSummaries Ad-related user summaries.
469
+ POST /api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.UiSettingsRpc/getTableColumnInfo UI column config.
470
+ Ads is entirely RPC-driven. No REST endpoints found (/api/ads/v3/..., /api/ad-management/... all 404). Migration would need full chirp RPC client.
471
+
472
+ Adapter design — Social + Ads
473
+ Social:
474
+
475
+ GET /api/broadcast/v2/accounts/with-channels → connected channels (FB pages, LinkedIn orgs, etc.)
476
+ GET /api/broadcast/v2/broadcasts?limit=100&offset={n} → all broadcasts
477
+ Ads: requires chirp client. Out of scope for REST-based adapter — would need a chirp gateway transport layer.
478
+
479
+ 2026-06-19 — Iteration 11: CMS (design manager, pages, domains, membership)
480
+ Captured by opening /design-manager/<portalId>/.
481
+
482
+ Design Manager (themes / templates / modules / layouts)
483
+ Method Path Status Response Note
484
+ GET /api/designmanager/v1/themes?portalId={p} ✅ 200 4MB, ~120 themes Full theme catalogue incl. HubSpot defaults + custom.
485
+ GET /api/designmanager/v1/templates?portalId={p} ✅ 200 50KB, 7000 templates All HTML/HUBL templates.
486
+ GET /api/designmanager/v1/modules?portalId={p} ✅ 200 35KB, 2886 modules All design-manager modules.
487
+ GET /api/designmanager/v1/layouts?portalId={p} ✅ 200 53KB, 776 layouts Drag-and-drop layouts.
488
+ GET /api/designmanager/v1/layouts/global-groups/summaries ✅ Global header/footer/sidebar groups.
489
+ GET /api/designmanager/v1/modules/builtin-mapping ✅ Built-in module mapping.
490
+ GET /api/content/api/v2/templates?portalId={p} ✅ 200 2MB, 7000 templates Alt path (content/api/v2).
491
+ Pages
492
+ Method Path Status Response
493
+ GET /api/content/api/v2/pages?portalId={p}&limit={n} ✅ 200 2573 pages, 123KB for limit=5
494
+ Settings
495
+ Method Path Status
496
+ GET /api/content-settings/v1/site-settings/by-portal-id/{portal} ✅
497
+ Portal copy (HubSpot's OWN content migration RPC)
498
+ Method Path Status Note
499
+ GET /api/content-portal-copy/v1/copy/status?portalId={p} ✅ 200 Empty array currently. Returns copy-job statuses. This is the entry point to HubSpot's "Content Asset Copy" feature.
500
+ This is a major find — HubSpot has its own portal copy job system. Worth investigating in a separate iteration if we can trigger it externally.
501
+
502
+ What's NOT reachable in this portal
503
+ /api/cms/v3/... — all 404 (CMS Hub public API not enabled?)
504
+ /api/website-pages/v3/pages — 404
505
+ /api/file-manager/api/v3/files — 404 (try v2)
506
+ /api/cms-domains/... + /api/cos-domains/... + /api/domains/v1/... — all 404. Domain config truly is UI-only.
507
+ /api/membership/v3/... — 404 (membership not enabled on this portal)
508
+ Adapter design — CMS
509
+ Read source (CSRF):
510
+
511
+ Themes: GET /api/designmanager/v1/themes
512
+ Templates: GET /api/designmanager/v1/templates
513
+ Modules: GET /api/designmanager/v1/modules
514
+ Layouts: GET /api/designmanager/v1/layouts
515
+ Pages: GET /api/content/api/v2/pages?limit=100&offset={n} — paginate
516
+ Site settings: GET /api/content-settings/v1/site-settings/by-portal-id/{portal}
517
+ Write to dest (NOT in scope):
518
+
519
+ POST /api/designmanager/v1/{templates|modules|layouts|themes} per asset.
520
+ Gap closed: CMS templates, modules, layouts, themes, pages all reachable. Domains remain UI-only — no API surface for cert/DNS config (likely intentional for safety).
521
+
522
+ 2026-06-19 — Iteration 12: Sales content body (snippets, templates, playbooks)
523
+ Snippets ✅ (full fidelity)
524
+ GET /api/snippets/v1/snippets?portalId={p} → 200, Array[45]
525
+
526
+ Object shape:
527
+
528
+ {
529
+ "id": <id>,
530
+ "portalId": <portalId>,
531
+ "name": "GT | <client> | Capsula informativa",
532
+ "shortcut": "capsula",
533
+ "body": "Hola Contact: {{ contact.firstname }}, ...", // plain text
534
+ "htmlBody": "<div>Hola...</div>", // rendered HTML
535
+ "folderId": 471673,
536
+ "createdBy": <id>, "createdAt": <id>,
537
+ "modifiedBy": <id>, "modifiedAt": <id>,
538
+ "deletedAt": null, "deletedBy": null
539
+ }
540
+ Full body fidelity reachable. Both plain text and HTML version. Supports HubL personalization tokens ({{ contact.firstname }}).
541
+
542
+ Templates (sales-templates)
543
+ Method Path Status
544
+ GET /api/templates/v1/templates?portalId={p} ✅ 200 — works (45 templates in portal).
545
+ POST /api/sales-templates/v2/templates ✅ 400 (requires {name, ...} body) — create endpoint.
546
+ POST /api/sales-templates/v2/templates/search ✅ 400 (requires search body) — search endpoint.
547
+ POST /api/sales-templates/v2/templates/folder ✅ 400 — folder operations.
548
+ GET /api/sales-templates/v2/templates/usage?portalId={p} ✅ 200, {limit, userLimit, currentUsage} — quota check.
549
+ Playbooks ❌
550
+ All tried 404:
551
+
552
+ /api/playbooks/v3/playbooks, /api/playbooks/v1/playbooks
553
+ /api/playbook/v3/playbooks, /api/playbook-app/v3/playbooks
554
+ /api/playbooks-api/v3/playbooks, /api/playbooks-internal/...
555
+ /api/sales-content-app/v1/playbooks
556
+ Playbooks may not be enabled on this portal, or require a different probe (chirp RPC). Gap remains — needs sandbox investigation.
557
+
558
+ Sequences ❌
559
+ All tried 404 except /api/sequences/v2/sequences which returns 405 (Method Not Allowed for GET — needs POST body):
560
+
561
+ /api/sales/sequences/v3/sequences
562
+ /api/sales-emails/sequences/v3/sequences
563
+ /api/sales-emails-app/v3/sequences
564
+ /api/sequences-app/v3/sequences
565
+ Likely behind chirp RPC — com.hubspot.sales.sequences.rpc.* not yet probed.
566
+
567
+ Adapter design — sales content
568
+ Snippets: trivial — GET /api/snippets/v1/snippets → POST per snippet to dest.
569
+
570
+ Templates: use POST /api/sales-templates/v2/templates/search with {} body to list, POST to create. Body shape requires investigation in sandbox.
571
+
572
+ Sequences + playbooks: gap remains. Needs chirp RPC probe (sequences likely behind com.hubspot.sales.sequences.*).
573
+
574
+ 2026-06-19 — Iteration 13 + 14: Forecast categories + Quote e-signature + Payments
575
+ Both forecast and quote e-signature are NOT enabled on this portal (/forecast/<portalId>/ → 404). Most expected endpoints 404:
576
+
577
+ /api/forecasts/v3/*, /api/forecast/v3/*, /api/sales-forecast/* — all 404
578
+ /api/goals/v3/goals, /api/goals-app/v3/goals, /api/sales-goals/v3/goals — all 404
579
+ /api/forecast-categories/v3/categories — 404
580
+ /api/quote-app/v3/quotes, /api/quotes-app/v3/quotes, /api/quotes-esign/v1/esign, /api/electronic-signatures/v1/signatures — all 404
581
+ Quotes ARE accessible — they're a CRM object (typeId 0-14)
582
+ Method Path Status Response
583
+ POST /api/crm-search/search?portalId={p} body {objectTypeId:"0-14", count, requestOptions:{includeAllProperties:true}} ✅ 200 {results[], hasMore, offset, total: 37964}
584
+ GET /crm/v3/objects/0-14?properties=... ⚠️ requires properties param List quotes via public API.
585
+ Line items, products, fee — also CRM objects
586
+ Line items: typeId 0-8
587
+ Products: typeId 0-7
588
+ Discounts: separate endpoint TBD
589
+ Carts: typeId 0-114
590
+ Orders: separate
591
+ Payments (Stripe integration)
592
+ Method Path Status Response
593
+ GET /api/payments/v1/payment-accounts/eligibility?portalId={p} ✅ 200 {status, underwritingStatus, connectionType, processorType, errorCode, legacyStripeAccountCBSP, processorStatus, isEnabled}
594
+ GET /api/payments/v1/payment-accounts/connected-account/default?portalId={p} ✅ 200 {portalId, internalAccountId, underwritingStatus, active, state, accountType, isEnabled}
595
+ Adapter design — quotes + forecasts
596
+ Quotes: use CRM API (/api/crm-search/search with objectTypeId:"0-14" + properties list). For full fidelity: include all properties + associations to deals/contacts/companies/line items. Already covered by CRM adapter.
597
+ E-signature: not enabled on this portal — needs different portal to probe. Likely behind /api/electronic-signatures-app/ or /api/cosignatures/.
598
+ Forecast categories: not enabled on this portal. Needs Sales Hub Enterprise + Forecasting feature. Gap remains.
599
+ Summary — 14 iterations, gaps closed
600
+ Area Gap before Status after this loop
601
+ 1. Reports/Dashboards/Datasets Fidelity unclear ✅ Closed — /api/dashboard/v2/reports/{id} returns full def; /api/reporting/v3/dataset/resolve/async for data; HubSpot has its own /api/reporting-migration/v2/reports/migrate.
602
+ 2. Workflows v4 actions actions[] thought internal-only ✅ Closed — /api/automationplatform/v1/hybrid/{flowId} returns full def incl. actions[], triggers, branches. CSRF header pattern unlocks 401s.
603
+ 3. Service Hub Chatflows + SLA + KB ⚠️ Partial — chatflows visible via chirp; SLA/customer portal/KB not enabled on this portal (needs different portal).
604
+ 4. Permission set assignment Assignment endpoint found ✅ Closed — GET /api/app-users/v1/permission-sets/permission-set-user-assignments returns full map; POST PermissionSetAssignmentsRpc/assignUserToPermissionSets applies assignments.
605
+ 5. FLP replay Capture toggle bodies ✅ Closed — POST .../allowlistings/access-state/multi-get body shape {objectTypeIds:[]} returns full state. GET .../blocklist/including-global for blocklist.
606
+ 6. Subscription types non-email Chat/Call/Post adapter ✅ Closed — GET /api/email/public/v1/subscriptions returns ALL channels (245 in this portal: 146 Email, 19 Chat, 3 Call, 3 Post, 74 legacy).
607
+ 7. Brand kits No public API ✅ Closed — /api/branding/v1/brand-kit, /colors, /logos, /favicons, /fonts-with-variants all reachable with CSRF.
608
+ 8. Saved sections + D&D email Earlier 404'd ✅ Closed — /api/cosemail/v1/emails returns full email def incl. dragAndDropLayoutId, dragAndDropThemeId, flexAreas, emailBody. 18,722 emails.
609
+ 9. CTAs new builder Endpoint shape unknown ✅ Closed — /api/ctas/v3/placements returns full list + isMigrated flag. 2,244 CTAs.
610
+ 10. Social/Ads Partial, read-first ⚠️ Partial — Social via /api/broadcast/v2/broadcasts. Ads entirely chirp-RPC (com.hubspot.ads.rpc.*).
611
+ 11. CMS (themes/templates/domains) Mostly UI-only ✅ Themes/templates/modules/layouts reachable via /api/designmanager/v1/*. Domains REMAIN UI-only. HubSpot has /api/content-portal-copy/v1/copy/status — its own content copy job system.
612
+ 12. Sales playbooks/snippets/templates Body fidelity unclear ⚠️ Partial — Snippets full fidelity via /api/snippets/v1/snippets (body + htmlBody + shortcut). Templates v1 + sales-templates v2 reachable. Playbooks + Sequences gap remains (likely chirp RPC).
613
+ 13. Forecast categories Not first-class ❌ Not enabled on this portal — needs sandbox with Sales Hub Enterprise Forecasting.
614
+ 14. Quote e-signature No public API ❌ Not enabled on this portal. Quotes themselves accessible via CRM (/api/crm-search/search typeId 0-14, 37,964 quotes).
615
+ 🔑 Most important discovery this loop
616
+ The CSRF pattern: read cookie hubspotapi-csrf, send as header X-HubSpot-CSRF-hubspotapi. This unlocks ALL 401-blocked internal endpoints — including the full workflow definition, permission set assignments, FLP state, brand kits, etc.
617
+
618
+ const csrf = document.cookie.split('; ').find(c => c.startsWith('hubspotapi-csrf='))?.split('=')[1];
619
+ const r = await fetch(url, { credentials: 'include', headers: { 'X-HubSpot-CSRF-hubspotapi': csrf } });
620
+ Apply throughout the hscli adapter layer for any internal endpoint that previously returned 401.
621
+
622
+ Adapter gaps still open
623
+ These need a sandbox portal to confirm write endpoints (we stayed read-only on live):
624
+
625
+ POST/PUT verbs for automationplatform/v1/hybrid (workflow import)
626
+ Permission set user-assignment write
627
+ FLP allowlist write
628
+ Brand kit create
629
+ Subscription type create
630
+ CTA placement create
631
+ All design-manager writes
632
+ Snippet/template create
633
+ These need a different portal where the feature is enabled:
634
+
635
+ Service Hub: SLA policies, customer portal, KB
636
+ Forecast categories (Sales Hub Enterprise + Forecasting)
637
+ Quote e-signature
638
+ Membership / gated content
639
+ Playbooks (likely Sales Hub Enterprise)
640
+ These remain UI-only (confirmed):
641
+
642
+ CMS domain TLS / cert config (intentional safety)
643
+ HubSpot's /api/content-portal-copy/v1/copy/* job triggering (only /status is GET-able)
644
+ 2026-06-19 — Iteration 15: Playbook + Sequence body export (gaps closed)
645
+ Both sequence body and playbook body export confirmed working with CSRF.
646
+
647
+ Sequences — full body
648
+ Method Path Status Note
649
+ POST /api/salescontentsearch/v2/search?portalId={p} body {query:"", limit:100, offset:N} ✅ 200 List endpoint. Returns mixed content (DOCUMENT, TEMPLATE, SEQUENCE, DOCUMENTS_FOLDER, etc.). Filter contentType === 'SEQUENCE' client-side. 1,501 items total. Paginate with offset.
650
+ GET /api/sequences/v3/sequences/{sequenceId}?portalId={p} ✅ 200 Full sequence body — see shape below.
651
+ Sequence detail shape (/api/sequences/v3/sequences/{id} → {sequence, userView}):
652
+
653
+ {
654
+ sequence: {
655
+ id: <id>,
656
+ portalId, folderId, userId,
657
+ name: "4th QTR Grocery Sequence",
658
+ active: "ACTIVE",
659
+ dynamic: false,
660
+ createdAt, updatedAt, deletedAt,
661
+
662
+ steps: [
663
+ {
664
+ id, sequenceId, portalId,
665
+ stepOrder: 0,
666
+ actionType: "EMAIL", // or "TASK", "FINISH_ENROLLMENT", etc.
667
+ delayMillis: <id>,
668
+ delayMillisMin, delayMillisMax,
669
+ branchNumber: 0,
670
+ emailPattern: { // ← references a Template by ID
671
+ id, sequenceId, sequenceStepId,
672
+ templateId: <id>,
673
+ threadEmailToStepOrder: 0, // reply-to-step linkage
674
+ activeType: "ACTIVE",
675
+ createdAt, updatedAt, deletedAt
676
+ },
677
+ taskPattern: null, // populated when actionType === "TASK"
678
+ variants: [],
679
+ variantMeta: null
680
+ },
681
+ // ...
682
+ ],
683
+
684
+ settings: {
685
+ id, sequenceId,
686
+ sellingStrategy: "LEAD_BASED",
687
+ eligibleFollowUpDays: "BUSINESS_DAYS",
688
+ sendWindowStartMinute: 480, // 8:00 AM
689
+ sendWindowEndMinute: 1080, // 6:00 PM
690
+ taskReminderMinute: 480,
691
+ individualTaskRemindersEnabled: false,
692
+ smartDelaysEnabled: false,
693
+ unenrollmentSettings: {
694
+ emailSettings: { criteria: "ALL", sellingStrategy: "LEAD_BASED" },
695
+ meetingSettings: { criteria: "ALL", sellingStrategy: "LEAD_BASED" }
696
+ }
697
+ },
698
+
699
+ engagementTriggers: null, // auto-unenroll-on-engagement config
700
+ dependencies: [] // what this sequence depends on
701
+ },
702
+ userView: { firstName, lastName, email } // sequence owner
703
+ }
704
+ Sequence content list item shape (salescontentsearch result with contentType:"SEQUENCE")
705
+ {
706
+ contentType: "SEQUENCE",
707
+ contentId: <id>, // ← sequence ID for /api/sequences/v3/sequences/{contentId}
708
+ portalId, userId, teamId, folderId,
709
+ name, description, shortcut,
710
+ documentId: "<portalId>_101799545_2",
711
+ partOfAPlay: false,
712
+ visibleToAll: false,
713
+ permissionedTeamIds: [...],
714
+ permissionedUserIds: [...],
715
+ userView: { email, firstName, lastName },
716
+ createdAt, updatedAt, lastUsedAt
717
+ }
718
+ Sequence export — full pipeline
719
+ // 1. Get all sequences (paginate salescontentsearch)
720
+ const sequences = [];
721
+ let offset = 0;
722
+ while (true) {
723
+ const r = await fetch('/api/salescontentsearch/v2/search?portalId=<portalId>', {
724
+ credentials: 'include',
725
+ headers: { 'X-HubSpot-CSRF-hubspotapi': csrf, 'Content-Type': 'application/json' },
726
+ method: 'POST',
727
+ body: JSON.stringify({ query: '', limit: 100, offset })
728
+ });
729
+ const { results, hasMore } = await r.json();
730
+ sequences.push(...results.filter(r => r.contentType === 'SEQUENCE'));
731
+ if (!hasMore) break;
732
+ offset += 100;
733
+ }
734
+ // 2. Per sequence: get full body
735
+ for (const seq of sequences) {
736
+ const r = await fetch(`/api/sequences/v3/sequences/${seq.contentId}?portalId=<portalId>`, {
737
+ credentials: 'include',
738
+ headers: { 'X-HubSpot-CSRF-hubspotapi': csrf }
739
+ });
740
+ const fullBody = await r.json();
741
+ // fullBody.sequence.steps[].emailPattern.templateId references templates
742
+ // Pull template bodies via /api/templates/v1/templates or /api/snippets/v1/snippets
743
+ }
744
+ Playbooks — full body (chirp RPC)
745
+ All playbook surface is behind com.hubspot.playbooks.rpc.PlaybooksRpc chirp RPCs. Found by reading network traffic on /playbooks/{portal}/compose/{playbookId}.
746
+
747
+ RPC Body Returns
748
+ getPlaybookCount {} {count: 3, limit: 5000}
749
+ listPlaybooks {} {playbooks: [...]} — full playbook bodies inline
750
+ getLatestPlaybookVersion {playbookId} Latest version body
751
+ getPlaybookVersionSummaries {playbookId} Version history (all versions)
752
+ All via:
753
+
754
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybooksRpc/{methodName}?portalId={p}
755
+ Headers: X-HubSpot-CSRF-hubspotapi: ...
756
+ Body: {playbookId?: number, limit?: number}
757
+ Chirp response wrapper: {type:"data", data:{...}, correlationId}. Real payload is in .data.
758
+
759
+ Playbook body shape (listPlaybooks response item)
760
+ {
761
+ id: <id>,
762
+ portalId: <portalId>,
763
+ title: "FI | Playbook (Example)",
764
+ playbookType: "HOW_TO", // or "CHECKLIST" etc.
765
+ publicationStage: "PUBLISHED_WITH_EDITS", // or "DRAFT", "PUBLISHED"
766
+ engagementTypes: ["CALL"], // CALL / MEETING / NOTE / TASK / EMAIL
767
+ visibleOn: ["EVERYWHERE"],
768
+ displayPreference: "SHOW_EVERYWHERE",
769
+ crmObjectId: <id>,
770
+ body: "{\"blocks\":[{\"key\":\"b9ife\",\"text\":\"...\",\"type\":\"unstyled\",...}]}", // Draft.js JSON
771
+ htmlBody: "<p>...</p><question qid=\"<id>\"></question>...", // rendered HTML
772
+ createdAt, createdBy,
773
+ updatedAt, updatedBy
774
+ }
775
+ Body format: Draft.js JSON in .body (block-by-block model), pre-rendered HTML in .htmlBody. Questions are linked via <question qid="..."> tags in htmlBody — resolve those via PlaybookQuestionsRpc/getLatestQuestionsByPlaybook.
776
+
777
+ Playbook questions
778
+ POST /api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybookQuestionsRpc/getLatestQuestionsByPlaybook?portalId={p}
779
+ Body: { playbookId: <id> }
780
+ → { type: "data", data: { questions: [
781
+ {
782
+ id: <id>, portalId, playbookId,
783
+ state: "DRAFT", // or "PUBLISHED"
784
+ version: 1,
785
+ answerType: "QUICK_REPLIES", // or "TEXT", "ENUMERATION", etc.
786
+ question: "What are the current challenges you are facing?",
787
+ quickAnswers: ["Cleanliness", "Food safety procedures", "Management", ...]
788
+ }
789
+ ]
790
+ }}
791
+ Playbook export — full pipeline
792
+ // 1. List all playbooks (body included)
793
+ const r = await fetch('/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybooksRpc/listPlaybooks?portalId=<portalId>', {
794
+ credentials: 'include',
795
+ headers: { 'X-HubSpot-CSRF-hubspotapi': csrf, 'Content-Type': 'application/json' },
796
+ method: 'POST',
797
+ body: JSON.stringify({})
798
+ });
799
+ const { data: { playbooks } } = await r.json();
800
+
801
+ // 2. Per playbook: get questions
802
+ for (const pb of playbooks) {
803
+ const qr = await fetch('/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybookQuestionsRpc/getLatestQuestionsByPlaybook?portalId=<portalId>', {
804
+ credentials: 'include', headers: { 'X-HubSpot-CSRF-hubspotapi': csrf, 'Content-Type': 'application/json' },
805
+ method: 'POST', body: JSON.stringify({ playbookId: pb.id })
806
+ });
807
+ pb.questions = (await qr.json()).data.questions;
808
+ }
809
+ Gaps closed
810
+ Asset Body export Notes
811
+ Sequence ✅ Full GET /api/sequences/v3/sequences/{id} — includes steps, settings, email templates by ID, send window, unenrollment rules, dependencies
812
+ Playbook ✅ Full listPlaybooks RPC returns body + htmlBody inline. Plus questions via separate RPC.
813
+ Caveat: Sequence steps reference templates by ID; full fidelity requires also fetching template bodies. Same for playbook questions.