Skip to the content.

Pulling Copilot Metrics & Billing Into Your Data Lake

Last updated: October 20, 2026


What it takes

GitHub serves the aggregate and user usage reports for up to a year, so if you want a longer adoption history (or billing data for chargeback) you have to pull it yourself and keep your own copy. The whole job:

  1. Set up one Enterprise GitHub App with read access to both Copilot metrics and enterprise billing.
  2. Pull four usage report families plus one billing export each day against the prior complete day. This preserves aggregate, user, team, repository, and cost data in roughly a dozen data requests.
  3. Drop the files into your data lake so the history outlives GitHub’s retention.

The example scripts do exactly this. The rest of this page explains the model and walks the setup so you can adapt it.

Note

This applies to GitHub Enterprise Cloud (including EMU). The endpoints are enterprise-scoped against api.github.com. GitHub App access to enterprise billing became available on August 26, 2026. Older implementations used a billing-manager PAT because Apps could not call these endpoints. See GitHub Apps can now access enterprise billing data.


Two domains, don’t confuse them

  Usage metrics Billing metrics
What it is Engagement/adoption — active users, completions, chat Consumption/cost — AI Credits, quantities, dollar amounts
Endpoint family /enterprises/{ent}/copilot/metrics/reports/... /enterprises/{ent}/settings/billing/reports
Dollar amounts ❌ none ✅ yes
Auth Enterprise GitHub App: Enterprise Copilot metrics (read) Same App: Enterprise billing (read)

Usage metrics tell you how Copilot is being used. The aggregate report has no user identity; the separate user report includes user_id and user_login. Billing tells you what it costs. Collect both API families separately, then join usage user_login to billing username for the same date. One App installation token can authorize every call when the App has both permissions.


One App, two permissions

The App needs one read permission for each API family:

App permission Used for
Enterprise Copilot metrics: Read-only Adoption and engagement reports
Enterprise billing: Read-only Usage summaries and billing report exports

The billing export starts with a POST, but it only requests creation of a read-only report. GitHub’s App permission matrix classifies the create, poll, and list report endpoints as read. Do not grant write access to a collection App; write is for changing budgets and cost centers.

The scripts still accept a classic PAT for compatibility, but the App is the recommended unattended credential: installation tokens expire after one hour, aren’t tied to an employee, and receive the higher App rate limit.


Set up the Enterprise GitHub App

You need enterprise owner access to create and install the App and enable the usage-metrics policy, plus openssl, curl, and jq locally.

  1. Enable the policy. The metrics endpoints only return data when Copilot usage metrics is Enabled everywhere (Settings → Policies → Copilot). See Manage enterprise policies for Copilot.

  2. Register the App at https://github.com/enterprises/<your-enterprise>/settings/apps/new (registering a GitHub App). The choices that matter for this example:
    • Enterprise permissions → View Enterprise Copilot Metrics: Read-only.
    • Enterprise permissions → Enterprise billing: Read-only.
    • Organization permissions → Organization Copilot metrics: Read-only (optional) if you’ll pull org-level reports with --org.
    • Webhook → Active: unchecked — no events needed.
    • Only on this account.

    Note the App ID shown after you create it.

  3. Generate a private key (App settings → Private keys), then lock it down:

    mv ~/Downloads/*.pem ./app.pem && chmod 600 ./app.pem
    
  4. Install the App on your enterprise and note the installation ID from the URL: .../settings/installations/<INSTALLATION_ID>.

The scripts mint the installation token themselves from the App ID, installation ID, and key.

Migrating an existing App

If you followed an older version of this guide, add Enterprise billing: Read-only to the existing App. Then review the enterprise installation and approve the updated permission if GitHub prompts you. For enterprise-owned Apps, some permission updates are accepted automatically; confirm the installation shows billing read access before removing GH_BILLING_TOKEN.

The installation continues using its old permissions until the update is accepted. That is the first thing to check if the App can read Copilot metrics but billing returns an authorization error. See Approving updated permissions for a GitHub App.


Verify both permissions

Before automating, confirm both App permissions work by pulling the last 28 days to files you can read. Download only the script you’re testing — no clone required.

Usage metrics (GitHub App):

export ENTERPRISE=<your-enterprise> APP_ID=<id> INSTALLATION_ID=<id> PRIVATE_KEY=./app.pem
base=https://raw.githubusercontent.com/samqbush/copilot-adoption/main/copilot-metrics-billing/scripts
curl -fsSLO "$base/copilot-usage-metrics.sh" && chmod +x copilot-usage-metrics.sh

day=$(date -u -v-1d +%F 2>/dev/null || date -u -d '1 day ago' +%F)
for report in aggregate users user-teams repos; do
  ./copilot-usage-metrics.sh "$ENTERPRISE" --day "$day" --report-type "$report" \
    --app-id "$APP_ID" --installation-id "$INSTALLATION_ID" --private-key "$PRIVATE_KEY" \
    > "usage-$report-$day.json"
done
jq '.report | length' usage-*-"$day".json

Note

Resource not accessible by integration means the App is missing the View Enterprise Copilot Metrics permission, or the usage-metrics policy isn’t enabled yet. Fix it, then re-accept the updated permissions on the installation.

Billing (same GitHub App):

export ENTERPRISE=<your-enterprise> APP_ID=<id> INSTALLATION_ID=<id> PRIVATE_KEY=./app.pem
base=https://raw.githubusercontent.com/samqbush/copilot-adoption/main/copilot-metrics-billing/scripts
curl -fsSLO "$base/copilot-billing-export.sh" && chmod +x copilot-billing-export.sh

./copilot-billing-export.sh "$ENTERPRISE" --last-28-days \
  --app-id "$APP_ID" --installation-id "$INSTALLATION_ID" --private-key "$PRIVATE_KEY" \
  --out billing-last-28-days.csv
head billing-last-28-days.csv             # or open it in a spreadsheet

Note

A 403 or 404 on the /reports endpoints usually means the App is missing Enterprise billing: Read-only, or the updated installation has not been accepted. Fix the permission and confirm the installation, then try again.

The usage files are not interchangeable: each report family covers a different slice of the same day. Billing is per-day detail and can lag usage slightly.


Minimizing API calls

These endpoints package each dataset for you. Use the report endpoints, not one API request per user or repository:

A full daily collection is typically 11–13 data requests, plus the App token exchange. Run it once a day against the prior complete UTC day and it remains well below the App rate limit.

Important

Don’t use the legacy GET /enterprises/{ent}/copilot/metrics endpoint. It was closed April 2, 2026 and returns 404. Use the /copilot/metrics/reports/enterprise-1-day report endpoint instead.


The endpoints

Usage metrics (engagement)

Pull all four single-day report families. The aggregate report does not contain the rows from the other three.

Report Enterprise endpoint What it preserves
Aggregate GET /enterprises/{ent}/copilot/metrics/reports/enterprise-1-day?day=YYYY-MM-DD Daily totals and aggregate breakdowns
Users GET /enterprises/{ent}/copilot/metrics/reports/users-1-day?day=YYYY-MM-DD Per-user usage
User teams GET /enterprises/{ent}/copilot/metrics/reports/user-teams-1-day?day=YYYY-MM-DD Daily user-to-team membership used to attribute user usage
Repositories GET /enterprises/{ent}/copilot/metrics/reports/repos-1-day?day=YYYY-MM-DD Repository usage detail

The script’s --org flag switches to organization scope. GitHub also publishes 28-day rolling reports for aggregate and user data, but not for user-team or repository data. The daily archive is the recommended path because you can rebuild longer windows from it.

Requires the Copilot usage metrics policy to be Enabled everywhere. GitHub serves the aggregate and user reports for up to one year back and doesn’t document retention for the user-team and repository reports. Pull all four daily and archive them yourself. Reference page: REST API endpoints for Copilot usage metrics.

Billing (cost)

Three calls, in order. The CSV export is the only way to get per-user rows without one call per known username.

Step Endpoint to call Docs
1. Create the report POST /enterprises/{ent}/settings/billing/reports — body {"report_type":"ai_credit","start_date":"YYYY-MM-DD","end_date":"YYYY-MM-DD"} (returns 202 + a report id) Create a usage report export
2. Poll until status: completed GET /enterprises/{ent}/settings/billing/reports/{id} Get a usage report export
3. Download the CSV fetch the signed download_urls[0] from step 2 (expires ~1h) —

Send header X-GitHub-Api-Version: 2026-03-10 on all three. Billing data is available for the past 24 months. Reference page: REST API endpoints for usage reports.

The ai_credit CSV gives you per-user, per-day, per-model rows with dollar amounts:

date, username, product, sku, model, quantity, unit_type,
applied_cost_per_quantity, gross_amount, discount_amount, net_amount,
total_monthly_quota, organization, repository, cost_center_name,
aic_quantity, aic_gross_amount, input, output, cache_read, cache_write

The last four are the token counts behind each model’s credit consumption. They show where the volume goes: on an agent-heavy day a single user can log 10.6M cache_read tokens against 37K output tokens, which the credit total alone won’t tell you. Field definitions are in the billing reports reference.

Tip

For a fast “total Copilot spend this month” number without the export, call GET /enterprises/{ent}/settings/billing/usage/summary?product=Copilot (docs). One call, aggregated totals, but no per-user breakdown.


The scripts

The scripts/ folder ships example scripts that implement the above. They’re a starting point: clean stdout (JSON/CSV), progress to stderr, meant to be adapted into your pipeline. Once your credentials are set up, verify them and then automate the daily pull.

Note

These scripts are built for quickly testing the APIs and your credentials, and for a simple daily pull into files. If you want dashboards instead of raw files, the Grafana add-on reuses the same two collectors and pushes daily summaries into Postgres for Grafana to read.

Script What it does Key flags
copilot-usage-metrics.sh Pulls one enterprise (or --org) usage report family → JSON. The workflow calls it four times daily. --report-type aggregate\|users\|user-teams\|repos, --day YYYY-MM-DD, --org, --28day, --app-id, --installation-id, --private-key
copilot-billing-export.sh Creates, polls, and downloads the ai_credit billing CSV. App auth recommended; PAT fallback supported. --start/--end, --last-28-days, --report-type (default ai_credit), --out, --poll-timeout, --app-id, --installation-id, --private-key

For the daily job, request all four report types for one day (--day, defaulting to yesterday). The 28-day flag is only for ad-hoc aggregate or user snapshots. The scripts need bash, curl, jq, and (for App auth) openssl, and set the 2026-03-10 API version header for you.


Automate with the example Action

For unattended daily collection, copy the example workflow and the scripts/ folder into your own repository (the workflow’s SCRIPTS_DIR defaults to scripts). Then set your credentials under Settings → Secrets and variables → Actions:

Kind Name Value
Variable ENTERPRISE your enterprise slug
Variable COPILOT_APP_ID the App ID
Variable COPILOT_INSTALLATION_ID the installation ID
Secret COPILOT_APP_PRIVATE_KEY the App’s .pem contents

App ID and installation ID are identifiers, not credentials, so they go in variables; the private key goes in secrets. Set all four from the terminal with gh (it encrypts the secret locally before upload):

gh variable set ENTERPRISE              --body "$ENTERPRISE"
gh variable set COPILOT_APP_ID          --body "$APP_ID"
gh variable set COPILOT_INSTALLATION_ID --body "$INSTALLATION_ID"
gh secret   set COPILOT_APP_PRIVATE_KEY < ./app.pem

gh targets the repo in the current directory; add --repo <owner>/<repo> to point elsewhere. The key is read from stdin rather than an argument, so it never lands in your shell history or the process list. If you can’t use gh, the Actions secrets REST API does the same — you seal each value against the repo’s public key yourself.

Commit the workflow and scripts (the credentials live in repo settings, not the tree):

git add .github/workflows/copilot-metrics-collection.yml scripts/copilot-*.sh
git commit -m "Add Copilot metrics & billing collection workflow"
git push

The workflow runs daily (and on demand via Run workflow), collects all four usage reports plus billing for the prior day, and uploads the files as a workflow artifact. Each report has its own filename. A failed usage report is surfaced without deleting successful files, and billing still runs.


Running it daily and landing it in a data lake

Prefer another scheduler? A Jenkins job, a GitLab schedule, or a plain cron entry run the same two scripts just as well.

Artifacts expire, so to keep a long-term history land the files in your data lake. Point the scripts at an output directory (copilot-usage-metrics.sh … > dir/usage-<report>-<day>.json and copilot-billing-export.sh … --out dir/billing-<day>.csv), then sync that directory to object storage with whatever you already use:

# pick the one that matches your stack
aws s3 cp ./copilot-data s3://my-bucket/copilot/$(date -u +%F)/ --recursive   # AWS S3
az storage blob upload-batch -d copilot/$(date -u +%F) -s ./copilot-data       # Azure Blob
gcloud storage cp ./copilot-data/* gs://my-bucket/copilot/$(date -u +%F)/      # GCS

Once the JSON and CSV are in your lake, load them into your warehouse. Join the user report’s user_login to billing username for the same date; do not expect identity fields in the aggregate report. Join the daily user report to the daily user-team report on user_id before rolling up team metrics. Partition raw files by date and report family so the daily pull becomes append-only history.

Note

Keep the raw files. GitHub serves the aggregate and user usage reports for up to a year and billing for 24 months, so your archived daily pulls become the long-term record.


Rate limits and security

Auth method Rate limit
GitHub App (installation token) 15,000 req/hr
Classic PAT fallback 5,000 req/hr

A full daily collection is roughly a dozen data requests, so either budget is plenty. The App is the better unattended choice because its tokens are short-lived and not tied to a person.


Gotchas