GitHub Actions
Three workflows, each a whole file you copy into .github/workflows/. One comments a cost summary on every pull request, one runs a scheduled check with the Python or TypeScript SDK, and one uploads a daily report as a build artifact.
l4 CLI. Terraform cost estimation and diff checks inside a workflow live on CI/CD Integration.Prerequisites
- A LevelFour API key. Authentication covers where keys are created, the two key formats, and rotation.
- The key stored as a GitHub Actions secret named
LEVELFOUR_API_KEY.
read key covers all three workflows. See Key scopes.Comment a cost summary on a pull request
The job needs pull-requests: write, which is the only permission it asks for.
name: Cost Report
on:
pull_request:
types: [opened, synchronize]
jobs:
cost-report:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Get savings summary
id: summary
env:
LEVELFOUR_API_KEY: ${{ secrets.LEVELFOUR_API_KEY }}
run: |
RESPONSE=$(curl -s \
-H "Authorization: Bearer $LEVELFOUR_API_KEY" \
https://api.levelfour.ai/api/v1/recommendations/savings-by-provider)
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "## Cloud Cost Summary" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
echo '```json' >> $GITHUB_OUTPUT
echo "$RESPONSE" | python3 -m json.tool >> $GITHUB_OUTPUT
echo '```' >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const body = process.env.BODY;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body,
});
env:
BODY: ${{ steps.summary.outputs.body }}Scheduled cost check
The same job runs in Python or in TypeScript, on a weekday schedule, and prints what it finds to the job log. Each SDK reads LEVELFOUR_API_KEY from the environment, so the client is constructed with no arguments. Python SDK and TypeScript SDK cover the rest of each client.
name: Cost Check (Python)
on:
schedule:
- cron: '0 9 * * 1-5'
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install levelfour
- name: Run cost check
env:
LEVELFOUR_API_KEY: ${{ secrets.LEVELFOUR_API_KEY }}
run: |
python3 -c "
from levelfour import LevelFour
client = LevelFour()
summary = client.recommendations.get_savings_by_provider()
print(summary)
overview = client.recommendations.get_overview()
print(overview)
costs = client.costs.get_summary()
print(costs)
"name: Cost Check (TypeScript)
on:
schedule:
- cron: '0 9 * * 1-5'
jobs:
report:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install levelfour
- name: Run cost check
env:
LEVELFOUR_API_KEY: ${{ secrets.LEVELFOUR_API_KEY }}
run: |
npx tsx -e "
import { LevelFourClient } from 'levelfour';
const client = new LevelFourClient();
const summary = await client.recommendations.getSavingsByProvider();
console.log(JSON.stringify(summary, null, 2));
const costs = await client.costs.getSummary();
console.log(JSON.stringify(costs, null, 2));
"Daily report as a build artifact
The job writes costs.json and recommendations.json, then uploads both under the artifact name cost-report-<run id>.
name: Daily Cost Report
on:
schedule:
- cron: '0 9 * * 1-5'
jobs:
report:
runs-on: ubuntu-latest
steps:
- name: Fetch cost summary
env:
LEVELFOUR_API_KEY: ${{ secrets.LEVELFOUR_API_KEY }}
run: |
curl -s \
-H "Authorization: Bearer $LEVELFOUR_API_KEY" \
https://api.levelfour.ai/api/v1/costs/summary \
| python3 -m json.tool > costs.json
- name: Fetch recommendations
env:
LEVELFOUR_API_KEY: ${{ secrets.LEVELFOUR_API_KEY }}
run: |
curl -s \
-H "Authorization: Bearer $LEVELFOUR_API_KEY" \
"https://api.levelfour.ai/api/v1/recommendations?page=1&page_size=100" \
| python3 -m json.tool > recommendations.json
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: cost-report-${{ github.run_id }}
path: |
costs.json
recommendations.jsonNext
- CI/CD Integration is the
l4CLI equivalent: Terraform cost estimates and diffs that gate a pull request - Authentication covers key creation, formats and scopes
- Python SDK and TypeScript SDK are the clients behind the scheduled check
- Scheduled Execution monitors savings execution on a recurring schedule
Virtual tags
Assign one tag key to every AWS and GCP cost line from ordered rules, to fix inconsistent tags, map accounts and services to owners, and split shared costs.
PR bot configuration
How the optional .levelfour/config.yml file tunes the PR bot per repository, where it lives, every field with its default, and what it does not control.