Copilot Credit Chargeback Gate Check
Is the Microsoft 365 Copilot metered line even worth allocating? One division, run across several closes, then a per-agent triage into charge back, showback and shared pool.
Standardized doc shell (hero + metadata + quick path). Custom content below remains page-specific.
Download Tool FilesStandard run path
Structured quick-reference sections for prerequisites, installation, usage, and troubleshooting.
Prerequisites
There are no cloud permissions to grant, because the script never talks to a cloud. Whatever access you needed to download your own invoice and usage export is the whole requirement.
- Python 3.8 or newer. No pip install, no SDK, no credentials, no network access
- Your last three Copilot invoices, split into the per-seat line and the metered credit line
- Optional, for the per-line verdicts: an agent-level credit consumption export from the admin center
- Optional, and only for the shared pool: a seat count per cost center
The three input files
Column names are matched case-insensitively and nothing is inferred. A missing required column stops the run and prints both what it found and what it expected.
month,seat_cost,metered_cost
2026-05,12000,600
2026-06,12000,1150
2026-07,12000,2200
2026-08,12000,3800agent,cost_center,credits,cost,shared
Claims intake-triage agent,Claims,240000,2400,false
Contract summariser,Legal,40000,400,false
Contract summariser,Underwriting,25000,250,false
IT helpdesk bot,IT,35000,350,true
Meeting summariser,,40000,400,false| File | Columns | Required | What it drives |
|---|---|---|---|
| --invoice | month, seat_cost, metered_cost | Yes | The gate and the trajectory. month is YYYY-MM, one row per close. Three or more rows make the trend meaningful |
| --usage | agent, cost_center, credits, cost, shared | No | The per-agent triage. One row per agent per consuming cost center, so an agent spread across three departments appears three times |
| --seats | cost_center, seats | No | The shared-pool reallocation, and nothing else |
If your usage export has no cost column, pass --credit-rate to derive cost from credits. Without it the run stops rather than guessing a rate.
Standard run path
- 1Run the gate alone first, on three or more invoices. If the latest close sits below your threshold and the line is not climbing, stop here. That is the answer, and it took one division.
- 2Read the trajectory before the ratio. A single close gives you a number; the series gives you a direction, and the direction decides whether you re-run this monthly or quarterly.
- 3Add --usage once the gate is cleared. The triage is where chargeback is actually decided, line by line, and it is a different question from the gate.
- 4Check the reconciliation line. If the usage export does not tie to the invoice metered line, fix that before anybody sees a statement.
- 5Add --seats to split the shared pool. Without it the pool is reported and left unsplit, deliberately.
- 6Paste the close record into your log with its date, including the months where nothing changed.
python copilot_credit_gate_check.py --invoice invoices.csvpython copilot_credit_gate_check.py --invoice invoices.csv --usage agents.csvpython copilot_credit_gate_check.py --invoice invoices.csv --usage agents.csv \
--seats seats.csv --threshold 5 --attribution-threshold 90# Your finance partner already has a materiality threshold, so use theirs
python copilot_credit_gate_check.py --invoice invoices.csv --threshold 2.5python copilot_credit_gate_check.py --invoice invoices.csv --usage agents.csv \
--output-csv triage.csv --output-json gate.jsonReading the verdicts
Three verdicts per agent, and the reason printed alongside each one
The verdict is what you paste into the statement discussion. The attribution percentage is the evidence behind it.
| Verdict | When it fires | What to do with it |
|---|---|---|
| CHARGE BACK | At or above the attribution bar, default 90%, on a single cost center | This is your chargeback line. Often there is exactly one, and one unarguable line is worth more than five contested ones |
| SHARED POOL | The agent is marked shared in the export | Carve it out and reallocate by seat share. The helpdesk bot, the HR policy bot and the meeting summariser usually live here |
| showback | Attribution below the bar, or no cost center on the export | Name it on the statement, charge nobody, and re-check coverage next close |
Cost that lands on no cost center is reported as zero attribution rather than as a clean 100% of the blank bucket. That distinction is the difference between an honest export and a flattering one.
- Coverage crossing your bar is what promotes a showback line to a chargeback line, and it usually crosses without anybody announcing it. That is the reason to re-run this at every close.
- The run reconciles the triage total against the invoice metered line and prints the gap in dollars when they disagree.
What this tool does not tell you
- It does not build your allocation model. It tells you whether one is worth building and which lines could go in it.
- It does not produce statements and it cannot reconcile a disputed one.
- It claims no savings. Chargeback moves cost onto the budget that caused it; it does not remove any.
- It does not price Copilot Credits. You supply the money, either as a cost column or as a --credit-rate you have confirmed against your own invoice.
- It does not know your finance function. The 5% default is a working assumption, and the threshold that survives audit is usually the one your finance partner already uses.
Clearing the gate is not permission
The most expensive misreading of this output is treating a cleared gate as a green light for full chargeback. The gate says the metered line is big enough to be worth allocating. It says nothing about whether every dollar inside it can be allocated defensibly, and that second question is answered line by line in the triage. Charge a business unit $2,400 it recognises plus $340 it does not, and the whole conversation becomes the $340.
Seats, not credits, for the shared pool
A shared agent costs what it costs because a population is entitled to call it, not because one team runs it hardest. Reallocate that pool by consumption and your heavy users pay twice: once for their own attributed credits, and again for a larger slice of a helpdesk bot they use no more than anyone else. That is the double-charge your best-instrumented team will find, and they will be right.
Troubleshooting
- A missing-column error prints the columns it found next to the ones it expected. Rename your export headers to match; nothing is inferred from position.
- month must be YYYY-MM. "Aug 2026" stops the run rather than being parsed loosely, because a misread month silently reorders your trend.
- One invoice row prints "no trend is computable" instead of a growth rate. That is the correct output for one data point, not a failure.
- A shared pool with no --seats file is reported and left unsplit. Supply the seat counts, because a guessed denominator here is a double-charge.
- Every failure exits with code 2 and a single ERROR line on stderr, so it is safe to run from a scheduled job.
Why this exists
The June 2026 Copilot billing change was additive. Cowork jobs, Work IQ grounding for custom agents and Copilot Studio agent runs bill in Copilot Credits on top of the flat per-seat licence, and the per-seat price did not move. One invoice now carries two lines that split by two different logics, and that is what breaks per-seat allocation. Headcount still splits the seats perfectly. It cannot split a credit pool, because credits are not consumed in proportion to who holds a licence.
Most teams then make one of two mistakes. They build nothing, and the metered line grows unwatched inside an IT cost pool. Or they clear a materiality threshold, read it as permission, and charge every line in the pool. The second failure is the expensive one: it survives exactly one month, until a business unit reads a statement and asks why it is carrying credits for an agent it has never invoked.
This script does the arithmetic for both decisions and keeps them separate. The gate is one division run across closes. The triage is per agent, on attribution coverage. Neither of them is a model, and the script stops where the judgement starts.
What the run costs
Nothing. There is no API to call and therefore no request to be billed for. The script opens the CSV files you point it at and writes only the output files you name with --output-csv or --output-json. It imports nothing outside the Python standard library, which is verifiable in the source in about thirty seconds.
A CHARGE BACK verdict is a measurement of attribution coverage, not permission to bill a department. Clearing the gate licenses building a model; charging a specific line is a decision, and it belongs on a signed page with a name and a date on it.
How it was tested
Verified on 6 August 2026, and the specifics matter more than the word verified. A 92-check offline suite drove the real code paths and all 92 passed: the gate arithmetic, the trajectory, the triage at three different attribution bars, the shared-pool reallocation, credit-rate derivation, twelve error exits, both output files, and three structural checks on the source itself. The worked example from the companion post reproduces exactly, including the reconciliation to the cent.
The refusals were tested as carefully as the results, because a tool that invents a number is worse than one that declines. One month of data prints that no trend is computable rather than a rate. A series starting at zero declines to compute a compound rate. A flat or falling line projects no gate crossing. A usage file with no cost column exits instead of guessing. A shared pool with no seats file is left unsplit. An AST walk over the source confirms that only two write paths exist and both are the output files you asked for, that no network or cloud SDK is imported, and that nothing outside the standard library is used.
Not verified: no real Microsoft 365 admin-center export was parsed, because the column names in your export are yours to map onto the four this script expects. The gate itself is one division, so check the first run by hand. The full disclosure and the checksum are recorded in the tool's trust metadata.
Read the post this came from
The script runs the arithmetic for your estate. The post explains why the partial model is the stronger position, why showback is instrumentation rather than a holding pattern, and why the shared pool gets reallocated by seats no matter which allocation method you land on.
Clearing the Gate Is Not PermissionThe 5% materiality floor is a working assumption, not a FinOps standard. If your finance partner already has a threshold, pass it with --threshold and cite theirs in the policy. Borrowed thresholds survive audit better than invented ones.
What to do next
Pick the path that fits where you are right now.