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.

EndpointReturnsPage size
GET /reports/billing/ciChart data, a summary, a paginated job list, and the available filtersper_page default 50, max 200
GET /reports/jobsPer job run count, success rate, and p75 and p90 for duration, queue time, CPU, and memoryper_page default 10, max 50
GET /reports/queue-timingsPer runner and stack queue time p75 and p90, plus a daily chart splitting total queue time into GitHub time and WarpBuild timeper_page default 10, max 50
GET /jobs/daywise-costsOne row per day with date, amount, and cumulative_amountNot 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.

FieldMeaning
repo, job_name, runner_labelThe identity of the job and the runner size it landed on
vcs_job_id, run_idThe GitHub Actions job id and workflow run id, for joining to your own data
execution_timeTime the job spent running
billed_timeTime charged for the job, which is the figure to reconcile against an invoice
runner_costRunner charge for the row
snapshot, snapshot_costWhether the job used a snapshot runner, and the snapshot charge
total_costRunner cost plus snapshot cost
stack, stack_kindThe stack the job ran on, which separates hosted runners from BYOC
timestampWhen 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.csv

The 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/json

A 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.tsv

Three 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 labelvCPURAMUSD per minute
warp-ubuntu-latest-x64-2x28 GB$0.004
warp-ubuntu-latest-x64-4x416 GB$0.008
warp-ubuntu-latest-x64-8x832 GB$0.016
warp-ubuntu-latest-x64-16x1664 GB$0.032
warp-ubuntu-latest-arm64-4x416 GB$0.006
warp-macos-15-arm64-6x622 GB$0.08
warp-windows-latest-x64-4x416 GB$0.016

Two line items sit next to runner minutes and show up in the same rows.

MetricPrice
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 labelJobsBilled minutesRateCost
warp-ubuntu-latest-x64-4x4122,680$0.008$21.44
warp-ubuntu-latest-x64-8x961,150$0.016$18.40
warp-macos-15-arm64-6x60300$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.