GitHub Action

Add cloud cost reports to your pull requests

Post actual cloud costs (not estimates) and carbon footprint to your PRs automatically.

Quick Start

1. Create an API Key

Go to Settings > API Keys and create a new key.

2. Add Secret to Repository

  1. Go to your repo’s Settings > Secrets and variables > Actions
  2. Click New repository secret
  3. Name: CLOUDEXPAT_API_KEY
  4. Paste your API key

3. Create Workflow File

Create .github/workflows/cloudexpat-cost-report.yml:

name: CloudExpat Cost Report

on:
  pull_request:
    types: [opened, synchronize, reopened]
  workflow_dispatch:

permissions:
  pull-requests: write
  contents: read

jobs:
  cost-report:
    name: Generate Cost Report
    runs-on: ubuntu-latest

    steps:
      - name: Fetch Cost Report from CloudExpat
        id: fetch-report
        env:
          CLOUDEXPAT_API_KEY: ${{ secrets.CLOUDEXPAT_API_KEY }}
        run: |
          if [ -z "$CLOUDEXPAT_API_KEY" ]; then
            echo "::error::CLOUDEXPAT_API_KEY secret is not set"
            exit 1
          fi

          RESPONSE=$(curl -s -w "\n%{http_code}" \
            "https://data.cloudexpat.com/v1/report?format=markdown" \
            -H "Authorization: Bearer $CLOUDEXPAT_API_KEY")

          HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
          BODY=$(echo "$RESPONSE" | sed '$d')

          if [ "$HTTP_CODE" != "200" ]; then
            echo "::error::Failed to fetch report. HTTP $HTTP_CODE"
            exit 1
          fi

          echo "$BODY" > report.md
          echo "success=true" >> $GITHUB_OUTPUT          

      - name: Post Report as PR Comment
        if: github.event_name == 'pull_request' && steps.fetch-report.outputs.success == 'true'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('report.md', 'utf8');

            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });

            const botComment = comments.find(c =>
              c.user.type === 'Bot' && c.body.includes('CloudExpat Cost Report')
            );

            const body = report + '\n\n---\n<sub>Generated by [CloudExpat](https://app.cloudexpat.com)</sub>';

            if (botComment) {
              await github.rest.issues.updateComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: botComment.id,
                body: body
              });
            } else {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                body: body
              });
            }            

      - name: Add Report to Job Summary
        if: steps.fetch-report.outputs.success == 'true'
        run: cat report.md >> $GITHUB_STEP_SUMMARY

Features

  • Actual costs — Real data from AWS Cost Explorer, Azure Cost Management
  • Carbon emissions — Track your cloud’s environmental impact
  • Multi-account — Aggregates all connected cloud accounts
  • Smart updates — Updates existing PR comments instead of creating duplicates
  • Job summary — Report also appears in GitHub Actions summary

Configuration Options

Report Period

# Last 30 days (default)
"https://data.cloudexpat.com/v1/report?format=markdown&period=month"

# Last 7 days
"https://data.cloudexpat.com/v1/report?format=markdown&period=week"

# Last 24 hours
"https://data.cloudexpat.com/v1/report?format=markdown&period=day"

Filter by Account

# Specific account only
"https://data.cloudexpat.com/v1/report?format=markdown&accountId=YOUR_ACCOUNT_ID"

Exclude Carbon Data

"https://data.cloudexpat.com/v1/report?format=markdown&include_carbon=false"

Scheduled Reports

Add to your workflow triggers:

on:
  schedule:
    - cron: '0 9 * * 1'  # Every Monday at 9am UTC

Example Output

When posted to a PR, you’ll see:

## CloudExpat Cost Report

**Report Period**: 2025-11-16 to 2025-12-16

### Cost Summary

| Account | Provider | Current | Previous | Change |
|---------|----------|--------:|---------:|-------:|
| Production | AWS | $1,234.56 | $1,100.00 | +12.2% |
| Staging | Azure | $456.78 | $480.00 | -4.8% |
| **Total** | | **$1,691.34** | **$1,580.00** | **+7.0%** |

### Carbon Emissions

| Account | Provider | Emissions |
|---------|----------|----------:|
| Production | AWS | 45.67 kgCO2e |
| Staging | Azure | 12.34 kgCO2e |

### Resource Inventory

| Resource Type | Count |
|--------------|------:|
| EC2 Instances | 12 |
| S3 Buckets | 8 |
| RDS Databases | 3 |

Data Quality

The report includes data quality indicators:

  • Full Cost Data — Complete data from Cost Explorer/Cost Management
  • Estimated — Using pricing estimates when detailed data unavailable
  • Resource Inventory — Fallback showing detected resources when cost data is pending

New accounts may take 24-48 hours for cost data to appear.


Troubleshooting

“CLOUDEXPAT_API_KEY secret is not set”

  • Add the secret in Settings > Secrets and variables > Actions

“Invalid API key format”

“No cost data available”

  • Cost data takes 24-48h after connecting a new account
  • AWS: Enable Cost Explorer in your AWS account
  • Azure: Ensure Cost Management API access is granted

Comment not appearing

  • Check the workflow has pull-requests: write permission
  • Verify the workflow ran successfully in the Actions tab

Security Best Practices

  • One key per repository — Easier to revoke if compromised
  • Never commit keys — Always use GitHub Secrets
  • Rotate periodically — Create new keys and revoke old ones
  • Review access — Revoke unused keys from the dashboard

API keys provide read-only access to your cost and carbon data.