Attribute Spend to Teams & Departments
Once AI spend is being metered per developer, the next question is always the same one: which team is that spend coming from? Finance wants a chargeback line per department. Engineering leadership wants to know whether the Payments team's usage justifies its seat count. Neither question is answerable from a flat list of individual developers.
Org units are Revenium's answer. You describe your reporting structure once, map each person to a unit, and every cost report can then be grouped by department. The structure lives in Revenium, not in your telemetry, which means you can build it after the data is already flowing, without re-sending or reprocessing a single event.
Availability. Org-unit attribution is enabled per tenant rather than being on by default. If the endpoints below return 403 Feature not available, it is not yet switched on for your account — contact Revenium to have it enabled. Per-team rollups are currently available through the API; a dimension selector in the UI is still to come. See Current Limits below.
What an Org Unit Is
An org unit is one node in a tree that mirrors how your organization actually reports. Engineering, Engineering/Platform, and Engineering/Platform/Payments are three units, each a child of the one before it.
Each person is assigned to exactly one unit as their primary membership, and that assignment is dated. Because the whole tree carries a path, a rollup at Engineering includes every descendant beneath it, so you can look at one department or drill into a single squad without maintaining two different structures.
Two ideas do the work here, and both matter for the sections below:
A person is not a user account. Org-unit people are directory records. Creating one does not create a Revenium login, does not send an invitation, and does not require the person to have sent any telemetry yet. You can load your entire roster on day one, before a single developer has been onboarded.
Attribution is resolved when you run the report, not when the event arrives. Nothing about a department is stamped onto a usage event. The department is worked out at query time by looking up who the person was reporting to at the moment the event happened. This is what makes the two behaviours described in Retroactive Attribution and When Someone Changes Teams below possible.
The CSV Contract
Bulk import takes a CSV with this header:
email,org_unit_pathAn optional third column, is_primary, is accepted. Columns are read by name, so their order in the file does not matter.
email
Yes
The developer's email address, matching the identity their tooling reports.
org_unit_path
Yes
Slash-separated unit names from the root, for example Engineering/Platform/Payments.
is_primary
No
Only primary memberships are supported. Omit it, or set it to true.
A realistic file:
You do not create the tree first. Every unit along each path is created if it does not already exist, so the eight rows above build the whole structure on their own, including the intermediate Engineering/Platform node that no row names directly. Likewise, a person who is not yet known to Revenium is created and linked automatically.
Repeat imports are safe. A row whose person is already in the unit it names is reported as unchanged rather than written again, so re-uploading a slightly edited roster does not duplicate anything.
File limits. Uploads are capped at 5 MB, and there is a maximum row count. A file that exceeds either is rejected with a clear error rather than partially applied. For a roster of a few thousand developers, neither limit is a practical concern.
Importing: Dry Run First, Then For Real
The import endpoint accepts multipart/form-data with a file part named file.
Start with a dry run:
A dry run is not a separate validation routine that might drift from the real thing. It runs the genuine import — the same unit lookups, the same person resolution, the same membership planning — inside a transaction that is rolled back at the end. It is therefore both a true rehearsal and guaranteed to write nothing.
Both dry and real runs return the same report:
Read errors before doing anything else. Each entry carries the 1-based line number in your file (the header is line 1) and a human-readable reason, so a malformed roster tells you exactly which rows to fix.
When the report looks right, run the same command with dryRun=false.
To move a single person later, without preparing a file, you need the numeric id of the destination unit. List the tree to find it:
Each entry carries its id, name, parentId and full path, so you can match on the path you used in the CSV and read the id from the same record. Then assign:
Listing requires an authenticated key; importing and assigning require an API key with permission to manage the organization.
Retroactive Attribution: The First Import Covers All History
This is the most useful property of the design, and it is worth planning your rollout around.
The first time a person is imported, their membership is backdated to the beginning of time rather than starting on the day you uploaded the file. Every event they have ever sent therefore attributes to the unit you just assigned them.
In practice this means you can run the rollout in the order that is easiest for you:
Turn on metering and let developers work. Spend accumulates, attributed to individuals.
Weeks later, when you have your roster sorted out, upload the CSV.
Every historical report is now broken down by department, including the weeks before the file existed.
There is no backfill job, no reprocessing window, and no need to re-send telemetry. Because departments are resolved when the report runs, the data was never missing — only the structure to interpret it was.
Backdating applies only to a person's first assignment. Once someone has a mapping, changing it takes effect from the moment you change it and leaves their earlier history where it was, exactly as a transfer does. A developer imported into the wrong unit and corrected a week later therefore ends up with that week attributed to the wrong department and everything after it attributed correctly, rather than the whole history moving.
That is worth knowing before the first import rather than after it. Run a dry run, read the report, and confirm the tree is right while backdating is still available to you.
The one prerequisite. Retroactive attribution works on the identity the events already carry, which is the developer's email address. Events that arrived without an email cannot be attributed later, because there is nothing to match the roster against. See Identity must be an email address.
When Someone Changes Teams
When you reassign a person who already has a mapping, their historical spend stays with their old team. Only usage from the moment of the change forward counts against the new one.
This is deliberate, and it is usually the opposite of what people expect, so it is worth being explicit:
A developer who spends six months on Payments and then moves to Identity leaves six months of spend on the Payments line. Identity's line starts at zero on the day of the move.
That is the correct behaviour for cost accounting. A department's reported spend for a past quarter should not change because somebody transferred afterwards; if it did, no closed period would ever stay closed and chargebacks already issued would stop reconciling.
The boundary is exact. An event landing precisely at the moment of a transfer counts against the new unit.
Note the distinction from the previous section, since the two can look contradictory. A first assignment is backdated, because before it the person had no department at all and there is no history to protect. A subsequent change is not backdated, because there is history, and it belongs where it was earned.
Current Limits
These are real constraints today, not caveats about edge cases.
The CSV is a point-in-time snapshot
Nothing keeps the tree in step with your directory. Joiners, leavers and transfers are reflected only when somebody uploads a new file. For an organization with normal churn this is a recurring task, not a one-off. Until automated sync ships (see below), plan for a monthly or quarterly re-upload, and treat it as an operational job with an owner.
Ambiguous emails are never guessed
If more than one active directory person claims the same email address, the import does not choose between them. The row is reported as ambiguous and left for an administrator to resolve. This is intentional: silently picking one would attribute somebody's spend to the wrong person, and that error is invisible once it is made.
Only primary memberships
Each person belongs to exactly one unit. Matrix organizations, dotted-line reporting, and splitting one developer's cost across two departments are not supported. A row that explicitly asks for a non-primary membership is reported as an error rather than being quietly ignored.
Identity must be an email address
Attribution matches on email at every stage. A fleet that reports only an opaque identifier and no email address cannot be mapped to a team, no matter how complete the roster is. Those events are still metered, still costed, and still attributed to your organization — they simply fall into the Unassigned bucket for department rollups.
Unassigned is a real bucket, not a dropped row. Totals grouped by department always reconcile against the ungrouped total, so a gap in your roster shows up as a visible Unassigned figure rather than as quietly missing money.
If you are configuring Claude Code, the stock configuration already reports developer email correctly, and no extra attribution setup is needed for org units to work. See Setup Claude Code.
Rollups are API-only for now
The department dimension is served by the reporting API, but the UI does not yet offer it as a grouping option. Until it does, per-team rollups are retrieved programmatically. Everything else described on this page — building the tree, importing rosters, reassigning people — works today.
Producing the CSV From Your Directory
Your IT team can generate the file directly from your existing directory. Both recipes below emit the exact two-column format the importer expects. Treat them as starting points: the attribute you use for the department path depends on how your directory is organized, and you will likely want to filter to the population that actually uses AI tooling.
Microsoft Entra ID (Azure AD)
Using the Microsoft Graph PowerShell SDK:
Department is a single value, which produces a flat one-level tree. To build a hierarchy, compose the path from whichever attributes carry your structure, for example a company or division and a department. Swap the org_unit_path expression above for:
Any attribute you reference in an expression must also appear in -Property, which is why CompanyName is requested above even though the flat version does not use it. Graph returns nothing for an unrequested attribute, and an empty leading segment would quietly collapse the hierarchy back to a flat tree rather than failing.
On-premises Active Directory
Using the ActiveDirectory module on a domain-joined machine:
If your reporting structure is expressed through the directory's own organizational-unit hierarchy rather than a department attribute, derive the path from each user's distinguished name instead, reversing the OU components into a root-first path.
Windows PowerShell 5.1 and the byte-order mark. -Encoding utf8NoBOM requires PowerShell 6 or later. In Windows PowerShell 5.1, -Encoding UTF8 writes a byte-order mark at the start of the file, which becomes part of the first column name. Because the importer matches columns by name, a file whose header reads as <BOM>email will not be recognised. Either run the export in PowerShell 7, or strip the mark afterwards:
Check the file before uploading it. Confirm the header is exactly email,org_unit_path, that no value contains a stray comma, and that the file carries no byte-order mark. Then run the import with dryRun=true and read the error list. Between them, those two steps catch essentially every formatting problem before anything is written.
Automated Directory Sync
Automated directory synchronization is not available yet. CSV import and the assignment endpoints are the supported ways to populate and maintain the tree today. If you have been told otherwise, that is the correct position as of this page's publication.
When it ships it will work as a scheduled pull from Revenium to your directory, on a daily incremental and weekly full reconciliation cycle, rather than as a push from you to us. Connecting it will require:
Microsoft Entra ID — an application registration in your tenant with directory read permission over users and group memberships, administrator consent, and a client credential shared with Revenium.
On-premises Active Directory — synchronization into Entra ID first, since a cloud service cannot read an on-premises directory directly. Organizations already running Entra Connect are covered by the row above.
Other providers — Okta, Google Workspace and HR-system sources are planned behind the same mechanism, each with its own credential model.
The shape of the integration therefore depends on how your directory is run, which is worth establishing early if automated sync matters to your rollout. In the meantime a scheduled export from the recipes above, uploaded on a regular cadence, achieves the same result with an owner attached.
Rollout Checklist
Confirm org-unit attribution is enabled for your tenant. The endpoints return
403if it is not.Confirm your AI tooling reports developer email. Everything else depends on it.
Export a roster from your directory using one of the recipes above.
Import with
dryRun=trueand resolve every entry inerrors.Import for real, then check
unitsCreatedmatches the structure you intended.Query cost reports grouped by department. Historical periods are included automatically.
Assign an owner and a cadence for re-uploading the roster as your organization changes.
Last updated
Was this helpful?