@revfleet/hscli 0.8.11 → 0.9.0
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.
- package/CHANGELOG.md +186 -0
- package/README.md +223 -10
- package/dist/cli.js +29 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent/contracts.d.ts +3 -0
- package/dist/commands/agent/contracts.js +139 -0
- package/dist/commands/agent/contracts.js.map +1 -0
- package/dist/commands/agent/index.d.ts +3 -0
- package/dist/commands/agent/index.js +188 -0
- package/dist/commands/agent/index.js.map +1 -0
- package/dist/commands/agent/recipes.d.ts +13 -0
- package/dist/commands/agent/recipes.js +108 -0
- package/dist/commands/agent/recipes.js.map +1 -0
- package/dist/commands/agent/runner.d.ts +16 -0
- package/dist/commands/agent/runner.js +165 -0
- package/dist/commands/agent/runner.js.map +1 -0
- package/dist/commands/auth/index.js +64 -7
- package/dist/commands/auth/index.js.map +1 -1
- package/dist/commands/automation/index.js +30 -0
- package/dist/commands/automation/index.js.map +1 -1
- package/dist/commands/backup/index.d.ts +12 -0
- package/dist/commands/backup/index.js +459 -0
- package/dist/commands/backup/index.js.map +1 -0
- package/dist/commands/cms/content-ops.js +1 -1
- package/dist/commands/cms/content-ops.js.map +1 -1
- package/dist/commands/communication-preferences/index.js +207 -7
- package/dist/commands/communication-preferences/index.js.map +1 -1
- package/dist/commands/content-permissions/index.d.ts +22 -0
- package/dist/commands/content-permissions/index.js +141 -0
- package/dist/commands/content-permissions/index.js.map +1 -0
- package/dist/commands/context/index.d.ts +3 -0
- package/dist/commands/context/index.js +182 -0
- package/dist/commands/context/index.js.map +1 -0
- package/dist/commands/crm/associations.js +38 -0
- package/dist/commands/crm/associations.js.map +1 -1
- package/dist/commands/crm/custom-objects.js +39 -2
- package/dist/commands/crm/custom-objects.js.map +1 -1
- package/dist/commands/crm/dated.js +3 -3
- package/dist/commands/crm/dated.js.map +1 -1
- package/dist/commands/crm/migration.js +210 -2
- package/dist/commands/crm/migration.js.map +1 -1
- package/dist/commands/crm/properties.js +2 -1
- package/dist/commands/crm/properties.js.map +1 -1
- package/dist/commands/crm/property-batch.d.ts +1 -0
- package/dist/commands/crm/property-batch.js +44 -5
- package/dist/commands/crm/property-batch.js.map +1 -1
- package/dist/commands/crm/shared.js +21 -2
- package/dist/commands/crm/shared.js.map +1 -1
- package/dist/commands/crm/sync.js +1 -1
- package/dist/commands/crm/sync.js.map +1 -1
- package/dist/commands/doctor/index.js +193 -0
- package/dist/commands/doctor/index.js.map +1 -1
- package/dist/commands/email-tracking/index.d.ts +16 -0
- package/dist/commands/email-tracking/index.js +314 -0
- package/dist/commands/email-tracking/index.js.map +1 -0
- package/dist/commands/extensions/index.js +1 -1
- package/dist/commands/extensions/index.js.map +1 -1
- package/dist/commands/forms/index.js +15 -2
- package/dist/commands/forms/index.js.map +1 -1
- package/dist/commands/forms/legacy-v2.d.ts +7 -2
- package/dist/commands/forms/legacy-v2.js +103 -7
- package/dist/commands/forms/legacy-v2.js.map +1 -1
- package/dist/commands/guide/index.d.ts +3 -0
- package/dist/commands/guide/index.js +75 -16
- package/dist/commands/guide/index.js.map +1 -1
- package/dist/commands/internal/catalog.d.ts +1 -0
- package/dist/commands/internal/catalog.js +2 -0
- package/dist/commands/internal/catalog.js.map +1 -0
- package/dist/commands/internal/index.d.ts +26 -0
- package/dist/commands/internal/index.js +823 -0
- package/dist/commands/internal/index.js.map +1 -0
- package/dist/commands/internal-session.d.ts +15 -0
- package/dist/commands/internal-session.js +152 -0
- package/dist/commands/internal-session.js.map +1 -0
- package/dist/commands/lists/index.js +1 -1
- package/dist/commands/lists/index.js.map +1 -1
- package/dist/commands/marketing/email-audit.d.ts +32 -0
- package/dist/commands/marketing/email-audit.js +121 -0
- package/dist/commands/marketing/email-audit.js.map +1 -0
- package/dist/commands/marketing/index.js +2 -0
- package/dist/commands/marketing/index.js.map +1 -1
- package/dist/commands/mcp.js +5 -3
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/playbooks/index.d.ts +3 -0
- package/dist/commands/playbooks/index.js +228 -0
- package/dist/commands/playbooks/index.js.map +1 -0
- package/dist/commands/portals/index.d.ts +3 -0
- package/dist/commands/portals/index.js +132 -0
- package/dist/commands/portals/index.js.map +1 -0
- package/dist/commands/reports/index.d.ts +3 -0
- package/dist/commands/reports/index.js +479 -0
- package/dist/commands/reports/index.js.map +1 -0
- package/dist/commands/sales-content/index.d.ts +13 -0
- package/dist/commands/sales-content/index.js +59 -0
- package/dist/commands/sales-content/index.js.map +1 -0
- package/dist/commands/sales-templates/index.d.ts +3 -0
- package/dist/commands/sales-templates/index.js +88 -0
- package/dist/commands/sales-templates/index.js.map +1 -0
- package/dist/commands/seed/cms.js +28 -45
- package/dist/commands/seed/cms.js.map +1 -1
- package/dist/commands/sequences/index.d.ts +18 -0
- package/dist/commands/sequences/index.js +263 -0
- package/dist/commands/sequences/index.js.map +1 -0
- package/dist/commands/settings/index.js +450 -102
- package/dist/commands/settings/index.js.map +1 -1
- package/dist/commands/settings/site-settings.d.ts +3 -0
- package/dist/commands/settings/site-settings.js +106 -0
- package/dist/commands/settings/site-settings.js.map +1 -0
- package/dist/commands/ui/index.d.ts +3 -0
- package/dist/commands/ui/index.js +422 -0
- package/dist/commands/ui/index.js.map +1 -0
- package/dist/commands/workflows/index.js +2 -0
- package/dist/commands/workflows/index.js.map +1 -1
- package/dist/commands/workflows/preflight.d.ts +23 -0
- package/dist/commands/workflows/preflight.js +295 -0
- package/dist/commands/workflows/preflight.js.map +1 -0
- package/dist/core/action-contract.d.ts +43 -0
- package/dist/core/action-contract.js +207 -0
- package/dist/core/action-contract.js.map +1 -0
- package/dist/core/auth.d.ts +6 -0
- package/dist/core/auth.js +85 -0
- package/dist/core/auth.js.map +1 -1
- package/dist/core/http.d.ts +8 -0
- package/dist/core/http.js +37 -5
- package/dist/core/http.js.map +1 -1
- package/dist/core/id-maps.d.ts +27 -0
- package/dist/core/id-maps.js +204 -0
- package/dist/core/id-maps.js.map +1 -0
- package/dist/core/output.d.ts +2 -1
- package/dist/core/output.js +34 -0
- package/dist/core/output.js.map +1 -1
- package/dist/mcp/profiles.d.ts +14 -0
- package/dist/mcp/profiles.js +68 -0
- package/dist/mcp/profiles.js.map +1 -0
- package/dist/mcp/server.d.ts +5 -3
- package/dist/mcp/server.js +22 -5
- package/dist/mcp/server.js.map +1 -1
- package/docs/CAPABILITY_LIBRARY.md +29 -8
- package/docs/COMMAND_TREE.md +98 -17
- package/docs/COMPARISON.md +8 -5
- package/docs/COOKBOOK.md +127 -5
- package/docs/INTERNAL-ENDPOINTS-DISCOVERY.md +813 -0
- package/docs/INTERNAL-ENDPOINTS.md +371 -0
- package/docs/INTERNAL_ENDPOINTS.md +243 -0
- package/docs/MARKETING_SETUP.md +21 -1
- package/docs/MCP.md +28 -89
- package/docs/MCP_TOOL_CATALOG.md +192 -0
- package/docs/MIGRATION_ADAPTERS.md +115 -0
- package/docs/PORTAL_BACKUP.md +212 -0
- package/docs/PUBLISHING.md +1 -1
- package/docs/SALES_CONTENT_INTERNAL_PUNCH_LIST.md +504 -0
- package/docs/TUTORIALS/secure-agent-writes.md +49 -0
- package/docs/WHY_HOW_WHAT.md +10 -4
- package/package.json +4 -2
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { CliError, printResult, redactSensitive } from "../../core/output.js";
|
|
5
|
+
import { addBrowserSessionOptions, resolveBrowserSession } from "../internal-session.js";
|
|
6
|
+
export const INTERNAL_CAPTURE_GUARDRAILS = [
|
|
7
|
+
"Public API first; use internal capture only for HubSpot UI-only gaps.",
|
|
8
|
+
"No generic internal passthrough: every endpoint must be declared in the allowlist.",
|
|
9
|
+
"Capture is read-only: GET plus HubSpot read-only POST/RPC/search/preview calls only.",
|
|
10
|
+
"Missing IDs, request bodies, or permissions are skipped and written to the manifest.",
|
|
11
|
+
"Never store browser cookies or CSRF tokens in output files.",
|
|
12
|
+
];
|
|
13
|
+
export const INTERNAL_SURFACES = [
|
|
14
|
+
{
|
|
15
|
+
id: "business-units",
|
|
16
|
+
label: "Business units",
|
|
17
|
+
mode: "internal-browser-session",
|
|
18
|
+
description: "Business-unit IDs and default business-unit context.",
|
|
19
|
+
endpoints: [
|
|
20
|
+
{
|
|
21
|
+
id: "business-units",
|
|
22
|
+
method: "GET",
|
|
23
|
+
path: "/api/business-units/v1/business-units?portalId={portalId}",
|
|
24
|
+
purpose: "List business units and default business-unit context.",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "branding",
|
|
30
|
+
label: "Brand kits and multi-brand identity",
|
|
31
|
+
mode: "internal-browser-session",
|
|
32
|
+
description: "Brand kits, brand metadata, and multi-brand settings.",
|
|
33
|
+
endpoints: [
|
|
34
|
+
{
|
|
35
|
+
id: "brand-kit",
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: "/api/branding/v1/brand-kit?portalId={portalId}",
|
|
38
|
+
purpose: "Brand kit catalog and BU-linked brand metadata.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "brand-kit-single",
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: "/api/branding/v1/brand-kit/{brandKitId}",
|
|
44
|
+
purpose: "Single brand kit detail.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "brand-kit-business-unit",
|
|
48
|
+
method: "GET",
|
|
49
|
+
path: "/api/branding/v1/brand-kit/business-unit/{businessUnitId}",
|
|
50
|
+
purpose: "Business-unit scoped brand kit detail.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "multi-brand-settings",
|
|
54
|
+
method: "GET",
|
|
55
|
+
path: "/api/multi-brand/v1/brands?portalId={portalId}",
|
|
56
|
+
purpose: "Multi-brand UI inventory when enabled.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "brand-kit-colors",
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: "/api/branding/v1/colors/brand-kit?portalId={portalId}",
|
|
62
|
+
purpose: "Brand kit color palette metadata for backup and replay planning.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "brand-kit-logos",
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: "/api/branding/v1/logos/brand-kit?portalId={portalId}",
|
|
68
|
+
purpose: "Brand kit logos for asset backup and remapping.",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "brand-kit-favicons",
|
|
72
|
+
method: "GET",
|
|
73
|
+
path: "/api/branding/v1/favicons/brand-kit?portalId={portalId}",
|
|
74
|
+
purpose: "Brand kit favicons for CMS/email identity backup.",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "brand-kit-fonts",
|
|
78
|
+
method: "GET",
|
|
79
|
+
path: "/api/branding/v1/fonts-with-variants?portalId={portalId}",
|
|
80
|
+
purpose: "Brand kit font families and variants.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "brand-kit-font-meta",
|
|
84
|
+
method: "GET",
|
|
85
|
+
path: "/api/branding/v1/font-meta?portalId={portalId}",
|
|
86
|
+
purpose: "Brand font metadata when enabled.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "brand-voice",
|
|
90
|
+
method: "GET",
|
|
91
|
+
path: "/api/branding/v1/brand-voice/by-bu-id/{businessUnitId}",
|
|
92
|
+
purpose: "Business-unit brand voice metadata when the AI copy UI is enabled.",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "permission-access",
|
|
98
|
+
label: "Users, teams, permission sets, roles, seats, and FLP",
|
|
99
|
+
mode: "internal-browser-session",
|
|
100
|
+
description: "Access model capture for permission migration, downgrade proof, and governance audits.",
|
|
101
|
+
endpoints: [
|
|
102
|
+
{ id: "app-users", method: "GET", path: "/api/app-users/v1/users?portalId={portalId}&limit=200", purpose: "Full app-user payloads with roleNames and permission-set IDs." },
|
|
103
|
+
{ id: "app-user", method: "GET", path: "/api/app-users/v1/users/{userId}?portalId={portalId}", purpose: "Single app-user payload with roleNames, permission-set IDs, teams, status, and seat state." },
|
|
104
|
+
{ id: "integrator-users", method: "GET", path: "/api/integrators/v1/{portalId}/info/users", purpose: "Full user ID-to-name/email/deactivation map used by older settings UIs." },
|
|
105
|
+
{ id: "teams", method: "GET", path: "/api/app-users/v1/teams?portalId={portalId}", purpose: "Internal team hierarchy and membership." },
|
|
106
|
+
{ id: "team-hub-settings", method: "GET", path: "/api/teams/v1/team-hub-settings?portalId={portalId}", purpose: "Team hub-level settings such as default content assignment." },
|
|
107
|
+
{ id: "permission-sets", method: "GET", path: "/api/app-users/v1/permission-sets?portalId={portalId}", purpose: "Permission-set definitions with roleNames." },
|
|
108
|
+
{ id: "permission-set", method: "GET", path: "/api/app-users/v1/permission-sets/{permissionSetId}?portalId={portalId}", purpose: "Single permission-set definition with capability flags." },
|
|
109
|
+
{ id: "permission-set-assignments", method: "GET", path: "/api/app-users/v1/permission-sets/permission-set-user-assignments?portalId={portalId}", purpose: "User-to-permission-set assignment map." },
|
|
110
|
+
{ id: "roles", method: "GET", path: "/api/app-users/v1/roles?portalId={portalId}", purpose: "Atomic role catalog." },
|
|
111
|
+
{ id: "role", method: "GET", path: "/api/app-users/v1/roles/{roleName}?portalId={portalId}", purpose: "Single atomic role, description, and dependency metadata." },
|
|
112
|
+
{ id: "role-categories", method: "GET", path: "/api/app-users/v1/role-categories?portalId={portalId}", purpose: "Role category UI grouping." },
|
|
113
|
+
{ id: "role-scopes", method: "GET", path: "/api/app-users/v1/scopes?portalId={portalId}", purpose: "Owned/team/all/unassigned scope buckets." },
|
|
114
|
+
{ id: "seats", method: "GET", path: "/api/app-users/v1/seats?portalId={portalId}", purpose: "Seat type catalog." },
|
|
115
|
+
{ id: "seat-assignments", method: "GET", path: "/api/app-users/v1/seat-assignments?portalId={portalId}", purpose: "Seat assignments by user." },
|
|
116
|
+
{ id: "seat-assignment", method: "GET", path: "/api/app-users/v1/seat-assignments/{userId}?portalId={portalId}", purpose: "Single user's seat assignment state." },
|
|
117
|
+
{ id: "permission-templates", method: "GET", path: "/api/permission-templates/v1/templates?portalId={portalId}", purpose: "HubSpot/custom permission templates." },
|
|
118
|
+
{ id: "permission-template", method: "GET", path: "/api/permission-templates/v1/templates/{templateId}?portalId={portalId}", purpose: "Single permission template definition." },
|
|
119
|
+
{ id: "permission-set-options", method: "GET", path: "/api/external-options/v3/fetch/PERMISSION_SET", purpose: "Permission-set typeahead options used by settings UI." },
|
|
120
|
+
{ id: "team-options", method: "GET", path: "/api/external-options/v3/fetch/TEAM", purpose: "Team typeahead options used by settings UI." },
|
|
121
|
+
{ id: "flp-allowlistings", method: "GET", path: "/api/crm-permissions/v1/field-level-permissions/allowlistings", purpose: "Field-level permission allowlist inventory." },
|
|
122
|
+
{ id: "flp-allowlistings-object", method: "GET", path: "/api/crm-permissions/v1/field-level-permissions/allowlistings/{typeId}", purpose: "Field-level permission allowlist for one CRM object type." },
|
|
123
|
+
{ id: "flp-allowlisting-property", method: "GET", path: "/api/crm-permissions/v1/field-level-permissions/allowlistings/{typeId}/{propertyName}", purpose: "Field-level permission allowlist for one property." },
|
|
124
|
+
{ id: "flp-blocklist", method: "GET", path: "/api/crm-permissions/v1/field-level-permissions/blocklist/including-global", purpose: "Field-level permission blocklist." },
|
|
125
|
+
{ id: "flp-bypass-users", method: "GET", path: "/api/permissions-skynet/v1/scopes/app/field-level-permissions-bypass/user-ids", purpose: "Users bypassing FLP." },
|
|
126
|
+
{ id: "app-users-batch", method: "POST", path: "/api/app-users/v1/users/partial/batch?portalId={portalId}", purpose: "Read-only batch user hydration.", readOnlyKind: "batch-read", bodyRequired: true },
|
|
127
|
+
{ id: "flp-access-state", method: "POST", path: "/api/crm-permissions/v1/field-level-permissions/allowlistings/access-state/multi-get?portalId={portalId}", purpose: "Read-only bulk field-level-permission access-state check.", readOnlyKind: "batch-read", bodyRequired: true },
|
|
128
|
+
{ id: "permission-audit-user-access", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.app.user.audits.service.rpc.AppUserAuditsRpc/getUserAccessAudit", purpose: "Read-only user access audit RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
129
|
+
{ id: "permission-set-assignment-history", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.app.user.audits.service.rpc.AppUserAuditsRpc/getPermissionSetAssignmentHistory", purpose: "Read-only permission-set assignment history RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
130
|
+
{ id: "role-change-history", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.app.user.audits.service.rpc.AppUserAuditsRpc/getRoleChangeHistory", purpose: "Read-only role change history RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "datasets-reporting",
|
|
135
|
+
label: "Datasets, reportable sources, and SQL reporting metadata",
|
|
136
|
+
mode: "internal-browser-session",
|
|
137
|
+
description: "Data Studio definitions, joins, reportable properties, and preview metadata.",
|
|
138
|
+
endpoints: [
|
|
139
|
+
{ id: "datasets", method: "GET", path: "/sql-reporting/v1/datasets", purpose: "Dataset catalog." },
|
|
140
|
+
{ id: "dataset", method: "GET", path: "/sql-reporting/v1/datasets/{datasetId}", purpose: "Dataset sources, joins, formulas, and filters." },
|
|
141
|
+
{ id: "global-dataset", method: "GET", path: "/sql-reporting/v1/global-datasets/{datasetId}", purpose: "Global dataset definition." },
|
|
142
|
+
{ id: "dataset-functions", method: "GET", path: "/sql-reporting/v1/datasets/functions", purpose: "Dataset formula/function catalog." },
|
|
143
|
+
{ id: "dataset-resolve-result", method: "GET", path: "/api/reporting/v3/dataset/resolve/async/{requestId}", purpose: "Poll a read-only async dataset/report render result." },
|
|
144
|
+
{ id: "reportable-sources", method: "GET", path: "/sql-reporting/v1/extended-reportable-options/data-sources", purpose: "Reportable data sources." },
|
|
145
|
+
{ id: "reportable-joins", method: "GET", path: "/sql-reporting/v1/extended-reportable-options/data-sources/available-joins-with-metadata?withVariants=true", purpose: "Data Studio join graph." },
|
|
146
|
+
{ id: "reportable-properties", method: "GET", path: "/sql-reporting/v1/extended-reportable-options/data-sources/properties/{objectTypeId}", purpose: "Reportable properties for one source." },
|
|
147
|
+
{ id: "multi-currency-information", method: "GET", path: "/api/multi-currency/v2/currencies/information", purpose: "Currency metadata used by revenue reports." },
|
|
148
|
+
{ id: "external-owner-options", method: "GET", path: "/api/external-options/v3/pagedFetch/OWNER", purpose: "Owner option metadata used by report filters." },
|
|
149
|
+
{ id: "reporting-insights-abtest-options", method: "GET", path: "/api/reporting-insights/copilot/v1/abtest/options", purpose: "Reporting insights feature-gate metadata." },
|
|
150
|
+
{ id: "dataset-preview", method: "POST", path: "/api/sql-reporting-query/v1/datasets/preview", purpose: "Read-only dataset preview.", readOnlyKind: "preview", bodyRequired: true },
|
|
151
|
+
{ id: "dataset-resolve", method: "POST", path: "/api/reporting/v3/dataset/resolve/async", purpose: "Read-only report/dataset resolver.", readOnlyKind: "resolve", bodyRequired: true },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "reports-dashboards",
|
|
156
|
+
label: "Reports, dashboards, widgets, permissions, and saved report views",
|
|
157
|
+
mode: "internal-browser-session",
|
|
158
|
+
description: "Report definitions and dashboard layouts that public endpoints often omit.",
|
|
159
|
+
endpoints: [
|
|
160
|
+
{ id: "favorites", method: "GET", path: "/api/dashboard/v2/favorite/all", purpose: "Starred report/dashboard inventory." },
|
|
161
|
+
{ id: "reports", method: "GET", path: "/api/dashboard/v2/reports", purpose: "Internal report catalog." },
|
|
162
|
+
{ id: "reports-list", method: "GET", path: "/api/dashboard/v2/reports/list", purpose: "Alternate internal report catalog." },
|
|
163
|
+
{ id: "reports-search", method: "GET", path: "/api/dashboard/v2/reports/search", purpose: "Report search endpoint used by the reports UI." },
|
|
164
|
+
{ id: "report", method: "GET", path: "/api/dashboard/v2/reports/{reportId}", purpose: "Full report definition and chart config." },
|
|
165
|
+
{ id: "report-record-view", method: "GET", path: "/api/dashboard/v2/reports/{reportId}/record-view", purpose: "Report view telemetry/read state captured by the report UI." },
|
|
166
|
+
{ id: "report-history", method: "GET", path: "/api/dashboard/v2/reports/{reportId}/history", purpose: "Report version history." },
|
|
167
|
+
{ id: "report-audit", method: "GET", path: "/api/dashboard/v2/reports/{reportId}/audit", purpose: "Per-report audit trail." },
|
|
168
|
+
{ id: "report-permissions", method: "GET", path: "/api/dashboard/v2/reports/{reportId}/permissions", purpose: "Report sharing and grants." },
|
|
169
|
+
{ id: "report-permissions-v1", method: "GET", path: "/api/reports-permissions/v1/permissions/report/{reportId}", purpose: "Report permission state fallback when dashboard v2 permissions omit grants." },
|
|
170
|
+
{ id: "reports-permission-probe", method: "GET", path: "/api/dashboard/v2/reports/permission", purpose: "Report permission probe endpoint used by the reports UI." },
|
|
171
|
+
{ id: "report-counts", method: "GET", path: "/api/dashboard/v2/reports/report-counts", purpose: "Report counts per list view." },
|
|
172
|
+
{ id: "reporting-portal-reports", method: "GET", path: "/api/reporting-frontend/v1/reports/get-portal-reports", purpose: "Portal-wide report list from reporting frontend." },
|
|
173
|
+
{ id: "report-template", method: "GET", path: "/api/reporting-discovery/v1/report-template", purpose: "Built-in report template catalog." },
|
|
174
|
+
{ id: "report-template-paged", method: "GET", path: "/api/reporting-discovery/v1/report-template/paged", purpose: "Paged built-in report template catalog." },
|
|
175
|
+
{ id: "report-template-key", method: "GET", path: "/api/reporting-discovery/v1/report-template/template-key/{templateKey}", purpose: "Single built-in report template definition." },
|
|
176
|
+
{ id: "dashboards", method: "GET", path: "/api/dashboard/v2/dashboard", purpose: "Internal dashboard catalog." },
|
|
177
|
+
{ id: "dashboards-list", method: "GET", path: "/api/dashboard/v2/dashboard/list", purpose: "Alternate internal dashboard catalog." },
|
|
178
|
+
{ id: "dashboards-search", method: "GET", path: "/api/dashboard/v2/dashboard/search", purpose: "Dashboard search endpoint used by the dashboards UI." },
|
|
179
|
+
{ id: "dashboards-total-count", method: "GET", path: "/api/dashboard/v2/dashboard/total-count", purpose: "Dashboard quick count." },
|
|
180
|
+
{ id: "dashboards-all-shareable", method: "GET", path: "/api/dashboard/v2/dashboard/all-shareable", purpose: "Shareable dashboard inventory." },
|
|
181
|
+
{ id: "dashboard-widget-counts", method: "GET", path: "/api/dashboard/v2/dashboard/widget-counts", purpose: "Widget counts per dashboard for dashboard completeness checks." },
|
|
182
|
+
{ id: "dashboard", method: "GET", path: "/api/dashboard/v2/dashboard/{dashboardId}", purpose: "Full dashboard definition, layout, widgets, and share settings." },
|
|
183
|
+
{ id: "dashboard-permission-single", method: "GET", path: "/api/dashboard/v2/dashboard/{dashboardId}/permission", purpose: "Single-user dashboard permission state." },
|
|
184
|
+
{ id: "dashboard-permissions", method: "GET", path: "/api/dashboard/v2/dashboard/{dashboardId}/permissions", purpose: "Dashboard permission grants." },
|
|
185
|
+
{ id: "dashboard-permission-config", method: "GET", path: "/api/dashboard/v2/dashboard/{dashboardId}/permission-config", purpose: "Dashboard permission configuration." },
|
|
186
|
+
{ id: "dashboard-permission-grants", method: "GET", path: "/api/dashboard/v2/dashboard/{dashboardId}/permission-grants", purpose: "Explicit dashboard permission grants." },
|
|
187
|
+
{ id: "dashboard-widget", method: "GET", path: "/api/dashboard/v2/widget/{widgetId}", purpose: "Single dashboard widget definition and report binding." },
|
|
188
|
+
{ id: "dashboard-widgets", method: "GET", path: "/api/dashboard/v2/widget", purpose: "Dashboard widget access endpoint used by the dashboard UI." },
|
|
189
|
+
{ id: "report-list-views", method: "GET", path: "/api/dashboard/v2/report-list-views", purpose: "Report-list saved views." },
|
|
190
|
+
{ id: "report-list-views-pinned", method: "GET", path: "/api/dashboard/v2/report-list-views/pinned", purpose: "Pinned report/dashboard views." },
|
|
191
|
+
{ id: "journey-report-resolve", method: "POST", path: "/api/reporting/v3/journey/{reportId}/resolve/async", purpose: "Read-only journey report resolver.", readOnlyKind: "resolve", bodyRequired: true },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "workflows-hybrid",
|
|
196
|
+
label: "Workflow hybrid definitions and automation editor metadata",
|
|
197
|
+
mode: "internal-browser-session",
|
|
198
|
+
description: "Full workflow action graphs, validation, outputs, action catalog, and canvas insights.",
|
|
199
|
+
endpoints: [
|
|
200
|
+
{ id: "flows", method: "GET", path: "/api/automation/v4/flows?limit=100", purpose: "Workflow list and UI metadata." },
|
|
201
|
+
{ id: "legacy-workflows", method: "GET", path: "/api/automation/v3/workflows", purpose: "Legacy workflow list for cross-checking." },
|
|
202
|
+
{ id: "workflow-hybrid", method: "GET", path: "/api/automationplatform/v1/hybrid/{flowId}", purpose: "Full workflow source of truth." },
|
|
203
|
+
{ id: "workflow-validation", method: "GET", path: "/api/automationplatform/v1/validation/{flowId}", purpose: "Workflow validation errors and warnings." },
|
|
204
|
+
{ id: "workflow-output-fields", method: "GET", path: "/api/automationplatform/v1/output-fields/flow/{flowId}/allOutputs", purpose: "Outputs available to downstream actions." },
|
|
205
|
+
{ id: "workflow-object-access", method: "GET", path: "/api/automationplatform/v1/access/object-enrolled-type-access", purpose: "Object types available for workflow enrollment." },
|
|
206
|
+
{ id: "workflow-permission-check", method: "GET", path: "/api/automationplatform/v1/access/permission-check/flow/{flowId}", purpose: "Per-flow access check." },
|
|
207
|
+
{ id: "workflow-action-catalog", method: "GET", path: "/api/automation/v4/actions/internal/access/definitions/filter", purpose: "Internal action catalog and schemas." },
|
|
208
|
+
{ id: "workflow-crm-objects", method: "GET", path: "/api/automationapps/v1/flow-crm-objects/{flowId}", purpose: "CRM object snapshots used in workflow editor." },
|
|
209
|
+
{ id: "workflow-canvas-errors", method: "GET", path: "/api/automationapps/v1/insights/canvas/errors/{flowId}", purpose: "Canvas insight errors." },
|
|
210
|
+
{ id: "workflow-bulk-processing", method: "GET", path: "/api/automation-platform-bulk-processing/v1/bulk-processing/flow/{flowId}", purpose: "Bulk-processing jobs attached to one workflow." },
|
|
211
|
+
{ id: "workflow-permission-check-batch", method: "POST", path: "/api/automationplatform/v1/access/permission-check/flow/batch/read", purpose: "Read-only batch workflow permission check.", readOnlyKind: "batch-read", bodyRequired: true },
|
|
212
|
+
{ id: "workflow-filter-descriptions", method: "POST", path: "/api/automationplatform/v1/filter-descriptions/generate", purpose: "Read-only human-readable workflow filter description preview.", readOnlyKind: "preview", bodyRequired: true },
|
|
213
|
+
{ id: "workflow-card-positions", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.workflow.canvas.rpc.FlowCardPositionRpc/getCardPositions", purpose: "Read-only workflow canvas card positions RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
214
|
+
{ id: "workflow-renderable-action-search", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.automation.actions.rpc.RenderableActionDefinitionService/search", purpose: "Read-only workflow action definition search RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
215
|
+
{ id: "workflow-delay-counts", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.automationplatform.chirp.rpc.DelayedActionsService/getDelayActionEnrollmentCounts", purpose: "Read-only workflow delayed-action enrollment counts RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
216
|
+
{ id: "workflow-billing-info", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.automationplatform.chirp.rpc.FlowBillingRpc/getFlowBillingInfo", purpose: "Read-only workflow billing/usage metadata RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
217
|
+
{ id: "workflow-copilot-context", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.workflows.copilot.rpc.WorkflowsCopilot/fetchInteractiveModeContextForFlow", purpose: "Read-only workflow Copilot context metadata RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "properties-metadata",
|
|
222
|
+
label: "Internal property metadata, groups, limits, fill rates, and validation",
|
|
223
|
+
mode: "internal-browser-session",
|
|
224
|
+
description: "Property settings UI metadata beyond public CRM schemas.",
|
|
225
|
+
endpoints: [
|
|
226
|
+
{ id: "properties-v1-contacts", method: "GET", path: "/api/properties/v1/contacts/properties", purpose: "Legacy contact property metadata used by old portals." },
|
|
227
|
+
{ id: "properties-v2-object", method: "GET", path: "/api/properties/v2/{objectType}/properties", purpose: "Legacy v2 property metadata; often preserves createdUserId for older properties." },
|
|
228
|
+
{ id: "properties", method: "GET", path: "/api/properties/v4/{typeId}", purpose: "Property metadata by object type." },
|
|
229
|
+
{ id: "property-named", method: "GET", path: "/api/properties/v4/{typeId}/named/{propertyName}", purpose: "Single property metadata by internal name." },
|
|
230
|
+
{ id: "property-groups", method: "GET", path: "/api/properties/v4/groups/{typeId}", purpose: "Property groups for one object type." },
|
|
231
|
+
{ id: "property-group-properties", method: "GET", path: "/api/properties/v4/groups/{typeId}/properties", purpose: "Properties nested under groups for one object type." },
|
|
232
|
+
{ id: "recycled-properties", method: "GET", path: "/api/properties/v4/recycle/{typeId}", purpose: "Soft-deleted/recycled properties." },
|
|
233
|
+
{ id: "combined-property-count", method: "GET", path: "/api/properties/v4/properties/combined-count", purpose: "Property counts across object types." },
|
|
234
|
+
{ id: "custom-limits", method: "GET", path: "/api/properties/v4/properties/{typeId}/custom-limits-and-counts", purpose: "Custom property quota and count state." },
|
|
235
|
+
{ id: "fill-rate", method: "GET", path: "/api/property-settings/v1/property-fill-rate/{typeId}/batch/read", purpose: "Property fill-rate telemetry." },
|
|
236
|
+
{ id: "property-validation", method: "GET", path: "/api/property-validation/v1/properties/{objectTypeId}", purpose: "Property validation constraints." },
|
|
237
|
+
{ id: "business-unit-property-validation", method: "GET", path: "/api/property-validation/v1/business-unit-property/{typeId}/property/{propertyName}", purpose: "Business-unit scoped property validation constraints." },
|
|
238
|
+
{ id: "conditional-options", method: "GET", path: "/api/conditional-properties/v2/options/{typeId}/{propertyName}", purpose: "Conditional property options and dependencies." },
|
|
239
|
+
{ id: "external-options-fetch", method: "GET", path: "/api/external-options/v3/fetch/{entity}", purpose: "Dynamic option fetch used by property/report/list UIs." },
|
|
240
|
+
{ id: "external-options-paged-fetch", method: "GET", path: "/api/external-options/v3/pagedFetch/{entity}", purpose: "Paged dynamic option fetch used by property/report/list UIs." },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "crm-search",
|
|
245
|
+
label: "CRM search, GraphQL, and object preview reads",
|
|
246
|
+
mode: "internal-browser-session",
|
|
247
|
+
description: "Generic CRM UI search/read helpers used by report builders, help desk, quotes, and object previews.",
|
|
248
|
+
endpoints: [
|
|
249
|
+
{ id: "crm-search", method: "POST", path: "/api/crm-search/search?portalId={portalId}", purpose: "Read-only CRM object search with UI request shape.", readOnlyKind: "search", bodyRequired: true },
|
|
250
|
+
{ id: "crm-search-ids", method: "POST", path: "/api/crm-search/search/ids?portalId={portalId}", purpose: "Read-only CRM ID search.", readOnlyKind: "search", bodyRequired: true },
|
|
251
|
+
{ id: "crm-search-report", method: "POST", path: "/api/crm-search/report?portalId={portalId}", purpose: "Read-only CRM report search payload.", readOnlyKind: "search", bodyRequired: true },
|
|
252
|
+
{ id: "graphql-crm", method: "POST", path: "/api/graphql/crm", purpose: "Read-only CRM GraphQL query used by help desk and index UIs.", readOnlyKind: "search", bodyRequired: true },
|
|
253
|
+
{ id: "inbounddb-object-preview", method: "GET", path: "/inbounddb-objects/v1/preview", purpose: "InboundDB object preview metadata." },
|
|
254
|
+
{ id: "inbounddb-object", method: "GET", path: "/inbounddb-objects/v1/crm-objects/{objectTypeId}/{objectId}", purpose: "Single CRM object UI payload by type and ID." },
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "object-metadata",
|
|
259
|
+
label: "Object metadata, schemas, framework builder, and portal-scoped types",
|
|
260
|
+
mode: "internal-browser-session",
|
|
261
|
+
description: "UI metadata for object types, schema resources, filters, imports, segments, and property settings.",
|
|
262
|
+
endpoints: [
|
|
263
|
+
{ id: "schema-portal-metadata", method: "GET", path: "/api/schemas-metadata/v1/portal-metadata", purpose: "Portal-wide schema metadata resources." },
|
|
264
|
+
{ id: "schema-portal-metadata-type", method: "GET", path: "/api/schemas-metadata/v1/portal-metadata/type/{type}", purpose: "Schema metadata for one UI metadata type." },
|
|
265
|
+
{ id: "schema-portal-metadata-resource", method: "GET", path: "/api/schemas-metadata/v1/portal-metadata/resource/{resource}", purpose: "Schema metadata for one resource." },
|
|
266
|
+
{ id: "schema-portal-metadata-resources", method: "GET", path: "/api/schemas-metadata/v1/portal-metadata/resources", purpose: "All schema metadata resources available to the UI." },
|
|
267
|
+
{ id: "framework-metadata-all", method: "GET", path: "/framework-builder/v1/read/metadata/type/all/batch", purpose: "Framework-builder metadata across CRM property settings, filters, imports, and segments." },
|
|
268
|
+
{ id: "framework-metadata-type", method: "GET", path: "/framework-builder/v1/read/metadata/types/{metadataType}", purpose: "Framework-builder metadata for one type family." },
|
|
269
|
+
{ id: "framework-property", method: "GET", path: "/api/framework-builder/v1/read/metadata/property/{objectTypeId}/{propertyName}", purpose: "Framework-builder metadata for one property and its UI behavior." },
|
|
270
|
+
{ id: "portal-scoped-objects", method: "GET", path: "/inbounddb-meta/v1/object-types/portal-scoped-objects/for-portal", purpose: "Portal-specific object type metadata and feature-gated object catalog." },
|
|
271
|
+
{ id: "event-object-types", method: "GET", path: "/inbounddb-meta/v1/object-types/events/for-portal", purpose: "Event object types available in the portal." },
|
|
272
|
+
{ id: "object-type", method: "GET", path: "/inbounddb-meta/v1/object-types/{objectTypeId}", purpose: "Single inbounddb object type metadata." },
|
|
273
|
+
{ id: "object-types-batch", method: "POST", path: "/inbounddb-meta/v1/object-types/batch-get", purpose: "Read-only batch object-type metadata lookup.", readOnlyKind: "batch-read", bodyRequired: true },
|
|
274
|
+
{ id: "object-types-batch-by-name", method: "POST", path: "/inbounddb-meta/v1/object-types/batch-get-by-names", purpose: "Read-only batch object-type metadata lookup by object names.", readOnlyKind: "batch-read", bodyRequired: true },
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: "conditional-properties",
|
|
279
|
+
label: "Conditional properties and dependent field rules",
|
|
280
|
+
mode: "internal-browser-session",
|
|
281
|
+
description: "Conditional property rule reads used to back up dependent picklists and UI field logic.",
|
|
282
|
+
endpoints: [
|
|
283
|
+
{ id: "conditional-options", method: "GET", path: "/api/conditional-properties/v2/options/{typeId}/{propertyName}", purpose: "Conditional property options and dependencies." },
|
|
284
|
+
{ id: "conditional-rules", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.conditionalproperties.rpc.ConditionalPropertiesUnifiedRpc/getRules", purpose: "Read-only conditional property rules RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: "lists-segments",
|
|
289
|
+
label: "Lists, ILS segments, filters, and legacy list search",
|
|
290
|
+
mode: "internal-browser-session",
|
|
291
|
+
description: "Fragmented list/segment UI surfaces used when public list filters are incomplete.",
|
|
292
|
+
endpoints: [
|
|
293
|
+
{ id: "lists-v3", method: "GET", path: "/api/lists/v3/lists", purpose: "Internal lists/segments catalog." },
|
|
294
|
+
{ id: "lists-v3-portal", method: "GET", path: "/api/lists/v3/{portalId}/lists", purpose: "Portal-scoped internal lists/segments catalog." },
|
|
295
|
+
{ id: "list-v3", method: "GET", path: "/api/lists/v3/lists/{listId}", purpose: "Single list/segment definition." },
|
|
296
|
+
{ id: "list-v3-portal", method: "GET", path: "/api/lists/v3/{portalId}/lists/{listId}", purpose: "Portal-scoped single list/segment definition." },
|
|
297
|
+
{ id: "list-v3-portal-segments", method: "GET", path: "/api/lists/v3/{portalId}/lists/{listId}/segments", purpose: "Segments nested inside a list." },
|
|
298
|
+
{ id: "list-v2", method: "GET", path: "/api/lists/v2/lists/{listId}", purpose: "Legacy list definition fallback." },
|
|
299
|
+
{ id: "contacts-lists", method: "GET", path: "/api/contacts/v1/lists", purpose: "Legacy contacts-list catalog." },
|
|
300
|
+
{ id: "contacts-list", method: "GET", path: "/api/contacts/v1/lists/{listId}", purpose: "Legacy contacts-list definition." },
|
|
301
|
+
{ id: "contact-list-management", method: "GET", path: "/api/contact-list-management/v1/{portalId}/lists/{listId}", purpose: "UI-facing contact-list management definition." },
|
|
302
|
+
{ id: "ils-segment", method: "GET", path: "/api/ils-segments/v1/portals/{portalId}/segments/{segmentId}", purpose: "ILS segment filter branch details." },
|
|
303
|
+
{ id: "ils-segment-short", method: "GET", path: "/api/ils-segments/v1/{portalId}/segments/{segmentId}", purpose: "Alternate ILS segment filter branch path." },
|
|
304
|
+
{ id: "ils-segment-store", method: "GET", path: "/api/ils-segment-store/v1/segments", purpose: "Bulk ILS segment store query." },
|
|
305
|
+
{ id: "segments-ui", method: "GET", path: "/api/segments-ui/v1/segments/{portalId}/{segmentId}", purpose: "Segments UI definition fallback." },
|
|
306
|
+
{ id: "legacy-segment-search", method: "GET", path: "/api/lists-search/v1/segments/all/legacy/{portalId}", purpose: "Legacy segment search used by list UI." },
|
|
307
|
+
{ id: "lists-search-v3", method: "POST", path: "/api/lists/v3/lists/search", purpose: "Read-only internal list search when body shape is known.", readOnlyKind: "search", bodyRequired: true },
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "sales-content",
|
|
312
|
+
label: "Sales templates, sequences, snippets, and playbooks",
|
|
313
|
+
mode: "internal-browser-session",
|
|
314
|
+
description: "Sales content definitions and Chirp RPC bodies for migration fidelity.",
|
|
315
|
+
endpoints: [
|
|
316
|
+
{ id: "sales-content-search", method: "POST", path: "/api/salescontentsearch/v2/search?portalId={portalId}", purpose: "Read-only search for sequences/templates/playbooks.", readOnlyKind: "search", bodyRequired: true },
|
|
317
|
+
{ id: "sales-content-templates", method: "GET", path: "/api/sales-content/v1/templates?portalId={portalId}", purpose: "Sales content template inventory fallback." },
|
|
318
|
+
{ id: "sales-content-sequences", method: "GET", path: "/api/sales-content/v1/sequences?portalId={portalId}", purpose: "Sales content sequence inventory fallback." },
|
|
319
|
+
{ id: "sequence", method: "GET", path: "/api/sequences/v3/sequences/{sequenceId}?portalId={portalId}", purpose: "Full sequence payload." },
|
|
320
|
+
{ id: "sequence-usage", method: "GET", path: "/api/sequences/v2/sequences/usage?portalId={portalId}", purpose: "Sequence usage and quota metadata." },
|
|
321
|
+
{ id: "sequence-enrollments-impacted", method: "GET", path: "/api/sequences/v2/enrollments/impacted?portalId={portalId}", purpose: "Impacted sequence enrollments metadata." },
|
|
322
|
+
{ id: "sequence-enrollment-health", method: "GET", path: "/api/sequences/v2/enrollments/health-check?portalId={portalId}", purpose: "Sequence enrollment health-check metadata." },
|
|
323
|
+
{ id: "sequence-folders-count", method: "GET", path: "/api/sequences/v3/folders/count?portalId={portalId}", purpose: "Sequence folder count metadata." },
|
|
324
|
+
{ id: "templates", method: "GET", path: "/api/templates/v1/templates?portalId={portalId}", purpose: "Sales template inventory." },
|
|
325
|
+
{ id: "sales-template-v2", method: "GET", path: "/api/sales-templates/v2/templates/{templateId}?portalId={portalId}", purpose: "Single sales template body and metadata." },
|
|
326
|
+
{ id: "sales-template-search", method: "POST", path: "/api/sales-templates/v2/templates/search?portalId={portalId}", purpose: "Read-only sales template search.", readOnlyKind: "search", bodyRequired: true },
|
|
327
|
+
{ id: "sales-template-usage", method: "GET", path: "/api/sales-templates/v2/templates/usage?portalId={portalId}", purpose: "Sales template usage and quota metadata." },
|
|
328
|
+
{ id: "sales-content-libraries", method: "GET", path: "/api/sales-content-libraries/v2/libraries?portalId={portalId}", purpose: "Sales content libraries." },
|
|
329
|
+
{ id: "snippets", method: "GET", path: "/api/snippets/v1/snippets", purpose: "Sales snippets with body/htmlBody and shortcuts." },
|
|
330
|
+
{ id: "playbooks-list", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybooksRpc/listPlaybooks", purpose: "Read-only playbook list RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
331
|
+
{ id: "playbooks-count", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybooksRpc/getPlaybookCount", purpose: "Read-only playbook count and limit RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
332
|
+
{ id: "playbook-version", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybooksRpc/getLatestPlaybookVersion", purpose: "Read-only playbook body/version RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
333
|
+
{ id: "playbook-version-summaries", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybooksRpc/getPlaybookVersionSummaries", purpose: "Read-only playbook version history RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
334
|
+
{ id: "playbook-questions", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.playbooks.rpc.PlaybookQuestionsRpc/getLatestQuestionsByPlaybook", purpose: "Read-only playbook questions RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
335
|
+
],
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: "crm-views-record-layout",
|
|
339
|
+
label: "CRM views, record cards, and layout editor metadata",
|
|
340
|
+
mode: "internal-browser-session",
|
|
341
|
+
description: "Saved views, visual settings, and record card containers.",
|
|
342
|
+
endpoints: [
|
|
343
|
+
{ id: "sales-views-legacy", method: "GET", path: "/sales-views/v2/views", purpose: "Legacy CRM custom views." },
|
|
344
|
+
{ id: "sales-views-v4", method: "GET", path: "/api/sales/v4/views", purpose: "CRM index custom views." },
|
|
345
|
+
{ id: "sales-view-v4", method: "GET", path: "/api/sales/v4/views/{viewId}", purpose: "Single CRM custom view definition." },
|
|
346
|
+
{ id: "sales-views-v4-all", method: "GET", path: "/api/sales/v4/views/{typeId}/all", purpose: "All CRM object views for one type." },
|
|
347
|
+
{ id: "sales-views-viz", method: "GET", path: "/api/sales/v4/views/{typeId}/all/with-visualization-settings", purpose: "CRM object views with column, sort, board, and visualization settings." },
|
|
348
|
+
{ id: "sales-views-v4-list", method: "GET", path: "/api/sales/v4/views/{typeId}/list", purpose: "CRM saved list views for one object type." },
|
|
349
|
+
{ id: "sales-views-v4-pinned", method: "GET", path: "/api/sales/v4/views/{typeId}/pinned/view", purpose: "Pinned CRM object views." },
|
|
350
|
+
{ id: "sales-views-v4-search", method: "GET", path: "/api/sales/v4/views/{typeId}/search", purpose: "CRM object view search." },
|
|
351
|
+
{ id: "default-sales-view-viz", method: "GET", path: "/api/sales/v4/views/default-view/{typeId}/with-visualization-settings", purpose: "Default CRM object view with visualization settings." },
|
|
352
|
+
{ id: "sales-views-v3", method: "GET", path: "/api/sales-views/v3/sales-views/{typeId}", purpose: "Legacy v3 CRM views fallback." },
|
|
353
|
+
{ id: "sales-views-v2-type", method: "GET", path: "/api/sales-views/v2/sales-views/{typeId}", purpose: "Legacy v2 CRM views fallback." },
|
|
354
|
+
{ id: "views-meta", method: "GET", path: "/api/views-meta/v1/views/{typeId}", purpose: "CRM view metadata fallback." },
|
|
355
|
+
{ id: "sales-view-limit-check", method: "GET", path: "/api/sales-views/v2/crm-search/limit-check/{typeId}", purpose: "CRM view limit check used by the index UI." },
|
|
356
|
+
{ id: "record-cards", method: "GET", path: "/api/crm-record-cards/v4/container-views/{objectTypeId}", purpose: "Record card container views." },
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: "marketing-internal",
|
|
361
|
+
label: "Marketing email fallbacks, subscription channels, CTAs, and forms",
|
|
362
|
+
mode: "internal-browser-session",
|
|
363
|
+
description: "Marketing UI-only fallbacks for email bodies, DnD metadata, subscription channels, and CTAs.",
|
|
364
|
+
endpoints: [
|
|
365
|
+
{ id: "subscription-channels", method: "GET", path: "/api/email/public/v1/subscriptions", purpose: "All subscription channels including Email, Chat, Call, and Post." },
|
|
366
|
+
{ id: "subscription-rules", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.subscriptions.settings.rpc.service.RulesRpc/getAllRules", purpose: "Read-only subscription visibility rules RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
367
|
+
{ id: "subscription-definition-rules", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.subscriptions.settings.rpc.service.RulesRpc/getAllDefinitionRules", purpose: "Read-only subscription definition rules RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
368
|
+
{ id: "privacy-settings", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.data.privacy.hub.settings.rpc.PrivacySettingsRpc/getPrivacySettings", purpose: "Read-only privacy/GDPR settings RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
369
|
+
{ id: "business-unit-displays", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.business.units.rpc.BusinessUnitsDisplaysService/getBusinessUnitDisplaysForUser", purpose: "Read-only business-unit display map RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
370
|
+
{ id: "can-spam-settings", method: "GET", path: "/api/content/api/v4/can-spam-settings?portalId={portalId}", purpose: "CAN-SPAM footer address settings." },
|
|
371
|
+
{ id: "subscription-settings-multi-get", method: "POST", path: "/api/subscriptions-settings/v1/settings/multi-get", purpose: "Read-only subscription settings multi-get.", readOnlyKind: "batch-read", bodyRequired: true },
|
|
372
|
+
{ id: "cosemail-list", method: "GET", path: "/api/cosemail/v1/emails?portalId={portalId}&limit=100&businessUnitId={businessUnitId}", purpose: "Internal marketing email catalog by BU." },
|
|
373
|
+
{ id: "cosemail-email", method: "GET", path: "/api/cosemail/v1/emails/{emailId}", purpose: "Full internal marketing email definition." },
|
|
374
|
+
{ id: "subscription-email-template", method: "GET", path: "/api/cosemail/v1/subscription-emails/{type}/get", purpose: "Subscription/resubscribe email templates." },
|
|
375
|
+
{ id: "ctas-v3-placements", method: "GET", path: "/api/ctas/v3/placements", purpose: "New CTA builder placement inventory." },
|
|
376
|
+
{ id: "forms-v2-batch", method: "GET", path: "/forms/v2/forms/batch", purpose: "Legacy forms batch surface." },
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
id: "cms-design-manager",
|
|
381
|
+
label: "CMS Design Manager, templates, themes, modules, and layouts",
|
|
382
|
+
mode: "internal-browser-session",
|
|
383
|
+
description: "CMS/editor metadata and Design Manager assets that complement public CMS APIs.",
|
|
384
|
+
endpoints: [
|
|
385
|
+
{ id: "templates", method: "GET", path: "/api/designmanager/v1/templates", purpose: "Design Manager templates." },
|
|
386
|
+
{ id: "content-templates-v2", method: "GET", path: "/api/content/api/v2/templates?portalId={portalId}", purpose: "Content API v2 template catalog fallback." },
|
|
387
|
+
{ id: "themes", method: "GET", path: "/api/designmanager/v1/themes", purpose: "Design Manager themes." },
|
|
388
|
+
{ id: "modules", method: "GET", path: "/api/designmanager/v1/modules", purpose: "Design Manager modules." },
|
|
389
|
+
{ id: "layouts", method: "GET", path: "/api/designmanager/v1/layouts", purpose: "Design Manager layouts." },
|
|
390
|
+
{ id: "global-groups", method: "GET", path: "/api/designmanager/v1/layouts/global-groups/summaries", purpose: "Design Manager global group summaries." },
|
|
391
|
+
{ id: "builtin-module-mapping", method: "GET", path: "/api/designmanager/v1/modules/builtin-mapping", purpose: "Built-in module mapping used by Design Manager." },
|
|
392
|
+
{ id: "content-pages", method: "GET", path: "/api/content/api/v2/pages?portalId={portalId}", purpose: "CMS page catalog through the content UI API." },
|
|
393
|
+
{ id: "site-settings", method: "GET", path: "/api/content-settings/v1/site-settings/by-portal-id/{portalId}", purpose: "Portal site settings used by CMS editors." },
|
|
394
|
+
{ id: "content-portal-copy-status", method: "GET", path: "/api/content-portal-copy/v1/copy/status?portalId={portalId}", purpose: "HubSpot content-portal-copy job status inventory." },
|
|
395
|
+
{ id: "membership-access-groups", method: "GET", path: "/api/membership-access-groups/v1/access-groups?portalId={portalId}", purpose: "Membership/gated content access groups when enabled." },
|
|
396
|
+
],
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "social-ads",
|
|
400
|
+
label: "Social publishing and ads RPC metadata",
|
|
401
|
+
mode: "internal-browser-session",
|
|
402
|
+
description: "Social broadcasts plus ads account metadata for marketing backup and migration planning.",
|
|
403
|
+
endpoints: [
|
|
404
|
+
{ id: "social-broadcasts", method: "GET", path: "/api/broadcast/v2/broadcasts?portalId={portalId}&limit=100", purpose: "Social post/broadcast inventory." },
|
|
405
|
+
{ id: "social-accounts", method: "GET", path: "/api/broadcast/v2/accounts/with-channels?portalId={portalId}", purpose: "Connected social accounts and channels." },
|
|
406
|
+
{ id: "social-trusted-vetting", method: "GET", path: "/api/vetting/public/v1/user-verification/bannerinfo/scope/social-trusted", purpose: "Social trusted-scope verification banner state." },
|
|
407
|
+
{ id: "ads-accounts-overview", method: "POST", path: "/api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.AdAccountRpc/getAdsAccountsOverviewForUser", purpose: "Read-only ads accounts overview RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
408
|
+
{ id: "ads-portal-metadata", method: "POST", path: "/api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.AdAccountRpc/getPortalMetaData", purpose: "Read-only ads portal metadata RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
409
|
+
{ id: "ads-user-summaries", method: "POST", path: "/api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.AuthRpc/getUserSummaries", purpose: "Read-only ads user summaries RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
410
|
+
{ id: "ads-table-columns", method: "POST", path: "/api/chirp-frontend-app-slow/v1/gateway/com.hubspot.ads.rpc.UiSettingsRpc/getTableColumnInfo", purpose: "Read-only ads UI table-column metadata RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: "governance-audit",
|
|
415
|
+
label: "Audit logs, security logs, installed apps, and sync bridge metadata",
|
|
416
|
+
mode: "internal-browser-session",
|
|
417
|
+
description: "Governance and app/integration inventory used for downgrade/migration evidence.",
|
|
418
|
+
endpoints: [
|
|
419
|
+
{ id: "audit-entries-object", method: "GET", path: "/api/audit-log/v1/audit-entries?objectType={objectType}&objectId={objectId}", purpose: "Per-object audit entries for lists, dashboards, reports, and workflows." },
|
|
420
|
+
{ id: "audit-log-portal", method: "GET", path: "/api/audit-log/v1/portal/{portalId}", purpose: "Portal-wide internal audit log." },
|
|
421
|
+
{ id: "audit-log-v3", method: "GET", path: "/api/audit-log/v3/audit", purpose: "Newer internal audit surface." },
|
|
422
|
+
{ id: "security-audit-logs", method: "GET", path: "/api/security-audit-logs/v1/audit-logs", purpose: "Security audit log entries." },
|
|
423
|
+
{ id: "installed-apps-ui", method: "GET", path: "/appinstalls/v3/app-install/installed-apps-for-ui-new", purpose: "Connected app inventory as shown in settings." },
|
|
424
|
+
{ id: "sync-bridge-accounts", method: "GET", path: "/sync-bridge/v3/accounts", purpose: "Data sync connected accounts and bridge metadata." },
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
id: "commerce-forecast-quotes",
|
|
429
|
+
label: "Commerce, payments, quote, and forecast internal status",
|
|
430
|
+
mode: "internal-browser-session",
|
|
431
|
+
description: "Feature-gated commerce and sales setup metadata for downgrade and migration checks.",
|
|
432
|
+
endpoints: [
|
|
433
|
+
{ id: "payment-eligibility", method: "GET", path: "/api/payments/v1/payment-accounts/eligibility?portalId={portalId}", purpose: "Payment account eligibility and readiness." },
|
|
434
|
+
{ id: "payment-account-default", method: "GET", path: "/api/payments/v1/payment-accounts/connected-account/default?portalId={portalId}", purpose: "Default connected payment account." },
|
|
435
|
+
{ id: "quotes-crm-search", method: "POST", path: "/api/crm-search/search?portalId={portalId}", purpose: "Read-only quote/commerce object search when body uses objectTypeId 0-14 or related commerce types.", readOnlyKind: "search", bodyRequired: true },
|
|
436
|
+
{ id: "forecast-categories", method: "GET", path: "/api/forecasting/v1/categories?portalId={portalId}", purpose: "Forecast category setup when enabled." },
|
|
437
|
+
{ id: "quote-esignature", method: "GET", path: "/api/quotes/v1/esignature/settings?portalId={portalId}", purpose: "Quote e-signature settings when enabled." },
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "conversations-chatflows",
|
|
442
|
+
label: "Conversations, help desk, and chatflows RPC metadata",
|
|
443
|
+
mode: "internal-browser-session",
|
|
444
|
+
description: "Help desk custom views and chatflow RPC fallbacks.",
|
|
445
|
+
endpoints: [
|
|
446
|
+
{ id: "conversation-custom-views", method: "GET", path: "/api/conversations-custom-views/v1/views?portalId={portalId}", purpose: "Conversation/help desk custom views." },
|
|
447
|
+
{ id: "conversation-view-settings-users", method: "GET", path: "/api/conversations-custom-views/v1/settings/users", purpose: "Help desk/conversations user view settings." },
|
|
448
|
+
{ id: "conversation-spaces-and-views", method: "GET", path: "/api/conversations-custom-views/v1/spaces-and-views/workspace/{workspaceId}", purpose: "Help desk workspace spaces and views." },
|
|
449
|
+
{ id: "conversation-view-settings", method: "GET", path: "/api/conversations-custom-views/v1/settings/views/{viewId}", purpose: "Single help desk custom view definition." },
|
|
450
|
+
{ id: "conversation-email-accounts", method: "GET", path: "/api/conversations-email/v1/connected-accounts/inbox/{inboxId}", purpose: "Connected email accounts for one inbox." },
|
|
451
|
+
{ id: "conversation-channel-instances", method: "GET", path: "/api/conversations-channels/v1/channel-instances/inbox/{inboxId}/paged", purpose: "Conversation channel instances for one inbox." },
|
|
452
|
+
{ id: "chatflows-list", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.chatflows.rpc.ChatflowsRpc/listChatflows", purpose: "Read-only chatflows RPC list.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
453
|
+
{ id: "chatflows-priority", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.chatflows.rpc.ChannelInstanceRpc/getUserChatflowsSortedByPriority", purpose: "Read-only chatflows priority RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
454
|
+
{ id: "helpdesk-tickets", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.cv.custom.views.member.rpc.CvCustomViewsMemberRpc/getPagedHelpDeskTickets", purpose: "Read-only help desk tickets for a view RPC.", readOnlyKind: "rpc-read", bodyRequired: true },
|
|
455
|
+
{ id: "helpdesk-notification-preferences", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.cv.tickets.notification.preferences.rpc.TicketNotificationPreferenceFrontendRpc/getUserNotificationPreferences", purpose: "Read-only help desk notification preferences RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
456
|
+
{ id: "helpdesk-migration-state", method: "POST", path: "/api/chirp-frontend-app/v1/gateway/com.hubspot.helpdesk.migration.rpc.HelpdeskMigrationRpc/getMigrationState", purpose: "Read-only help desk migration state RPC.", readOnlyKind: "rpc-read", defaultBody: {} },
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: "account-security",
|
|
461
|
+
label: "Account, security health, and installed-app metadata",
|
|
462
|
+
mode: "internal-browser-session",
|
|
463
|
+
description: "Account-level setup and security metadata used by 360 backup and downgrade checks.",
|
|
464
|
+
endpoints: [
|
|
465
|
+
{ id: "account-info", method: "GET", path: "/account-info/v3/details?portalId={portalId}", purpose: "Account identity, edition, and portal metadata." },
|
|
466
|
+
{ id: "account-activity", method: "GET", path: "/account-info/v3/activity/{portalId}", purpose: "Account activity summary." },
|
|
467
|
+
{ id: "private-apps", method: "GET", path: "/account-info/v3/private-apps", purpose: "Private apps and scopes inventory." },
|
|
468
|
+
{ id: "security-health", method: "GET", path: "/api/security-health/v1/portal/{portalId}", purpose: "Security health state when available." },
|
|
469
|
+
{ id: "security-settings", method: "GET", path: "/api/security-settings/v1/settings?portalId={portalId}", purpose: "Security settings surfaced by the account UI." },
|
|
470
|
+
{ id: "security-center-events", method: "GET", path: "/api/security-center/v1/account-events", purpose: "Security center account events feed." },
|
|
471
|
+
{ id: "security-monitoring", method: "GET", path: "/api/security-monitoring/v2/security-monitoring/account/{portalId}", purpose: "Security monitoring state." },
|
|
472
|
+
{ id: "crm-usage", method: "GET", path: "/api/crm-usages/v2/usages/", purpose: "CRM usage telemetry surfaced by account settings." },
|
|
473
|
+
{ id: "installed-apps", method: "GET", path: "/appinstalls/v3/app-install/installed-apps-for-ui-new", purpose: "Installed apps inventory as shown in HubSpot settings." },
|
|
474
|
+
{ id: "sync-bridge-accounts", method: "GET", path: "/sync-bridge/v3/accounts", purpose: "Data sync connected accounts and bridge metadata." },
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
];
|
|
478
|
+
export function validateInternalCaptureCatalogue(surfaces) {
|
|
479
|
+
const errors = [];
|
|
480
|
+
const surfaceIds = new Set();
|
|
481
|
+
for (const surface of surfaces) {
|
|
482
|
+
if (surfaceIds.has(surface.id))
|
|
483
|
+
errors.push(`duplicate surface id: ${surface.id}`);
|
|
484
|
+
surfaceIds.add(surface.id);
|
|
485
|
+
const endpointIds = new Set();
|
|
486
|
+
for (const endpoint of surface.endpoints) {
|
|
487
|
+
if (endpointIds.has(endpoint.id))
|
|
488
|
+
errors.push(`duplicate endpoint id in ${surface.id}: ${endpoint.id}`);
|
|
489
|
+
endpointIds.add(endpoint.id);
|
|
490
|
+
if (endpoint.method === "POST" && !endpoint.readOnlyKind) {
|
|
491
|
+
errors.push(`POST endpoint must declare readOnlyKind: ${surface.id}/${endpoint.id}`);
|
|
492
|
+
}
|
|
493
|
+
if (endpoint.method !== "GET" && endpoint.method !== "POST") {
|
|
494
|
+
errors.push(`unsupported method in ${surface.id}/${endpoint.id}: ${String(endpoint.method)}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return { errors };
|
|
499
|
+
}
|
|
500
|
+
export function registerInternal(program, getCtx) {
|
|
501
|
+
const internal = program.command("internal").description("Allowlisted HubSpot browser-session capture adapters");
|
|
502
|
+
internal
|
|
503
|
+
.command("surfaces")
|
|
504
|
+
.description("List allowlisted internal capture surfaces")
|
|
505
|
+
.action(() => {
|
|
506
|
+
const ctx = getCtx();
|
|
507
|
+
printResult(ctx, {
|
|
508
|
+
guardrails: INTERNAL_CAPTURE_GUARDRAILS,
|
|
509
|
+
surfaces: INTERNAL_SURFACES.map((surface) => ({
|
|
510
|
+
id: surface.id,
|
|
511
|
+
label: surface.label,
|
|
512
|
+
mode: surface.mode,
|
|
513
|
+
description: surface.description,
|
|
514
|
+
endpoints: surface.endpoints.length,
|
|
515
|
+
})),
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
internal
|
|
519
|
+
.command("coverage")
|
|
520
|
+
.description("Summarize internal endpoint docs and allowlisted capture coverage")
|
|
521
|
+
.action(() => {
|
|
522
|
+
const ctx = getCtx();
|
|
523
|
+
printResult(ctx, buildInternalCoverage(ctx));
|
|
524
|
+
});
|
|
525
|
+
addBrowserSessionOptions(internal
|
|
526
|
+
.command("capture")
|
|
527
|
+
.description("Capture allowlisted internal endpoints to local JSON files")
|
|
528
|
+
.option("--surface <id>", "Surface id to capture")
|
|
529
|
+
.option("--endpoint <id>", "Endpoint id inside the selected surface; repeat to capture several", collectValues, [])
|
|
530
|
+
.option("--all", "Capture every allowlisted surface endpoint that has enough inputs")
|
|
531
|
+
.option("--out-dir <path>", "Output directory for raw JSON and capture-manifest.json")
|
|
532
|
+
.option("--body-file <endpoint=path>", "JSON body file for an allowlisted read-only POST endpoint", collectValues, [])
|
|
533
|
+
.option("--var <key=value>", "Placeholder value such as flowId=123 or datasetId=abc", collectValues, [])).action(async (opts) => {
|
|
534
|
+
const ctx = getCtx();
|
|
535
|
+
const selected = selectEndpoints(selectSurfaces(opts.surface, Boolean(opts.all)), opts.endpoint ?? []);
|
|
536
|
+
const vars = parseVarFlags(opts.var ?? []);
|
|
537
|
+
const bodyFiles = parseBodyFileFlags(opts.bodyFile ?? []);
|
|
538
|
+
if (ctx.dryRun) {
|
|
539
|
+
const portalId = opts.portalId ?? process.env.HSCLI_PORTAL_ID ?? "";
|
|
540
|
+
printResult(ctx, {
|
|
541
|
+
mode: "dry-run",
|
|
542
|
+
guardrails: INTERNAL_CAPTURE_GUARDRAILS,
|
|
543
|
+
surfaces: selected.map((surface) => surface.id),
|
|
544
|
+
endpoints: selected.flatMap((surface) => (surface.endpoints.map((endpoint) => dryRunEndpoint(surface, endpoint, portalId, vars, bodyFiles)))),
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const session = resolveBrowserSession(ctx, opts, "Internal capture", true);
|
|
549
|
+
const portalId = session.portalId;
|
|
550
|
+
const outDir = opts.outDir?.trim() || `./hscli-internal-capture-${portalId}`;
|
|
551
|
+
const results = [];
|
|
552
|
+
mkdirSync(outDir, { recursive: true });
|
|
553
|
+
for (const surface of selected) {
|
|
554
|
+
const surfaceDir = join(outDir, safeFileName(surface.id));
|
|
555
|
+
mkdirSync(surfaceDir, { recursive: true });
|
|
556
|
+
for (const endpoint of surface.endpoints) {
|
|
557
|
+
const prepared = prepareEndpoint(endpoint, portalId, vars, bodyFiles);
|
|
558
|
+
if (!prepared.ready) {
|
|
559
|
+
results.push({
|
|
560
|
+
surface: surface.id,
|
|
561
|
+
endpoint: endpoint.id,
|
|
562
|
+
status: "skipped",
|
|
563
|
+
reason: prepared.reason,
|
|
564
|
+
missing: prepared.missing,
|
|
565
|
+
});
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const responseFile = join(surfaceDir, `${safeFileName(endpoint.id)}.json`);
|
|
569
|
+
const requestBodyFile = prepared.body !== undefined
|
|
570
|
+
? join(surfaceDir, `${safeFileName(endpoint.id)}.request.json`)
|
|
571
|
+
: undefined;
|
|
572
|
+
try {
|
|
573
|
+
if (requestBodyFile) {
|
|
574
|
+
writeFileSync(requestBodyFile, JSON.stringify(redactSensitive(prepared.body), null, 2), "utf8");
|
|
575
|
+
}
|
|
576
|
+
const response = await session.client.request(prepared.path, {
|
|
577
|
+
method: endpoint.method,
|
|
578
|
+
body: prepared.body,
|
|
579
|
+
permissionMode: "read",
|
|
580
|
+
});
|
|
581
|
+
writeFileSync(responseFile, JSON.stringify(response, null, 2), "utf8");
|
|
582
|
+
results.push({
|
|
583
|
+
surface: surface.id,
|
|
584
|
+
endpoint: endpoint.id,
|
|
585
|
+
status: "captured",
|
|
586
|
+
method: endpoint.method,
|
|
587
|
+
path: prepared.path,
|
|
588
|
+
responseFile,
|
|
589
|
+
...(requestBodyFile ? { requestBodyFile, requestBodySha256: sha256Json(prepared.body) } : {}),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
results.push({
|
|
594
|
+
surface: surface.id,
|
|
595
|
+
endpoint: endpoint.id,
|
|
596
|
+
status: "failed",
|
|
597
|
+
method: endpoint.method,
|
|
598
|
+
path: prepared.path,
|
|
599
|
+
responseFile,
|
|
600
|
+
...(requestBodyFile ? { requestBodyFile, requestBodySha256: sha256Json(prepared.body) } : {}),
|
|
601
|
+
error: error instanceof Error ? error.message : String(error),
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const manifestPath = join(outDir, "capture-manifest.json");
|
|
607
|
+
const manifest = {
|
|
608
|
+
generatedAt: new Date().toISOString(),
|
|
609
|
+
portalId,
|
|
610
|
+
mode: "capture",
|
|
611
|
+
guardrails: INTERNAL_CAPTURE_GUARDRAILS,
|
|
612
|
+
surfaces: selected.map((surface) => surface.id),
|
|
613
|
+
totals: summarize(results),
|
|
614
|
+
results: redactSensitive(results),
|
|
615
|
+
};
|
|
616
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
617
|
+
const totals = summarize(results);
|
|
618
|
+
printResult(ctx, {
|
|
619
|
+
surface: opts.all ? "all" : selected[0]?.id,
|
|
620
|
+
...totals,
|
|
621
|
+
manifest: manifestPath,
|
|
622
|
+
outDir,
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function collectValues(value, previous) {
|
|
627
|
+
return [...previous, value];
|
|
628
|
+
}
|
|
629
|
+
function buildInternalCoverage(ctx) {
|
|
630
|
+
const endpoints = INTERNAL_SURFACES.flatMap((surface) => surface.endpoints);
|
|
631
|
+
const readLikePostEndpoints = endpoints.filter((endpoint) => endpoint.method === "POST");
|
|
632
|
+
const parameterizedEndpoints = endpoints.filter((endpoint) => endpointPlaceholders(endpoint).length > 0);
|
|
633
|
+
const bodyRequiredEndpoints = endpoints.filter((endpoint) => Boolean(endpoint.bodyRequired));
|
|
634
|
+
return {
|
|
635
|
+
guardrails: INTERNAL_CAPTURE_GUARDRAILS,
|
|
636
|
+
catalogue: {
|
|
637
|
+
posture: "allowlisted-capture-adapters",
|
|
638
|
+
allowlistSource: "src/commands/internal/catalog.ts",
|
|
639
|
+
fullCatalogue: "docs/INTERNAL-ENDPOINTS.md",
|
|
640
|
+
discoveryLog: "docs/INTERNAL-ENDPOINTS-DISCOVERY.md",
|
|
641
|
+
condensedDocs: "docs/INTERNAL_ENDPOINTS.md",
|
|
642
|
+
surfaces: INTERNAL_SURFACES.length,
|
|
643
|
+
endpoints: endpoints.length,
|
|
644
|
+
getEndpoints: endpoints.filter((endpoint) => endpoint.method === "GET").length,
|
|
645
|
+
readLikePostEndpoints: readLikePostEndpoints.length,
|
|
646
|
+
placeholderEndpoints: parameterizedEndpoints.length,
|
|
647
|
+
bodyRequiredEndpoints: bodyRequiredEndpoints.length,
|
|
648
|
+
},
|
|
649
|
+
surfaces: INTERNAL_SURFACES.map((surface) => {
|
|
650
|
+
const surfacePosts = surface.endpoints.filter((endpoint) => endpoint.method === "POST");
|
|
651
|
+
const surfaceParameterized = surface.endpoints.filter((endpoint) => (endpointPlaceholders(endpoint).length > 0 || endpoint.bodyRequired));
|
|
652
|
+
return {
|
|
653
|
+
id: surface.id,
|
|
654
|
+
label: surface.label,
|
|
655
|
+
description: surface.description,
|
|
656
|
+
mode: surface.mode,
|
|
657
|
+
endpoints: surface.endpoints.length,
|
|
658
|
+
getEndpoints: surface.endpoints.length - surfacePosts.length,
|
|
659
|
+
readLikePostEndpoints: surfacePosts.length,
|
|
660
|
+
parameterizedEndpoints: surfaceParameterized.map((endpoint) => endpoint.id),
|
|
661
|
+
captureCommand: internalCoverageCaptureCommand(ctx, surface),
|
|
662
|
+
};
|
|
663
|
+
}),
|
|
664
|
+
writeAdapterPolicy: [
|
|
665
|
+
"Public API first; internal adapters fill HubSpot UI-only gaps.",
|
|
666
|
+
"Internal write/replay endpoints stay documented until promoted into explicit guarded commands.",
|
|
667
|
+
"Promoted write adapters must keep dry-run, --force, policy/change-ticket checks, and trace/audit output.",
|
|
668
|
+
"No arbitrary internal URL passthrough is exposed.",
|
|
669
|
+
],
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function internalCoverageCaptureCommand(ctx, surface) {
|
|
673
|
+
return [
|
|
674
|
+
"hscli",
|
|
675
|
+
"--profile",
|
|
676
|
+
ctx.profile,
|
|
677
|
+
"--json",
|
|
678
|
+
"internal",
|
|
679
|
+
"capture",
|
|
680
|
+
"--surface",
|
|
681
|
+
surface.id,
|
|
682
|
+
"--portal-id",
|
|
683
|
+
"<portalId>",
|
|
684
|
+
"--cookie-file",
|
|
685
|
+
"<hubspot-cookie-file>",
|
|
686
|
+
"--out-dir",
|
|
687
|
+
"<backupDir>/internal-api/allowlisted-capture",
|
|
688
|
+
].join(" ");
|
|
689
|
+
}
|
|
690
|
+
function endpointPlaceholders(endpoint) {
|
|
691
|
+
const placeholders = new Set();
|
|
692
|
+
for (const match of endpoint.path.matchAll(/\{([A-Za-z][A-Za-z0-9_]*)\}/g)) {
|
|
693
|
+
if (match[1] !== "portalId")
|
|
694
|
+
placeholders.add(match[1]);
|
|
695
|
+
}
|
|
696
|
+
return [...placeholders];
|
|
697
|
+
}
|
|
698
|
+
function selectSurfaces(surfaceId, all) {
|
|
699
|
+
if (all && surfaceId)
|
|
700
|
+
throw new CliError("INTERNAL_CAPTURE_SELECTION_INVALID", "Use --all or --surface, not both.");
|
|
701
|
+
if (all)
|
|
702
|
+
return INTERNAL_SURFACES;
|
|
703
|
+
const id = surfaceId?.trim();
|
|
704
|
+
if (!id)
|
|
705
|
+
throw new CliError("INTERNAL_CAPTURE_SURFACE_REQUIRED", "internal capture requires --surface <id> or --all.");
|
|
706
|
+
const surface = INTERNAL_SURFACES.find((item) => item.id === id);
|
|
707
|
+
if (!surface) {
|
|
708
|
+
throw new CliError("INTERNAL_CAPTURE_SURFACE_UNKNOWN", `Unknown internal surface "${id}". Run hscli internal surfaces.`);
|
|
709
|
+
}
|
|
710
|
+
return [surface];
|
|
711
|
+
}
|
|
712
|
+
function selectEndpoints(surfaces, endpointIds) {
|
|
713
|
+
const requested = endpointIds.map((id) => id.trim()).filter(Boolean);
|
|
714
|
+
if (requested.length === 0)
|
|
715
|
+
return surfaces;
|
|
716
|
+
const requestedSet = new Set(requested);
|
|
717
|
+
const selected = surfaces
|
|
718
|
+
.map((surface) => ({
|
|
719
|
+
...surface,
|
|
720
|
+
endpoints: surface.endpoints.filter((endpoint) => requestedSet.has(endpoint.id)),
|
|
721
|
+
}))
|
|
722
|
+
.filter((surface) => surface.endpoints.length > 0);
|
|
723
|
+
const found = new Set(selected.flatMap((surface) => surface.endpoints.map((endpoint) => endpoint.id)));
|
|
724
|
+
const missing = requested.filter((id) => !found.has(id));
|
|
725
|
+
if (missing.length > 0) {
|
|
726
|
+
throw new CliError("INTERNAL_CAPTURE_ENDPOINT_UNKNOWN", `Unknown endpoint(s) for selected surface(s): ${missing.join(", ")}.`);
|
|
727
|
+
}
|
|
728
|
+
return selected;
|
|
729
|
+
}
|
|
730
|
+
function parseVarFlags(flags) {
|
|
731
|
+
const vars = {};
|
|
732
|
+
for (const flag of flags) {
|
|
733
|
+
const index = flag.indexOf("=");
|
|
734
|
+
if (index <= 0)
|
|
735
|
+
throw new CliError("INTERNAL_CAPTURE_VAR_INVALID", "--var must be key=value.");
|
|
736
|
+
const key = flag.slice(0, index).trim();
|
|
737
|
+
const value = flag.slice(index + 1).trim();
|
|
738
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(key))
|
|
739
|
+
throw new CliError("INTERNAL_CAPTURE_VAR_INVALID", `Invalid var name "${key}".`);
|
|
740
|
+
if (!value)
|
|
741
|
+
throw new CliError("INTERNAL_CAPTURE_VAR_INVALID", `--var ${key}=... requires a value.`);
|
|
742
|
+
vars[key] = value;
|
|
743
|
+
}
|
|
744
|
+
return vars;
|
|
745
|
+
}
|
|
746
|
+
function parseBodyFileFlags(flags) {
|
|
747
|
+
const bodies = {};
|
|
748
|
+
for (const flag of flags) {
|
|
749
|
+
const index = flag.indexOf("=");
|
|
750
|
+
if (index <= 0)
|
|
751
|
+
throw new CliError("INTERNAL_CAPTURE_BODY_FILE_INVALID", "--body-file must be endpoint=path.");
|
|
752
|
+
const endpointId = flag.slice(0, index).trim();
|
|
753
|
+
const path = flag.slice(index + 1).trim();
|
|
754
|
+
if (!endpointId || !path)
|
|
755
|
+
throw new CliError("INTERNAL_CAPTURE_BODY_FILE_INVALID", "--body-file must be endpoint=path.");
|
|
756
|
+
const raw = readFileSync(path, "utf8");
|
|
757
|
+
try {
|
|
758
|
+
bodies[endpointId] = { sourceFile: path, body: JSON.parse(raw) };
|
|
759
|
+
}
|
|
760
|
+
catch (error) {
|
|
761
|
+
throw new CliError("INTERNAL_CAPTURE_BODY_FILE_INVALID", `Body file for ${endpointId} must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return bodies;
|
|
765
|
+
}
|
|
766
|
+
function dryRunEndpoint(surface, endpoint, portalId, vars, bodyFiles) {
|
|
767
|
+
const prepared = prepareEndpoint(endpoint, portalId, vars, bodyFiles);
|
|
768
|
+
return {
|
|
769
|
+
surface: surface.id,
|
|
770
|
+
id: endpoint.id,
|
|
771
|
+
method: endpoint.method,
|
|
772
|
+
path: prepared.path,
|
|
773
|
+
status: prepared.ready ? "ready" : "needs-input",
|
|
774
|
+
missing: prepared.ready ? [] : prepared.missing,
|
|
775
|
+
bodySourceFile: prepared.ready && prepared.bodySourceFile ? prepared.bodySourceFile : undefined,
|
|
776
|
+
purpose: endpoint.purpose,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function prepareEndpoint(endpoint, portalId, vars, bodyFiles) {
|
|
780
|
+
const missing = [];
|
|
781
|
+
const path = endpoint.path.replace(/\{([A-Za-z][A-Za-z0-9_]*)\}/g, (_match, key) => {
|
|
782
|
+
if (key === "portalId")
|
|
783
|
+
return encodeURIComponent(portalId);
|
|
784
|
+
const value = vars[key];
|
|
785
|
+
if (!value) {
|
|
786
|
+
missing.push(key);
|
|
787
|
+
return `{${key}}`;
|
|
788
|
+
}
|
|
789
|
+
return encodeURIComponent(value);
|
|
790
|
+
});
|
|
791
|
+
if (missing.length > 0) {
|
|
792
|
+
return { ready: false, path, reason: "missing-placeholder", missing };
|
|
793
|
+
}
|
|
794
|
+
const explicitBody = bodyFiles[endpoint.id];
|
|
795
|
+
if (explicitBody && endpoint.method !== "POST") {
|
|
796
|
+
return { ready: false, path, reason: "body-file-for-non-post", missing: [] };
|
|
797
|
+
}
|
|
798
|
+
if (explicitBody) {
|
|
799
|
+
return { ready: true, path, body: explicitBody.body, bodySourceFile: explicitBody.sourceFile };
|
|
800
|
+
}
|
|
801
|
+
if (endpoint.bodyRequired && endpoint.defaultBody === undefined) {
|
|
802
|
+
return { ready: false, path, reason: "missing-read-body", missing: ["body"] };
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
ready: true,
|
|
806
|
+
path,
|
|
807
|
+
...(endpoint.defaultBody !== undefined ? { body: endpoint.defaultBody } : {}),
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function sha256Json(value) {
|
|
811
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
812
|
+
}
|
|
813
|
+
function summarize(results) {
|
|
814
|
+
return {
|
|
815
|
+
captured: results.filter((result) => result.status === "captured").length,
|
|
816
|
+
skipped: results.filter((result) => result.status === "skipped").length,
|
|
817
|
+
failed: results.filter((result) => result.status === "failed").length,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function safeFileName(raw) {
|
|
821
|
+
return raw.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "capture";
|
|
822
|
+
}
|
|
823
|
+
//# sourceMappingURL=index.js.map
|