---
title: "Entity-scoped CAC detail for an analysis period"
method: GET
path: "/companies/{companyId}/metrics/cac"
tags: ["Metrics"]
---

# Entity-scoped CAC detail for an analysis period

`GET /companies/{companyId}/metrics/cac`

## Request

- Base URL: `https://api.medianfi.com/api/v1`
- URL: `https://api.medianfi.com/api/v1/companies/{companyId}/metrics/cac`
- Auth: HTTP bearer

Customer acquisition cost for one concrete legal entity and an explicit analysis period (default: year to date). The numerator is explicit CAC-tagged sales & marketing spend (GL classification is reconciliation evidence, never a tag); the denominator is canonical new paying customers, available only with continuous subscription history from before the period start. When `available` is false, `reason` names why and no numeric zero is fabricated. The top-level `available` always equals `metric.available`: a snapshot can compute and still have no ratio (`insufficient_inputs`), in which case `period` and `breakdown` are still returned as evidence while `metric.value` is null.

## Query parameters

- `entityId` string, uuid
- `period` 'ytd' | 'month'
- `startDate` string, date
- `endDate` string, date

## Response `200`

Availability-discriminated CAC detail payload

- object
  - `success` boolean
  - `data` object
    - `enabled` boolean, required: Rollout stamp for saas_metrics_engines. Informational ONLY: metric bodies are identical at any flag state (F07). It does not gate the correction write path.
    - `entityId` string, uuid, required
    - `available` boolean, required
    - `reason` 'insufficient_history' | 'source_not_authorized' | 'mixed_currency' | 'entity_scope_unavailable' | 'insufficient_inputs' | 'truncated' | 'failed', required: Customer-safe reason for an unavailable CAC. `truncated` (a capped scan) and `failed` (a thrown read, added by the /metrics/saas assembler) are the FAILURE class the UI renders as an explicit unavailable state (master-index pin P4); every other value is honest absence.
    - `metric` CacMetric, required: The CAC headline. Served under the `cac` key of GET /metrics/saas and as `metric` on GET /metrics/cac. `value` is the LEGACY major-unit float kept for old clients; new consumers read `cacMoney`, the shared ExactMoney shape (master-index pin P2). The money authority is the ENTITY's functional currency, which can differ from the envelope's subscription billing currency — render from `currency`/`minorUnitExponent` here, never from the envelope's. An unavailable metric carries `reason` and never a numeric zero.
      - `available` boolean, required
      - `value` number, nullable, required: Legacy major units (cacCents / 100). Prefer `cacMoney`.
      - `smSpendCents` integer, nullable, required
      - `newCustomers` integer, nullable, required
      - `currency` string, nullable
      - `minorUnitExponent` integer, nullable
      - `cacMoney` ExactMoney: Exact money value: signed integer minor units as a string (never a float), with explicit currency and minor-unit exponent.
        - `amountMinor` string, required: Signed integer minor units, e.g. "80000" = $800.00 at exponent 2.
        - `currency` string, required
        - `minorUnitExponent` integer, required
      - `smSpendMoney` ExactMoney: Exact money value: signed integer minor units as a string (never a float), with explicit currency and minor-unit exponent.
        - `amountMinor` string, required: Signed integer minor units, e.g. "80000" = $800.00 at exponent 2.
        - `currency` string, required
        - `minorUnitExponent` integer, required
      - `reason` 'insufficient_history' | 'source_not_authorized' | 'mixed_currency' | 'entity_scope_unavailable' | 'insufficient_inputs' | 'truncated' | 'failed': Customer-safe reason for an unavailable CAC. `truncated` (a capped scan) and `failed` (a thrown read, added by the /metrics/saas assembler) are the FAILURE class the UI renders as an explicit unavailable state (master-index pin P4); every other value is honest absence.
    - `period` CacAnalysisPeriod: The resolved analysis window. `period=month` is CLAMPED to today, so a partial month is never keyed (or served) under the complete-month end.
      - `start` string, date, required
      - `end` string, date, required
      - `label` string, required
    - `breakdown` CacBreakdown: Evidence behind the headline, all in the entity's functional currency. `glSmSpendCents` is the naive GL 64xx bucket — EVIDENCE for reconciling against the explicit tags, never itself a tag — and carries the same currency authority and refund sign convention as `smSpendCents`, so `reconciliationDeltaCents` is a comparable figure.
      - `smSpendCents` integer, required
      - `glSmSpendCents` integer, required
      - `reconciliationDeltaCents` integer
      - `taggedTxnCount` integer
      - `newCustomers` integer, required
      - `cacCents` integer, nullable
      - `currency` string, required
      - `minorUnitExponent` integer, required
      - `sourceGeneration` string, nullable
      - `evidenceSha256` string, nullable
      - `source` string
      - `computedAt` string, date-time
    - `trend` CacTrendRow[], required: Up to 12 persisted snapshots for this entity, newest first. DENSITY IS A ROLLOUT ARTIFACT, not a claim: the complete-month rows come from the CAC backfill worker, which only visits companies the saas_metrics_engines candidate list names, so a company outside it carries only the windows that were actually requested.
      - `period` string, required
      - `cacCents` integer, nullable, required
      - `smSpendCents` integer, required
      - `newCustomers` integer, required

## Other responses

- `401`: Authentication required
- `403`: Insufficient permissions or not a company member
- `422`: Request failed validation

---

[API](https://skmtc.dev/medianfi/apis/median-api.md) · [All operations](https://skmtc.dev/medianfi/apis/median-api/llms.txt) · [OpenAPI document](https://skmtc.dev/medianfi/apis/median-api/revisions/20090ea32b0d?raw)
