Pulling GitHub Actions Metrics Through the API
Pull GitHub Actions metrics from the WarpBuild API: CI billing, jobs, queue timings and daywise costs, with a wkey- key on the ci scope and CSV export.
Last verified:
GitHub Actions metrics are available over HTTP from the WarpBuild API at https://api.warpbuild.com/api/v1, using an API key that starts with wkey- and carries the ci scope, sent as an Authorization: Bearer header. Four read endpoints cover the ground most teams want: the CI billing report, the jobs report, the queue timings report, and daywise costs, each taking an RFC3339 start and end date, and the report endpoints also returning CSV.
This guide covers why a dashboard runs out of road, which endpoint answers which question, how to create and scope the key, and how to turn one report pull into a weekly cost per repository with the arithmetic written out.
Diagnosis
A dashboard answers last week's question. It was designed around the groupings someone needed at the time, and it holds those groupings until somebody edits it. The question a team actually asks on a Monday morning is usually one grouping away from what the dashboard offers.
Three shapes of question keep landing outside a fixed view.
Questions that need data the platform does not hold. Cost per team, cost per service, cost per tier of customer. None of that lives in a GitHub Actions report, because team ownership lives in your service catalog or in a CODEOWNERS file. Answering it means pulling rows keyed by repository and job name and joining them to a table you own.
Questions with an odd window. A dashboard tends to offer 7, 30, and 90 days. The real question is often "what did the two weeks after the test-sharding change cost compared with the two weeks before", which is two explicit RFC3339 windows and a subtraction.
Questions that should run without a human. A weekly digest in Slack, a check that fails when a repository crosses a budget, a row appended to a warehouse table every night. Those need a scheduled pull rather than a browser tab.
There is a second reason to pull rather than look. Reports render a page at a time, and the interesting analysis usually needs the whole set. The CI billing report caps per_page at 200 and the jobs and queue timings reports cap it at 50, so a full month of job rows is a paging loop in JSON or a single request with format=csv.
One constraint to plan around before you write the client. Every operation in the WarpBuild API is annotated alpha, and every response carries an X-WarpBuild-API-Stability header. The endpoints and fields below are subject to change. Pin only to documented behavior, read the stability header in your client, and treat a schema surprise as expected maintenance rather than an outage.
Fix
Four endpoints cover reporting. All of them take start_date and end_date as required RFC3339 timestamps.
| Endpoint | Returns | Page size |
|---|---|---|
GET /reports/billing/ci | Chart data, a summary, a paginated job list, and the available filters | per_page default 50, max 200 |
GET /reports/jobs | Per job run count, success rate, and p75 and p90 for duration, queue time, CPU, and memory | per_page default 10, max 50 |
GET /reports/queue-timings | Per runner and stack queue time p75 and p90, plus a daily chart splitting total queue time into GitHub time and WarpBuild time | per_page default 10, max 50 |
GET /jobs/daywise-costs | One row per day with date, amount, and cumulative_amount | Not paginated |
Start with the CI billing report, which is the one that carries money.
curl -sS -G 'https://api.warpbuild.com/api/v1/reports/billing/ci' \
-H 'Authorization: Bearer wkey-xxxx' \
-H 'Accept: application/json' \
--data-urlencode 'start_date=2026-08-04T00:00:00Z' \
--data-urlencode 'end_date=2026-08-11T00:00:00Z' \
--data-urlencode 'chart_group_by=repo' \
--data-urlencode 'per_page=200'chart_group_by accepts repo or runner_label. The filters are repos, runner_labels, stack_ids, job_names, vcs_job_ids, run_ids, and snapshot, plus a search parameter that matches repository, job name, and runner label. Sorting uses sort_by with sort_order set to asc or desc.
Each row in jobs.items carries the fields the billing table renders.
| Field | Meaning |
|---|---|
repo, job_name, runner_label | The identity of the job and the runner size it landed on |
vcs_job_id, run_id | The GitHub Actions job id and workflow run id, for joining to your own data |
execution_time | Time the job spent running |
billed_time | Time charged for the job, which is the figure to reconcile against an invoice |
runner_cost | Runner charge for the row |
snapshot, snapshot_cost | Whether the job used a snapshot runner, and the snapshot charge |
total_cost | Runner cost plus snapshot cost |
stack, stack_kind | The stack the job ran on, which separates hosted runners from BYOC |
timestamp | When the job ran |
For a spreadsheet or a warehouse load, ask for CSV instead. The export contains every row matching the current filters rather than the visible page.
curl -sS -G 'https://api.warpbuild.com/api/v1/reports/billing/ci' \
-H 'Authorization: Bearer wkey-xxxx' \
--data-urlencode 'start_date=2026-08-04T00:00:00Z' \
--data-urlencode 'end_date=2026-08-11T00:00:00Z' \
--data-urlencode 'format=csv' \
-o ci-billing-2026-08-04.csvThe jobs report answers the timing half of the same window, aggregated per repository, workflow, and job name.
curl -sS -G 'https://api.warpbuild.com/api/v1/reports/jobs' \
-H 'Authorization: Bearer wkey-xxxx' \
--data-urlencode 'start_date=2026-08-04T00:00:00Z' \
--data-urlencode 'end_date=2026-08-11T00:00:00Z' \
--data-urlencode 'chart_metric=duration' \
--data-urlencode 'chart_percentile=p90' \
--data-urlencode 'sort_by=duration_p90' \
--data-urlencode 'sort_order=desc' \
--data-urlencode 'per_page=50' \
| jq -r '.table.items[]
| [.repo, .workflow_name, .job_name, .run_count, .success_rate,
.duration_p90, .queue_time_p90, .cpu_p90, .memory_p90]
| @tsv'When end to end pipeline time moves while duration_p90 stays flat, the queue is where it went. The queue timings report aggregates per runner and stack, accepts a categories filter of stock, custom-byoc, or custom-warp, and returns a daily chart alongside the table.
curl -sS -G 'https://api.warpbuild.com/api/v1/reports/queue-timings' \
-H 'Authorization: Bearer wkey-xxxx' \
--data-urlencode 'start_date=2026-08-04T00:00:00Z' \
--data-urlencode 'end_date=2026-08-11T00:00:00Z' \
--data-urlencode 'categories=stock' \
--data-urlencode 'sort_by=queue_time_p90' \
| jq -r '.table.items[]
| [.runner_label, .stack, .stack_kind, .run_count,
.queue_time_p75, .queue_time_p90]
| @tsv'Daywise costs is the smallest response of the four and the easiest to graph. It returns an array of {date, amount, cumulative_amount}, which makes it the right endpoint for a burn-down against a monthly budget.
curl -sS -G 'https://api.warpbuild.com/api/v1/jobs/daywise-costs' \
-H 'Authorization: Bearer wkey-xxxx' \
--data-urlencode 'start_date=2026-08-01T00:00:00Z' \
--data-urlencode 'end_date=2026-09-01T00:00:00Z' \
| jq -r '.[] | [.date, .amount, .cumulative_amount] | @tsv'One more endpoint is worth wiring into the same job. GET /org_metrics/job_runner_recommendations returns the jobs whose utilization crossed an upgrade or downgrade threshold, with the current label and the recommended label for each. Full request and response definitions for all of these live in the API reference.
Configuration
Authentication is a single header. Create the key on the API keys page in dashboard settings and grant it the CI scope. API key scopes are CI, Cache, and Helios, and a key can carry any combination of them. The generated value starts with wkey- and is displayed only once, so write it into your secret manager in the same step that creates it. The API keys documentation covers creating, editing, and rotating keys.
Send it as a bearer token on every request.
Authorization: Bearer wkey-xxxx
Accept: application/jsonA weekly pull is a scheduled workflow, and the runner it needs is small.
name: weekly-actions-cost-report
on:
schedule:
- cron: "0 7 * * 1"
workflow_dispatch:
jobs:
pull-reports:
runs-on: warp-ubuntu-latest-x64-2x
steps:
- name: Compute the window
id: window
run: |
echo "start=$(date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z)" >> "$GITHUB_OUTPUT"
echo "end=$(date -u +%Y-%m-%dT00:00:00Z)" >> "$GITHUB_OUTPUT"
- name: Pull the CI billing report
env:
WARPBUILD_API_KEY: ${{ secrets.WARPBUILD_API_KEY }}
START: ${{ steps.window.outputs.start }}
END: ${{ steps.window.outputs.end }}
run: |
curl -sS -G 'https://api.warpbuild.com/api/v1/reports/billing/ci' \
-H "Authorization: Bearer $WARPBUILD_API_KEY" \
--data-urlencode "start_date=$START" \
--data-urlencode "end_date=$END" \
--data-urlencode 'format=csv' \
-o ci-billing.csv
- name: Roll the rows up per repository
run: |
curl -sS -G 'https://api.warpbuild.com/api/v1/reports/billing/ci' \
-H "Authorization: Bearer ${{ secrets.WARPBUILD_API_KEY }}" \
--data-urlencode "start_date=${{ steps.window.outputs.start }}" \
--data-urlencode "end_date=${{ steps.window.outputs.end }}" \
--data-urlencode 'per_page=200' \
| jq -r '.jobs.items
| group_by(.repo)[]
| [ .[0].repo, length,
(map(.billed_time) | add),
(map(.total_cost) | add) ]
| @tsv' | tee per-repo.tsv
- uses: actions/upload-artifact@v4
with:
name: actions-cost-report
path: |
ci-billing.csv
per-repo.tsvThree notes on running this in production. Walk page until jobs.next stops advancing when you take the JSON path, because a busy month exceeds 200 rows. Keep the window boundaries aligned to UTC midnight so two consecutive pulls neither overlap nor leave a gap. And log the X-WarpBuild-API-Stability header from each response, so an alpha change is visible in your own logs.
The same key type drives the rest of the platform. Terraform support exists for BYOC on AWS through the WarpBuild Terraform provider, and BYOC itself runs on AWS, GCP, and Azure, so runners inside your own cloud account report into the same endpoints as hosted ones. For an agent that reads these reports and proposes changes in a chat window, see the guide to the WarpBuild MCP server. Every one of them appears in the runner_label column of the billing rows.
Access control around the key is worth a line. Keys are created per organization from dashboard settings, and SSO is available for a flat $250 per month, whatever the user count, if you want the dashboard behind your identity provider before you hand out key creation.
Cost or Time Model
Turning a report pull into a cost per repository is arithmetic over two inputs: billed minutes per runner label, and the published per minute rate for that label. Rates below come from the WarpBuild pricing page, checked on 2026-08-13, and billing is per minute.
| Runner label | vCPU | RAM | USD per minute |
|---|---|---|---|
warp-ubuntu-latest-x64-2x | 2 | 8 GB | $0.004 |
warp-ubuntu-latest-x64-4x | 4 | 16 GB | $0.008 |
warp-ubuntu-latest-x64-8x | 8 | 32 GB | $0.016 |
warp-ubuntu-latest-x64-16x | 16 | 64 GB | $0.032 |
warp-ubuntu-latest-arm64-4x | 4 | 16 GB | $0.006 |
warp-macos-15-arm64-6x | 6 | 22 GB | $0.08 |
warp-windows-latest-x64-4x | 4 | 16 GB | $0.016 |
Two line items sit next to runner minutes and show up in the same rows.
| Metric | Price |
|---|---|
| Snapshot restore | $0.04 per job |
| Snapshot storage | $0.025 per snapshot-hour |
| Cache storage | $0.20 per GB-month |
| Cache write or restore | $0.0001 per operation |
Assumptions. The numbers below are an example pull for one repository over one week, of the shape the CI billing report returns. Rates are the published rates above. Costs use billed_time rather than execution_time, summed per runner label and converted to minutes, because billed time is what reconciles against an invoice. Queue time is excluded because it is not billed.
The pull. One request for acme/web over 2026-08-04 to 2026-08-11, filtered with repos=acme/web, grouped by runner label:
| Runner label | Jobs | Billed minutes | Rate | Cost |
|---|---|---|---|---|
warp-ubuntu-latest-x64-4x | 412 | 2,680 | $0.008 | $21.44 |
warp-ubuntu-latest-x64-8x | 96 | 1,150 | $0.016 | $18.40 |
warp-macos-15-arm64-6x | 60 | 300 | $0.08 | $24.00 |
The arithmetic. Runner cost is 2,680 x $0.008 = $21.44, plus 1,150 x $0.016 = $18.40, plus 300 x $0.08 = $24.00, giving $63.84 for the week. The 96 jobs on the 8x label restored a snapshot, which adds 96 x $0.04 = $3.84 in the snapshot_cost column. The weekly total for the repository is $63.84 + $3.84 = $67.68.
Two derived numbers make that figure useful to someone outside the platform team. Against 84 pull requests merged in the same week, the repository costs $67.68 / 84 = $0.81 per merged pull request. Held flat, the weekly figure annualizes to $67.68 x 52 = $3,519.36, which is $293.28 a month.
The cross-check. Run GET /jobs/daywise-costs over the same seven days and compare the last cumulative_amount against the sum of the per repository totals. Daywise costs is organization wide, so it should land at or above the single repository figure; if it lands below, the window boundaries are misaligned or a filter dropped rows.
Repeat the per repository step for every row in the group_by(.repo) output and you have the weekly table. Joining that table to team ownership is the next step, covered in attribute GitHub Actions cost to teams and repositories. If the duration percentiles beside the cost are what moved, read build duration percentiles for GitHub Actions works the jobs report from the timing side, and GitHub Actions observability with WarpBuild is the wider view of what the platform reports across an estate.
FAQ
Which API key scope do the metrics endpoints need?
The ci scope. Create the key on the API keys page in dashboard settings, grant it CI, and send it as an Authorization Bearer header. Keys start with wkey- and the value is displayed only once, so store it in your secret manager at creation time. The available scopes are CI, Cache, and Helios.
Is the WarpBuild API stable enough to build a report on?
Every operation is annotated alpha and every response carries an X-WarpBuild-API-Stability header. Expect breaking changes and pin only to documented behavior. Read the header in your client and log it, so a stability change shows up in your own logs before it shows up in a broken dashboard.
Can I get report data as CSV instead of JSON?
Yes. The reports endpoints accept format=csv, and the export contains every row matching the current filters rather than the visible page. JSON responses page instead, with per_page capped at 200 on the CI billing report and 50 on the jobs and queue timings reports.
How do I join WarpBuild billing rows to GitHub Actions runs?
Each row in the CI billing report carries vcs_job_id and run_id, which are the GitHub Actions job id and workflow run id. The same endpoint accepts vcs_job_ids and run_ids as filters, so you can pull the cost of one workflow run by id after a pull request merges.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.