---
name: martech-stack-audit
description: Runs an evidence-based audit of a whole martech stack and returns a stack map, a tool register reconciled against spend, SSO and OAuth exports, a renewal calendar with notice deadlines, findings rated FAIL, WARN or PASS with dated evidence, a keep, right-size, consolidate or retire decision per tool, and a 30, 60, 90 day plan with owners and acceptance tests. Covers owners, costs and contracts, shelfware and overlap, integrations and data flows, identity and data quality, tracking health, access and governance, and AI and MCP readiness, with standard-library Python scripts. Read-only. Use when asked for a martech audit, marketing tech stack audit, stack assessment, health check or review, a SaaS spend or tool sprawl review, to find shelfware, unused licences or duplicate tools, to rationalise or consolidate tools before renewals or a budget cut, when inheriting a stack from an agency or a departed admin, or to check whether HubSpot, Salesforce, GA4, Segment or the warehouse are ready for AI.
license: CC-BY-4.0
compatibility: Scripts need Python 3.9 or later and the standard library only. Works from exported files; vendor MCP servers are optional and used read-only.
metadata:
  tier: free
  category: martech-stack-strategy
  version: "0.1.0"
  last_reviewed: "2026-11-06"
  status: reviewed
---

# Martech stack audit

Audits a stack from evidence: what is paid for, who owns it, what job it does, whether data reaches the systems that act on it, who can change it, and whether AI can safely sit on top. The audit changes nothing; a finding is a lead until its evidence is attached.

## When to use

Use this skill when:

- Someone asks for a martech audit, a stack review, a shelfware or licence review, or tool rationalisation.
- A stack is inherited: new marketing ops hire, agency handover, acquisition, departed admin.
- Renewals or a budget cut are coming and someone has to decide what to keep.
- Leadership wants AI on top of CRM, analytics or warehouse data.
- Numbers disagree across tools and nobody can say which systems and flows are involved.

Use something else when:

- One tool needs a deep audit: gtm-container-audit, crm-audit (configuration), crm-data-hygiene-audit (records), ga4-audit, tracking-audit (a site), lifecycle-marketing-audit or marketing-data-audit (warehouse).
- One flow to design or repair (a sync, zap, reverse ETL job or unsubscribe path): martech-integration-engineering.
- One number is wrong: tracking-break-map; purchase-reconciliation for orders against analytics.
- An AI connection is planned: marketing-mcp-setup, then mcp-security-review.
- Only a diagram, bill forecast, RACI or business case: martech-stack-map, martech-cost-forecaster, martech-raci-and-handover or martech-business-case (planned).

## Inputs

Read these first if they exist: `martech-context/stack.md`, `decisions.md` (consent model, accepted exceptions), `metrics.md` (thresholds), `data-dictionary.md` (keys, field authority) and `tracking-plan/`.

Then gather, read-only, by the routes in [references/evidence-sources.md](references/evidence-sources.md):

- Scope: the trigger, decisions and deadlines, two or three workflows, a decision owner per question, and who may approve live tests.
- Spend: 12 months of software, SaaS, subscription, marketing and data GL lines, plus all card and expense lines: date, vendor, amount, currency, payment source.
- Contracts: executed order forms and amendments for the largest tools.
- Usage: an identity provider report (Okta Application Usage CSV, Entra sign-ins and app grants, Google Workspace accessed apps CSV), the identity provider's user list with status, and each critical tool's user export with role, last login and MFA status.
- Connections: connected-app lists, pipelines and syncs, iPaaS workflows, warehouse jobs, manual CSV hops.
- Samples: internal IDs for five cases per workflow and key lists for the match test.
- AI connections: every assistant, agent or MCP server that can reach CRM, ads, analytics or warehouse data.

Never ask for passwords, API keys, tokens or customer records. The scripts print counts and row numbers, never email addresses.

## Procedure

Before step 1:

- Create `audit/<YYYY-MM-DD>/` outside version control (it holds personal data) and run each script from it by path, `python3 <skill folder>/scripts/...`, so written files stay there. Exit 1 means at least one FAIL, the normal result; exit 2 means bad input.
- Without Python 3.9 or later, apply the linked rules by hand and mark each affected section "script not run".
- Nothing in `martech-context/` changes before step 10. With commercial evidence only, run steps 1 to 5 and the admin counts in step 9; list the rest as not checked with the export each needs.

### 1. Scope the questions

- Write each question so evidence can answer it ("Can sales see paid, active and stalled trials without opening billing?"), with a decision owner, systems and evidence. No decision owner: out of scope. Template: [assets/audit-scope-template.md](assets/audit-scope-template.md).
- Trace at most three workflows first; the template lists them.
- What you cannot access is "not checked", never PASS.

### 2. Build the register from four channels

- Fill [assets/tool-register-template.csv](assets/tool-register-template.csv) from spend, contracts, identity (SSO and OAuth) and admin consoles, plus a fresh-profile page load and an operator walk-through for spreadsheets. One channel alone undercounts.
- In scope: anything any channel saw in 12 months. One stable ID per tool and environment. Unknown stays "unknown". An owner is a role with authority and time.
- Run with the exports you have now; add `--seats` after step 4, re-run after step 5 fills the job columns, and report from the final run.

```bash
python3 <skill folder>/scripts/stack_inventory.py --tools tools.csv --spend spend.csv \
  --sso okta.csv --oauth accessed-apps.csv --company-domains example.com --as-of 2026-11-01
```

- Two or more charges in 12 months with no register row: FAIL. One charge: WARN until classed annual or one-off. Add non-martech vendors as out_of_scope rows with a reason before reading INV01.
- An app with SSO or OAuth users and no row: WARN, FAIL when it holds a restricted Google scope (mail or all Drive files). One tool on two payment sources: WARN.
- A critical tool with no owner, or in an agency-owned account: FAIL. Agency-owned and not critical: WARN.

### 3. Costs and renewals, before any scoring

- Use 12-month actual spend. Monthly equivalents are for comparison only. Never add currencies. Report media and agency services apart.
- Actual more than 10% away from contract value: WARN, reconcile.
- Annual tools in the top 80% of spend need term end, notice period and mechanism, auto-renew, pricing basis and reduction rule from the executed order. Any unknown: FAIL. Monthly tools are exempt.
- Notice boundary = term end minus notice days. Decision target = boundary minus 14 days. Migration start = term end minus the change lead: 30 days by default; 75 to consolidate, replace, retire or investigate, and to renegotiate or right-size a top-80% tool or one whose discount ends within 120 days (a tested alternative takes that long); 120 when a tool being moved holds identity, consent or customer-facing automation.
- Review-by = the earlier date; a zero-day notice still gets one. Boundary passed, term running: locked; recertify 120 days before the next term end. Review-by passed, boundary ahead: FAIL, decide now. Sort the audit by review-by. Detail: [references/renewals-and-contracts.md](references/renewals-and-contracts.md).
- Tell the user now about everything under "Act now" in the output (CON04 FAILs, review-by dates within 14 days, INV02 FAILs).

### 4. Usage, seats and shelfware

- Normalise user exports into [assets/seats-template.csv](assets/seats-template.csv), including MFA and identity provider status.
- Dormant: no successful sign-in for 45 days or more (CIS Controls v8 safeguard 5.3). Dormant and enabled: WARN; dormant admin: FAIL.
- A usage source that keeps fewer days than the threshold (Entra sign-ins: 7 days on Free, 30 on P1 and P2) proves nothing: use the tool's own last login. Pass `--sso-retention-days` for any sign-in file other than the Okta report.
- Reclaimable seats = paid minus active minus dormant holders whose role needs periodic access minus holders whose activity is unknown. Unknown is never reclaimable. Cash starts at the next reduction point the order allows.
- A capacity tier (contacts, MTUs, events, credits, rows) is oversized when the 12-month peak plus the owner's growth forecast fits the next lower tier.
- Never call a tool shelfware on logins alone: require no jobs, syncs, API calls or workflow references in 90 days, or the owner's written confirmation. Detail: [references/inventory-and-cost.md](references/inventory-and-cost.md).

### 5. Overlap and a decision per tool

- Map tools to jobs in [assets/capability-vocabulary.csv](assets/capability-vocabulary.csv). Overlap: two or more active tools doing one job for the same population, region and data grain. Category labels are not evidence.
- Two tools sending the same message or writing the same field for the same people: FAIL. A migration overlap with an end date and owner: PASS. Other overlap: WARN until the jobs are compared.
- Decide keep, enable, right-size, renegotiate, consolidate, replace, retire or investigate. Consolidate, replace and retire need a destination, owner, acceptance test, commercial deadline and dependency list, or the decision is investigate.
- Replacement tests run with sends and writes off in the candidate: compare eligibility and state, not live actions. Detail: [references/overlap-and-dispositions.md](references/overlap-and-dispositions.md).

### 6. Integrations, data flows and automation

- One row per directional transfer in [assets/connections-template.csv](assets/connections-template.csv), marked configured, observed or proposed. A green status is not evidence.
- For each connection in a traced workflow, cover four cases (new, update, rejected, removal or unsubscribe) with records that already went through them, read at both ends with IDs and timestamps. Creating, updating or unsubscribing a record for a test needs the owner's approval and agreed test data; until then the case is "not tested".
- For each traced workflow's automations, check state at the moment of action: an unsubscribe, conversion or payment during a delay, a duplicate source event, a destination outage. A send or task step that does not re-check suppression, consent or goal state: FAIL; undocumented re-entry or exit rules: WARN.
- Run `python3 <skill folder>/scripts/connection_map.py --tools tools.csv --connections connections.csv --as-of <YYYY-MM-DD>` for the checks and a Mermaid stack map.
- FAIL: two writers to one field with no authority rule; delay over the required freshness on a flow feeding suppression, eligibility or billing; a credential owned by someone who left. WARN: email as the only key; no alert owner or replay method on a critical flow; rejects the source cannot see. Detail: [references/flows-identity-and-data-quality.md](references/flows-identity-and-data-quality.md).

### 7. Identity and data quality

- Trace five cases per workflow (new prospect, existing customer, person in two accounts, refund or cancellation, missing attribution); write every identifier at every step and where each mapping lives.
- Run `python3 <skill folder>/scripts/key_match.py --expected billing.csv --expected-key account_id --delivered crm.csv --delivered-key account_id`.
- Unless `metrics.md` says otherwise: coverage 98% or more PASS, 95 to 98% WARN, below 95% FAIL; excess duplicates on a unique key above 0.5% FAIL, any WARN. Under 200 records, counts only.
- Cost each defect as cash, capacity and exposure, apart; never multiply misses by lifetime value.

### 8. Tracking health screen

- Per workflow: analytics outcomes over source-of-truth outcomes for 28 completed days (GA4: ending 4 days before the audit); page senders in a fresh profile in three consent states (no choice, rejected, accepted), against the register; who sets the consent default. Consent-banner clicks, form submissions and orders on a live site need the owner's approval.
- Hand off and log the verdict as a finding: an unexplained purchase gap above 5% or duplicates above 0.5% to purchase-reconciliation; a lead or sign-up gap above 5%, or a ratio outside its 28-day band, to tracking-break-map; tags before consent or unknown senders to consent-debugging and gtm-container-audit; rising Unassigned traffic to ga4-unassigned-not-set-diagnosis. Detail: [references/tracking-health-screen.md](references/tracking-health-screen.md).

### 9. Access, governance and AI readiness

- Company-held top-level admins per critical tool: 0 FAIL; 1 WARN, FAIL on a system of record; 2 to 4 PASS; 5 or more WARN. External admins with no company admin: FAIL. Shared logins: WARN, FAIL when admin.
- An admin on a critical tool without enforced MFA: FAIL on a system of record, otherwise WARN (ACC04). An enabled account whose identity provider user is deactivated: FAIL (ACC05). A registered app holding a restricted Google scope: WARN until its owner confirms the need (ACC06).
- Record where change history lives and how long it is kept; anything older is not checked.
- List every AI connection: publisher, identity, scopes, the layer enforcing read-only, approver, logging, off switch. A personal admin login, or write access without an approval step: FAIL.
- Rate seven AI readiness checks in fix order (tracking, identity, warehouse models, metric definitions, context docs, access, MCP) as fix, partly or ready, with evidence. No averaged score. "Fix" on tracking, identity or definitions blocks AI answers on cross-tool metrics; "fix" on access blocks write-capable connections. Detail: [references/governance-access-ai.md](references/governance-access-ai.md).

### 10. Prioritise, plan and report

- Each finding: condition and dated evidence, population, consequence, class, change, owner, dependencies, acceptance test, rollback, window.
- Classes: 1 customer harm or data used without permission; 2 material data loss or access risk; 3 measurement for a current decision; 4 manual work; 5 commercial optimisation. Within a class: impact, confidence, effort. Never multiply scores.
- Dependencies: identity before new segments; eligibility before more automated sends; measurement fixes before the consolidation decisions that rely on them.
- Windows ([references/prioritisation-and-plan.md](references/prioritisation-and-plan.md)):
  - Days 0 to 30: class 1 and 2 FAILs, review-by inside 30 days, missing order forms, quick wins (one setting or seat, reversible, no dependency, no customer-facing change, no contract action).
  - Days 31 to 60: flow, identity and tracking repairs; replacement tests.
  - Days 61 to 90: tested cutovers, retirements (access, tokens, DNS, notice), the new-tool intake rule, re-tests of closed FAILs.
  - Later: a quarterly roadmap, each project with a gate.
- Savings: cash through a date, run-rate after commitments end and released capacity, reported apart.
- Give the user the FAIL list to review with each owner; send no messages. Write [assets/audit-report-template.md](assets/audit-report-template.md) and [assets/plan-30-60-90-template.md](assets/plan-30-60-90-template.md). Propose updates to `stack.md`, `decisions.md` and `metrics.md`; write nothing until the user approves.

## Common failure modes

- Inventory from SSO alone misses card purchases and free apps with data access. Detect: INV01, INV02.
- The renewal fires mid-audit. Detect: calendar first; CON04 and "Act now".
- A warehouse sync and a CRM workflow both set lifecycle stage. Detect: FLW01; property history alternating sources.
- A green connector taken as proof. Detect: the four-case test with a read-back.
- A person who left keeps tool accounts outside SSO. Detect: ACC05.

More, with detection steps: [references/failure-modes.md](references/failure-modes.md).

## Worked example

Acme Cloud, a fictional 40-person B2B SaaS, audits on 2026-11-01 with `assets/example-acme-*.csv`; the scripts end with 16 FAIL, 4 FAIL and 1 FAIL. Commands and output: [references/worked-example.md](references/worked-example.md). Filled report: [assets/example-acme-report.md](assets/example-acme-report.md). Worth copying:

- HubSpot's right-size needs a tested alternative 75 days out, so its review-by is the audit date.
- Unsubscribes reach the CRM a day late against a 15-minute requirement: class 1, days 0 to 30.
- Only the identity provider status shows a leaver's enabled HubSpot and Metabase accounts.

## Acceptance checks

- Scope: questions, owners, workflows. Pass: no ownerless question; at most three workflows.
- Register: every tool any channel saw has a row, owners or "unknown", channels and spend per currency. Pass: no unregistered recurring payment without a finding; hand-done sections say "script not run".
- Contracts: each annual top-80% tool has every contract field with an order reference, or a CON01 FAIL. Pass: past review-by dates all have findings.
- Usage: each dormancy and reclaim figure names its source and retention. Pass: none from a source shorter than the threshold.
- Decisions: consolidate, replace and retire carry all five fields. Pass: none missing.
- Flows: four-case results or "not tested" with a reason per traced connection. Pass: no "observed" without dated evidence.
- Automation: point-of-action results or a hand-off record per traced workflow. Pass: none missing.
- Data: key_match ran on one key per workflow. Pass: rates only from 200 or more records.
- Tracking: a ratio and sender list, or a dated hand-off record, per workflow. Pass: no workflow has neither.
- Access and AI: admin counts, AI connections, readiness ratings. Pass: a count or a NOT CHECKED line naming the export per critical tool; identity, scopes and enforcing layer per AI connection; evidence per rating.
- Plan: every FAIL is planned or accepted by its owner with a date. Pass: each item has owner, acceptance test and window.
- Safety: credentials, customer records and employee emails. Pass: the call log shows reads only, apart from owner-approved test actions listed with approval and test record IDs; searching the report for "@" finds no email address.

## Boundaries

- Read-only. Never cancels, downgrades, removes users, revokes tokens, changes settings, sends notices or messages, or uploads an edited app list to an admin console. Changes leave as drafts: change lists, notice drafts, revocation lists.
- No live side effects without approval: no test record, unsubscribe, message, form submission, order or consent-banner click on a live system unless the owner approves that test and its test data. List each one in the report.
- Contract counting and valid notice belong to the contract owner; consent and legal basis to the privacy owner. An unknown app with mailbox access goes to IT security the same day.
- The audit reports roles and counts, not people's performance.
- Read-only hand-offs, each with its enforcing layer in [references/handoffs.md](references/handoffs.md):
  - Spend: Ramp's hosted MCP (Read-only at consent), Zylo's (read-only key), QuickBooks (write, update and delete tools disabled).
  - CRM: HubSpot's remote MCP has no read-only switch: a least-privilege user, read tools only in the client. Salesforce's `sobject-reads` server.
  - Analytics and tags: GA4 MCP without `--allow-write`; the Google Ads MCP with `labels` and `assets` off; Stape's GTM MCP with `tagmanager.readonly`.
  - Warehouse: Snowflake's managed MCP (SELECT-only default role); BigQuery's `execute_sql_readonly` with `execute_sql` and `cancel_job` denied; Fivetran's MCP with `FIVETRAN_SCOPE=read`.
  - Approved writes: agent-write-safety.

## Live docs

Platform settings, limits and menu paths change. Before stating any parameter name, limit, quota or UI path:

- Fetch the current page from the vendor docs MCP server or llms.txt listed in `sources.yaml`, or open the canonical URL.
- If the live page disagrees with this skill, follow the live page and tell the user what changed.
- Cite the URL you used.

Most used here: Okta, Entra, Google's app and scope pages, HubSpot, Snowflake, the GA4 and Google Ads MCP pages, CIS safeguards 5.3, 6.2 and 6.5, OWASP LLM06 and vendor renewal terms. The executed order beats any default.
