---
title: "Upload patient-free practice-metric aggregates for a customer"
method: POST
path: "/accountant/companies/{customerId}/practice-metrics"
tags: ["Accountant Portal"]
---

# Upload patient-free practice-metric aggregates for a customer

`POST /accountant/companies/{customerId}/practice-metrics`

## Request

- Base URL: `https://api.medianfi.com/api/v1`
- URL: `https://api.medianfi.com/api/v1/accountant/companies/{customerId}/practice-metrics`
- Auth: HTTP bearer

Upserts the practice-metrics aggregate feed for one customer: per-member revenue/cost/hours, per-channel spend and customer counts, and owner-pay scalars the Median GL cannot compute itself. Idempotent on (metricKey, period, dimensionKey): re-uploading the same payload rewrites the same rows. Optional `retireDimensionKeys` deletes only those explicit superseded labels before the upsert so a clinician-name re-upload cannot double-count `member_a` alongside `Alex Rivera`. Dimension keys may contain staff display names or channel names, but must not contain patient identifiers; values shaped like emails or phone numbers are rejected (422). Requests are capped at 2000 points; `cents` series values must be integers. Optionally upserts the practice config in the same request. Deliberately NOT gated by the customer's additionalMetrics list, so the feed loads before `practice_metrics` is listed. This is the currently configured accountant-upload path; it is not a scheduled connector.

## Request body

- UploadPracticeMetricsRequest: Capped at 2000 points per request. `cents` series values must be integers; duplicate (period, dimensionKey) points within a series are rejected. Dimension keys may be staff display names or channel names but must not contain patient identifiers; email-shaped and phone-number-shaped values are rejected. `retireDimensionKeys` deletes only those listed labels for the customer before the upsert; a key listed there and also present in `series` is rejected (422).
  - `series` object[], required
    - `metricKey` 'revenue_by_member' | 'direct_cost_by_member' | 'hours_booked_by_member' | 'hours_delivered_by_member' | 'spend_by_channel' | 'customers_by_channel' | 'overhead_pool' | 'owner_pay' | 'owner_draws' | 'owner_billings' | 'net_income_before_owner_pay', required: Metric vocabulary of the practice-metrics feed. `*_by_member` and `*_by_channel` series are dimensioned by `dimensionKey`; the rest are per-period scalars (dimensionKey '').
    - `unit` 'cents' | 'percent' | 'hours' | 'count', required
    - `currency` string: ISO 4217 code of a `cents` series' values; must be a supported two-decimal currency (422 otherwise, or on a non-`cents` series). Optional; Home holds profit by team member until member revenue, direct cost and overhead pool all declare the books' currency. Re-uploading a series without it clears the stored declaration.
    - `points` object[], required
      - `period` string, required
      - `dimensionKey` string: De-identified token; omit for scalar metrics.
      - `value` number, required
      - `computedAt` string, date-time
  - `practiceConfig` PracticeConfigInput: Partial practice config — omitted fields keep their stored values.
    - `overheadAllocationBasis` 'revenue' | 'hours' | 'headcount'
    - `ownerCompensationCents` integer, nullable
    - `ownerDrawSplitPct` number, nullable
  - `retireDimensionKeys` string[]: Explicit superseded dimension labels to delete before this upsert (for example `member_a` after a clinician-name re-upload). Empty string (scalar) keys are rejected.

## Response `200`

Upload accepted; reports rows written and the effective config

- UploadPracticeMetricsResponse
  - `success` true, required
  - `data` object, required
    - `upsertedPoints` integer, required: Rows written by this upload (inserts + updates).
    - `retiredPoints` integer, required: Rows deleted because their dimension key was listed in retireDimensionKeys.
    - `config` PracticeConfig, required
      - `overheadAllocationBasis` 'revenue' | 'hours' | 'headcount', required: How the overhead pool is allocated across members.
      - `ownerCompensationCents` integer, nullable, required: S-corp reasonable-comp reference, integer cents; null until set.
      - `ownerDrawSplitPct` number, nullable, 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)
