diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index dbd3c9c..30e3b8f 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -3,6 +3,13 @@ > Open blockers and cross-repo follow-ups. Append new entries at the top; > change only the `Status` line of an existing entry. +## ISS-SURFACE-UNPLANNED — 134 documented HubSpot functions belong to no deliverable + +- **Status:** OPEN (found 2026-10-20 by D-API-0's deliverable map; operator decision) +- **Where:** `data/hubspot/surface_map.tsv` rows with `UNPLANNED`. +- **What:** 19 spec families (134 functions) have no D-id: marketing events (37), app platform (22: CRM cards, extensions, feature flags, uninstall hooks), file manager (19), partner program (14), custom behavioural events (12), portal limits (9), record tags (6), answer-engine optimisation (5), data-studio ingestion (4), deal splits and forecasts (4), API metadata (2). +- **Decide per group:** a new D-id, folded into an existing one (the map row changes and the test re-pins), or out of scope (the row stays `UNPLANNED` with the reason). Candidates that fit existing work: custom behavioural events as workflow triggers (D-FLOW-2) and report inputs (D-QRY-6); file manager onto spear's document hub; record tags as a ClassView property. + ## ISS-PACKED-RUN-RBAC-GRAIN — a packed run is granted as a whole, and contact's holds personal attributes - **Status:** OPEN (found 2026-10-20 by ISS-FIELD-MASK-256's resolution; input for D-IAM-3) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 2c438c7..25acd90 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -284,8 +284,11 @@ changes shape in 2026-09 (`/crm/v3/objects/x` → `/crm/objects/2026-09/x`), so the page slug, not the path, is what says it is the same function. - Measured 2026-10-20 (`coverage data/hubspot/api_surface.tsv - .cache/harvest.json`): about **1,518 distinct functions** — 980 current, - 440 legacy-only, 98 beta-only. The stable harvest holds 912 of the 980 + .cache/harvest.json`): about **1,506 distinct functions** — 980 current, + 440 legacy-only, 86 beta-only (corrected from 1,518 / 98 on 2026-10-21: + 12 beta pages also exist in legacy and were counted twice; the area + table's "beta only" column still counts beta pages without a *current* + counterpart, 98). The stable harvest holds 912 of the 980 current ones. | area | current (2026-09) | in stable harvest | legacy only | beta only | @@ -318,6 +321,73 @@ page-slug match). A Haiku `census-counter` run confirmed the per-version counts (AGENT_LOG 2026-10-20). +## The API surface by deliverable (D-API-0) + +- `data/hubspot/surface_map.tsv` maps each of the 140 spec families + (`crm-contacts`, `cms-pages`, …; `surface::family` strips directory, + `.json` and version) to the D-id that owns it, or `UNPLANNED` with a + reason. `hubspo-harvest deliverables ` counts the + functions per D-id and refuses a family with no row. +- A test pins it to the committed data: every family in the surface has a + row, no row names a family that is not there, every D-id is on + STATUS_BOARD, and the functions sum to 1,506 (a page in both legacy and + beta counts once, under legacy). +- The mapping is a judgement, recorded so it can be argued with row by row. + Object CRUD of every object type (including types with no concept, like + courses and listings) is D-API-1, which is why it holds a third of the + surface. Measured 2026-10-20: + +| deliverable | spec families | current | legacy only | beta only | functions | +|---|---|---|---|---|---| +| D-API-1 | 43 | 377 | 168 | 0 | 545 | +| D-MKT-4 | 21 | 208 | 77 | 0 | 285 | +| UNPLANNED | 19 | 99 | 30 | 5 | 134 | +| D-COM-1 | 10 | 39 | 3 | 28 | 70 | +| D-MKT-2 | 7 | 27 | 32 | 0 | 59 | +| D-MAIL-5 | 3 | 22 | 25 | 0 | 47 | +| D-QRY-2 | 1 | 31 | 1 | 0 | 32 | +| D-QRY-6 | 2 | 0 | 8 | 22 | 30 | +| D-MKT-3 | 1 | 26 | 1 | 0 | 27 | +| D-UI-1 | 1 | 0 | 24 | 0 | 24 | +| D-IAM-4 | 2 | 19 | 2 | 0 | 21 | +| D-FLOW-1 | 2 | 0 | 16 | 4 | 20 | +| D-LOG-2 | 1 | 19 | 0 | 0 | 19 | +| D-FLOW-3 | 1 | 17 | 0 | 0 | 17 | +| D-MAIL-10 | 2 | 13 | 4 | 0 | 17 | +| D-MKT-1 | 3 | 0 | 17 | 0 | 17 | +| D-API-2 | 1 | 8 | 9 | 0 | 17 | +| D-SVC-2 | 1 | 0 | 0 | 15 | 15 | +| D-ACT-3 | 2 | 11 | 4 | 0 | 15 | +| D-MAIL-6 | 2 | 8 | 5 | 1 | 14 | +| D-FLOW-4 | 1 | 9 | 4 | 0 | 13 | +| D-STO-3 | 1 | 9 | 2 | 0 | 11 | +| D-IAM-2 | 1 | 9 | 0 | 0 | 9 | +| D-API-3 | 1 | 3 | 5 | 0 | 8 | +| D-INT-3 | 1 | 0 | 0 | 8 | 8 | +| D-ONT-7 | 1 | 6 | 0 | 0 | 6 | +| D-MAIL-8 | 1 | 5 | 0 | 0 | 5 | +| D-SIM-2 | 1 | 5 | 0 | 0 | 5 | +| D-ACT-2 | 2 | 3 | 1 | 0 | 4 | +| D-ONT-6 | 1 | 2 | 2 | 0 | 4 | +| D-INT-4 | 1 | 0 | 0 | 3 | 3 | +| D-IAM-6 | 1 | 3 | 0 | 0 | 3 | +| D-IAM-5 | 1 | 1 | 0 | 0 | 1 | +| D-INT-6 | 1 | 1 | 0 | 0 | 1 | +| **total** | 140 | 980 | 440 | 86 | 1,506 | + +- Unplanned, 134 functions in 11 groups (`ISS-SURFACE-UNPLANNED`): + marketing events 37, app platform 22 (cards, extensions, feature flags, + uninstalls), file manager 19, partner program 14, custom behavioural + events 12, portal limits 9, record tags 6, answer-engine optimisation 5, + data-studio ingestion 4, deal splits and forecasts 4, API metadata 2. +- 4 mapping tests; seven disable runs measured red (unmapped family, + duplicate row, version-suffix strip, legacy-only by page slug, D-id not on + the board, missing row, stale row), plus the legacy/beta double count + (Codex on #40), red when the legacy check is removed. +- D-ids with no surface functions are internal (store, simulation, query + engine, UI, CI) or have their API inside another D-id's families: D-SVC-1's + tickets and feedback are D-API-1 CRUD, D-IAM-1's roles have no public API. + ## Probe P-QDB-1 (Quack as the database; plan H-ADR-6, D-QRY-1) `probes/p-qdb-1`. Command: diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 2641bce..51e1aef 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -28,7 +28,7 @@ | D-ONT-3 | Mint dossier: per candidate concept (H-ADR-2 table) the METHOD/STORAGE/STRUCTURE evidence + OGIT source + convergence argument; submit to the 5+3 codebook council | M | Shipped | ratified v3 `.claude/plans/hubspo-concept-mint-dossier-v3.md` (5+3 council, 2026-10-10) | | D-ONT-4 | OGAR PR: `ConceptDomain::Crm` (`0x05XX`), 15 concepts in `class_ids` + `CODEBOOK` + `ALL`, `HubSpoPort` (`0x000B`) + aliases, layout doc row; lance-graph `ogar_codebook` mirror rows + `AppPrefix` 0x0008–0x000B. `crm_actions` deferred to D-ACT-1 (dossier v3 §7) | M | Shipped | OGAR#330, lance-graph#1438 | | D-ONT-5 | `hubspo-port`: `RbacPlug` binding; `HotPlug` over Crm ids asserted to fail `NoCapabilitiesFor` until D-ACT-1 (dossier v3 §7); deliberately-wrong-plug test proving each drift arm can fire | S | Shipped | `hubspo-port`, 13 tests (`04edf87` on main) | -| D-ONT-6 | `hubspo-schema`: ClassViews for contact, company, deal, ticket, lead, product, line item, quote, activity kinds; standard property position tables via the generator into `hubspo-generated` | L | In PR | 2026-10-20 provisional (operator: public docs): `hubspo-harvest` objdef/guide/merge/emit from HubSpot's object-definition pages and activity guides, `data/hubspot/standard_properties.tsv` (24 object types, 1,413 properties by internal name), generated tables, `hubspo-schema::HubSpoClassView` keyed by concept for 13 object types (counts pinned); contact passes the 256-position mask ceiling (`ISS-FIELD-MASK-256`); see `ISS-KB-PROPERTIES-PROVISIONAL`. Gate G1 stays open until the portal. Unblocked 2026-10-20: `ISS-CRM-KEY-CLASSVIEW` decided, CRM keys carry classview `0x0000` (`concept << 16`); ClassViews keyed by concept. Earlier: `ISS-CRM-KEY-CLASSVIEW`: no ClassView or key on the `0x000B` prefix until answered (plan H-ADR-1 note) | +| D-ONT-6 | `hubspo-schema`: ClassViews for contact, company, deal, ticket, lead, product, line item, quote, activity kinds; standard property position tables via the generator into `hubspo-generated` | L | Shipped | 2026-10-20 provisional (operator: public docs): `hubspo-harvest` objdef/guide/merge/emit from HubSpot's object-definition pages and activity guides, `data/hubspot/standard_properties.tsv` (24 object types, 1,413 properties by internal name), generated tables, `hubspo-schema::HubSpoClassView` keyed by concept for 13 object types (counts pinned); contact passes the 256-position mask ceiling (`ISS-FIELD-MASK-256`); see `ISS-KB-PROPERTIES-PROVISIONAL`. Gate G1 stays open until the portal. Unblocked 2026-10-20: `ISS-CRM-KEY-CLASSVIEW` decided, CRM keys carry classview `0x0000` (`concept << 16`); ClassViews keyed by concept. Earlier: `ISS-CRM-KEY-CLASSVIEW`: no ClassView or key on the `0x000B` prefix until answered (plan H-ADR-1 note) | | D-ONT-7 | Custom-object/-property design (H-ADR-3) + probe P-CUSTOM-1 (lane-per-property vs packed run; fold latency at 50 / 500 / 2,000 custom properties) | M | In progress | P-CUSTOM-1 2026-10-10: lanes 0.73–1.61× std, packed 15–35×; the 33–96 s is a fresh K-column `Dataset::write`, not updates. Both measured paths are off the shipped path since #23/#25: re-probe as P-CUSTOM-2 under the resident fold | ## Wave 2 — Store, versions, history @@ -144,7 +144,7 @@ | D-id | Deliverable | Size | Status | Evidence | |---|---|---|---|---| -| D-API-0 | The documented API surface as data: every operation of legacy, 2026-09 and 2027-03-beta, with coverage per area (the map of functions to rebuild) | M | In PR | 2026-10-20: `hubspo-harvest surface`/`coverage`, `scripts/fetch-hubspot-surface.sh`, `data/hubspot/api_surface.tsv` — 2,279 endpoints (legacy 1,194, 2026-09 980, beta 105; 2,325 doc entries, generic endpoints listed by several specs); ~1,518 distinct functions (980 current + 440 legacy-only + 98 beta-only). Table in LATEST_STATE. Linear claim: EXO-50 `session_01PPcakjk7ioQsnasmNDtc5z/002` (D-API-SURFACE) | +| D-API-0 | The documented API surface as data: every operation of legacy, 2026-09 and 2027-03-beta, with coverage per area (the map of functions to rebuild) | M | Shipped | 2026-10-20: `hubspo-harvest surface`/`coverage`, `scripts/fetch-hubspot-surface.sh`, `data/hubspot/api_surface.tsv` — 2,279 endpoints (legacy 1,194, 2026-09 980, beta 105; 2,325 doc entries, generic endpoints listed by several specs); ~1,506 distinct functions (980 current + 440 legacy-only + 86 beta-only; corrected from 1,518, 12 beta pages double-counted). Table in LATEST_STATE. Linear claim: EXO-50 `session_01PPcakjk7ioQsnasmNDtc5z/002` (D-API-SURFACE). Shipped in #39. Deliverable map: `data/hubspot/surface_map.tsv` + `hubspo-harvest deliverables` (140 families → 33 D-ids, 134 functions unplanned: `ISS-SURFACE-UNPLANNED`) | | D-API-1 | `/crm/v3/objects/*` CRUD + batch + search on the façade; association v4; properties; pipelines; owners | XL | Queued | | | D-API-2 | Webhook subscriptions (outbound) from version deltas | M | Queued | | | D-API-3 | OAuth apps / private app tokens mapped to RBAC actors with delegation | M | Queued | | diff --git a/crates/hubspo-harvest/src/main.rs b/crates/hubspo-harvest/src/main.rs index 1a3c9f0..dc9a3d6 100644 --- a/crates/hubspo-harvest/src/main.rs +++ b/crates/hubspo-harvest/src/main.rs @@ -25,6 +25,11 @@ //! output already holds, and how many also exist under the current version: //! `version area operations harvested in_current`. //! +//! `hubspo-harvest deliverables ` — prints, per +//! deliverable in the map, the documented functions it owns: +//! `deliverable families current legacy_only +//! beta_only functions`. A family without a map row is an error. +//! //! `hubspo-harvest emit ` — prints the generated Rust for //! `hubspo-generated/src/standard_properties.rs`. @@ -194,6 +199,24 @@ fn run(args: &[String]) -> Result<(), String> { } Ok(()) } + [cmd, table, map] if cmd == "deliverables" => { + let ops = surface::parse_tsv(&read(table)?).map_err(|l| format!("{table}: bad line {l}"))?; + let map_rows = surface::parse_map(&read(map)?).map_err(|l| format!("{map}: bad line {l}"))?; + let counts = surface::by_deliverable(&ops, &map_rows) + .map_err(|f| format!("{map}: no row for spec family {f}"))?; + for c in counts { + println!( + "{}\t{}\t{}\t{}\t{}\t{}", + c.deliverable, + c.families, + c.current, + c.legacy_only, + c.beta_only, + c.functions() + ); + } + Ok(()) + } [cmd, table] if cmd == "emit" => { let rows = read_table(Path::new(table))?; print!("{}", positions::emit(&rows)); @@ -201,7 +224,7 @@ fn run(args: &[String]) -> Result<(), String> { } _ => Err( "usage: hubspo-harvest select | report | objdef \ - | guide | surface ... | coverage | merge | emit " + | guide | surface ... | coverage | deliverables | merge | emit " .to_string(), ), } diff --git a/crates/hubspo-harvest/src/surface.rs b/crates/hubspo-harvest/src/surface.rs index 7b0f454..679f27f 100644 --- a/crates/hubspo-harvest/src/surface.rs +++ b/crates/hubspo-harvest/src/surface.rs @@ -259,6 +259,135 @@ pub fn parse_tsv(tsv: &str) -> Result, usize> { .collect() } +/// A spec file's family: its name without directory, `.json` and version +/// suffix (`v3/crm-contacts-v3.json` → `crm-contacts`), so one family +/// groups a function across versions. +#[must_use] +pub fn family(spec: &str) -> String { + let name = spec.rsplit('/').next().unwrap_or(spec); + let name = name.strip_suffix(".json").unwrap_or(name); + match name.rsplit_once("-v") { + Some((head, tail)) if is_version(&format!("v{tail}")) || is_version(tail) => { + head.to_string() + } + _ => name.to_string(), + } +} + +/// One row of the family → deliverable map: the D-id that owns a spec +/// family, or `UNPLANNED` with the reason in `note`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MapRow { + pub family: String, + pub deliverable: String, + pub note: String, +} + +/// The deliverable value for a family no deliverable owns yet. +pub const UNPLANNED: &str = "UNPLANNED"; + +/// Read the map TSV (`family deliverable note`). +/// +/// # Errors +/// +/// The 1-based number of the first line without three fields, with an +/// empty field, or naming a family twice. +pub fn parse_map(tsv: &str) -> Result, usize> { + let mut out: Vec = Vec::new(); + for (i, line) in tsv.lines().enumerate().filter(|(_, l)| !l.is_empty()) { + let f: Vec<&str> = line.split('\t').collect(); + let [family, deliverable, note] = f[..] else { + return Err(i + 1); + }; + if [family, deliverable, note].iter().any(|s| s.is_empty()) + || out.iter().any(|r| r.family == family) + { + return Err(i + 1); + } + out.push(MapRow { + family: family.into(), + deliverable: deliverable.into(), + note: note.into(), + }); + } + Ok(out) +} + +/// The documented functions one deliverable owns. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct DeliverableCount { + pub deliverable: String, + /// Spec families mapped to it. + pub families: usize, + /// Endpoints of the current version (`latest` tab). + pub current: usize, + /// Legacy endpoints whose reference page has no current counterpart. + pub legacy_only: usize, + /// Beta endpoints whose reference page has neither a current nor a + /// legacy counterpart (a page in both legacy and beta is one function, + /// counted under legacy). + pub beta_only: usize, +} + +impl DeliverableCount { + /// Distinct functions: current plus the legacy-only and beta-only ones. + #[must_use] + pub fn functions(&self) -> usize { + self.current + self.legacy_only + self.beta_only + } +} + +/// Count the surface per deliverable, in map order. An operation belongs to +/// the family of its first spec. +/// +/// # Errors +/// +/// The first family with operations but no map row. +pub fn by_deliverable(ops: &[Operation], map: &[MapRow]) -> Result, String> { + let current: std::collections::BTreeSet = ops + .iter() + .filter(|o| tab(&o.href) == "latest") + .map(|o| page_slug(&o.href)) + .collect(); + let legacy: std::collections::BTreeSet = ops + .iter() + .filter(|o| o.version == "legacy") + .map(|o| page_slug(&o.href)) + .collect(); + let mut out: Vec = Vec::new(); + for row in map { + if !out.iter().any(|c| c.deliverable == row.deliverable) { + out.push(DeliverableCount { + deliverable: row.deliverable.clone(), + ..DeliverableCount::default() + }); + } + if let Some(c) = out.iter_mut().find(|c| c.deliverable == row.deliverable) { + c.families += 1; + } + } + for op in ops { + let fam = family(op.specs.first().map_or("", String::as_str)); + let row = map + .iter() + .find(|r| r.family == fam) + .ok_or_else(|| fam.clone())?; + let Some(c) = out.iter_mut().find(|c| c.deliverable == row.deliverable) else { + return Err(fam); + }; + if tab(&op.href) == "latest" { + c.current += 1; + } else if !current.contains(&page_slug(&op.href)) { + if op.version == "legacy" { + c.legacy_only += 1; + } else if !legacy.contains(&page_slug(&op.href)) { + c.beta_only += 1; + } + } + } + Ok(out) +} + #[cfg(test)] mod tests { use super::*; @@ -438,6 +567,110 @@ mod tests { assert_eq!(coverage(&alone, &harvested)[0].in_current, 0); } + #[test] + fn family_drops_directory_suffix_and_version() { + assert_eq!(family("v3/crm-contacts-v3.json"), "crm-contacts"); + assert_eq!(family("crm-contacts-v2026-09.json"), "crm-contacts"); + assert_eq!( + family("account-account-info-v2027-03-beta.json"), + "account-account-info" + ); + assert_eq!(family("v1/forms.json"), "forms"); + // `-v` inside a name is not a version. + assert_eq!( + family("v3/crm-video-conferencing-extension-v3.json"), + "crm-video-conferencing-extension" + ); + assert_eq!( + family("v3/crm-public-app-feature-flags-v3-v3.json"), + "crm-public-app-feature-flags-v3" + ); + } + + #[test] + fn deliverables_count_current_legacy_only_and_beta_only() { + let mut beta = op("2027-03-beta", "crm", "GET", "/crm/2027-03-beta/new"); + beta.specs = vec!["crm-new-v2027-03-beta.json".into()]; + let mut legacy_same = op("legacy", "crm", "GET", "/crm/v3/objects/contacts"); + legacy_same.specs = vec!["v3/crm-contacts-v3.json".into()]; + let mut legacy_only = op("legacy", "crm", "GET", "/crm/v3/old"); + legacy_only.specs = vec!["v3/crm-contacts-v3.json".into()]; + let mut cur = op("2026-09", "crm", "GET", "/crm/objects/2026-09/contacts"); + cur.specs = vec!["crm-contacts-v2026-09.json".into()]; + // The same page as `legacy_only`, in beta: one function, not two. + let mut beta_twin = op("2027-03-beta", "crm", "GET", "/crm/2027-03-beta/old"); + beta_twin.specs = vec!["crm-new-v2027-03-beta.json".into()]; + let ops = [legacy_same, legacy_only, cur, beta, beta_twin]; + let map = + parse_map("crm-contacts\tD-API-1\tcrud\ncrm-new\tUNPLANNED\tnew\nunused\tD-API-1\tx\n") + .unwrap(); + let c = by_deliverable(&ops, &map).unwrap(); + let rows: Vec<(&str, usize, usize, usize, usize)> = c + .iter() + .map(|r| { + ( + r.deliverable.as_str(), + r.families, + r.current, + r.legacy_only, + r.beta_only, + ) + }) + .collect(); + assert_eq!(rows, [("D-API-1", 2, 1, 1, 0), ("UNPLANNED", 1, 0, 0, 1)]); + assert_eq!(c[0].functions(), 2); + // A family without a map row is refused, never dropped. + assert_eq!(by_deliverable(&ops, &map[..1]), Err("crm-new".to_string())); + } + + #[test] + fn map_rows_are_complete_and_unique() { + assert_eq!(parse_map("a\tD-X-1\n"), Err(1)); + assert_eq!(parse_map("a\t\tnote\n"), Err(1)); + assert_eq!(parse_map("a\tD-X-1\tn\na\tD-X-2\tn\n"), Err(2)); + assert_eq!( + parse_map("a\tD-X-1\tn\nb\tD-X-1\tn\n").map(|m| m.len()), + Ok(2) + ); + } + + /// The committed map covers every family of the committed surface, names + /// only families that exist, and only D-ids on the status board. + #[test] + fn the_committed_map_matches_the_surface_and_the_board() { + let ops = parse_tsv(include_str!("../../../data/hubspot/api_surface.tsv")).unwrap(); + let map = parse_map(include_str!("../../../data/hubspot/surface_map.tsv")).unwrap(); + let counts = by_deliverable(&ops, &map).expect("every family is mapped"); + assert_eq!( + counts + .iter() + .map(DeliverableCount::functions) + .sum::(), + 1506 + ); + let families: std::collections::BTreeSet = ops + .iter() + .map(|o| family(o.specs.first().map_or("", String::as_str))) + .collect(); + for row in &map { + assert!( + families.contains(&row.family), + "stale map row {}", + row.family + ); + } + let board = include_str!("../../../.claude/board/STATUS_BOARD.md"); + for row in &map { + if row.deliverable != UNPLANNED { + assert!( + board.contains(&format!("| {} |", row.deliverable)), + "{} is not on the status board", + row.deliverable + ); + } + } + } + #[test] fn tsv_round_trips() { let ops = vec![ diff --git a/data/hubspot/surface_map.tsv b/data/hubspot/surface_map.tsv new file mode 100644 index 0000000..252f6b0 --- /dev/null +++ b/data/hubspot/surface_map.tsv @@ -0,0 +1,140 @@ +account-account-info D-API-1 portal details on the façade +account-activity D-ACT-2 account activity = action audit +account-audit-logs D-ACT-2 audit log = ActionInvocation audit +analytics-reporting D-QRY-6 reports +auth-oauth D-API-3 OAuth apps and tokens +automation-actions-v4 D-FLOW-3 custom workflow actions +automation-automation-v4 D-FLOW-1 workflows API +automation-email-templates D-MAIL-8 email templates +automation-sequences D-FLOW-4 sequences +blog-comments D-MKT-4 CMS blog (legacy) +blog-details D-MKT-4 CMS blog (legacy) +blog-posts D-MKT-4 CMS blog (legacy) +blog-topics D-MKT-4 CMS blog (legacy) +business-units-business-units D-IAM-5 business units +cms-authors D-MKT-4 CMS +cms-blog-settings D-MKT-4 CMS +cms-cms-content-audit D-MKT-4 CMS +cms-domains D-MKT-4 CMS +cms-hubdb D-MKT-4 CMS data tables +cms-knowledge-base D-SVC-2 knowledge base +cms-media-bridge D-MKT-4 CMS media integrations +cms-pages D-MKT-4 CMS pages +cms-posts D-MKT-4 CMS blog posts +cms-site-search D-MKT-4 CMS site search +cms-source-code D-MKT-4 CMS developer files +cms-tags D-MKT-4 CMS blog tags +cms-url-mappings D-MKT-4 CMS +cms-url-redirects D-MKT-4 CMS +commerce-contracts D-COM-1 commerce +commerce-payment-links D-COM-1 commerce +commerce-payment-methods D-COM-1 commerce +commerce-payments D-COM-1 commerce +commerce-payments-settings D-COM-1 commerce +commerce-paymentsaccounts D-COM-1 commerce +commerce-price-books D-COM-1 commerce +communication-preferences-subscriptions D-MKT-2 subscription types and opt-outs +conversations-conversations D-MAIL-5 shared inbox +conversations-custom-channels D-MAIL-5 inbox channels +conversations-visitor-identification D-MAIL-5 chat visitor identity +create-manage-workflows D-FLOW-1 workflows API (legacy) +crm-activity-auto-associations D-INT-4 AUTO associations +crm-app-uninstalls UNPLANNED app platform: install lifecycle +crm-appointments D-API-1 object CRUD +crm-associations D-API-1 association v4 +crm-associations-schema D-STO-3 association labels +crm-calling-extensions D-MAIL-10 calling (logging only in v1) +crm-calls D-API-1 object CRUD +crm-carts D-API-1 object CRUD +crm-commerce-payments D-API-1 object CRUD +crm-commerce-subscriptions D-API-1 object CRUD +crm-communications D-API-1 object CRUD +crm-companies D-API-1 object CRUD +crm-contacts D-API-1 object CRUD +crm-contracts D-API-1 object CRUD +crm-courses D-API-1 object CRUD +crm-crm-owners D-API-1 owners +crm-custom-objects D-API-1 object CRUD (custom objects) +crm-deal-splits UNPLANNED revenue: deal splits +crm-deals D-API-1 object CRUD +crm-discounts D-API-1 object CRUD +crm-emails D-API-1 object CRUD +crm-engagements D-API-1 object CRUD (legacy engagements) +crm-exports D-IAM-6 exports (accumulation guard) +crm-extensions UNPLANNED app platform: extensions +crm-feedback-submissions D-API-1 object CRUD +crm-fees D-API-1 object CRUD +crm-forecast-types UNPLANNED revenue: forecasting +crm-goal-targets D-API-1 object CRUD +crm-imports D-SIM-2 import through crm-sim +crm-invoices D-API-1 object CRUD +crm-leads D-API-1 object CRUD +crm-limits-tracking UNPLANNED portal limits +crm-line-items D-API-1 object CRUD +crm-listings D-API-1 object CRUD +crm-lists D-QRY-2 lists +crm-meetings D-API-1 object CRUD +crm-notes D-API-1 object CRUD +crm-notetaker-conversation-recap D-INT-6 meeting recap +crm-object-library D-ONT-6 which standard objects are enabled +crm-object-tags UNPLANNED record tags +crm-orders D-API-1 object CRUD +crm-owners D-API-1 owners (legacy) +crm-partner-clients UNPLANNED partner program +crm-partner-services UNPLANNED partner program +crm-pipeline-governance D-ACT-3 stage rules +crm-pipelines D-API-1 pipelines +crm-postal-mail D-API-1 object CRUD +crm-products D-API-1 object CRUD +crm-projects D-API-1 object CRUD +crm-properties D-API-1 properties +crm-properties-v1-companies D-API-1 properties (legacy) +crm-properties-v1-contacts D-API-1 properties (legacy) +crm-properties-v1-deals D-API-1 properties (legacy) +crm-property-validations D-ACT-3 property rules +crm-public-app-crm-cards UNPLANNED app platform: record cards +crm-public-app-feature-flags-v3 UNPLANNED app platform: feature flags +crm-quotes D-API-1 object CRUD +crm-schemas D-ONT-7 custom object schemas +crm-services D-API-1 object CRUD +crm-subscription-lifecycle D-COM-1 subscriptions +crm-tasks D-API-1 object CRUD +crm-tasks-series D-API-1 recurring tasks +crm-taxes D-API-1 object CRUD +crm-tickets D-API-1 object CRUD +crm-timeline D-UI-1 timeline events +crm-transcriptions D-MAIL-10 call transcripts +crm-users D-IAM-4 users +crm-video-conferencing-extension D-MAIL-6 meeting links +data-studio-datasource-ingestion UNPLANNED data studio ingestion +domains D-MKT-4 CMS domains (legacy) +email-analytics D-MKT-2 email statistics +events-events UNPLANNED custom behavioural events +events-manage-event-definitions UNPLANNED custom behavioural events +events-send-event-completions UNPLANNED custom behavioural events +files-files UNPLANNED file manager +forms D-MKT-1 forms (legacy) +forms-v3-legacy D-MKT-1 forms (legacy) +marketing-aeo UNPLANNED answer-engine optimisation +marketing-campaigns-public-api D-MKT-3 campaigns +marketing-events-tracking UNPLANNED marketing events +marketing-forms D-MKT-1 forms +marketing-lead-scoring D-INT-3 lead scoring +marketing-marketing-emails D-MKT-2 marketing email +marketing-marketing-events UNPLANNED marketing events +marketing-single-send D-MKT-2 marketing email +marketing-subscriptions D-MKT-2 subscription types (legacy) +marketing-transactional-single-send D-MKT-2 transactional email +meta-origins UNPLANNED API metadata +modules D-MKT-4 CMS modules (legacy) +pages D-MKT-4 CMS pages (legacy) +reporting D-QRY-6 reports (legacy) +scheduler-meetings D-MAIL-6 booking pages +settings-multicurrency D-COM-1 currencies +settings-tax-rates D-COM-1 tax rates +settings-teams D-IAM-2 teams +settings-user-provisioning D-IAM-4 user provisioning +site-search D-MKT-4 CMS site search (legacy) +transactional-emails D-MKT-2 transactional email (legacy) +webhooks-journal-webhooks-journal D-LOG-2 journal replay +webhooks-webhooks D-API-2 webhooks