Guides

Integrate l4 into your CLI

If you don't want to add an SDK dependency to an existing CLI / script / Makefile / CI runner, just shell out to the official l4 binary. Use --json (or --jq, --template, --quiet) to get machine-readable output, parse with the tools you already have, and rely on the stable exit codes for branching.

For an SDK-based alternative that gives you typed objects in Python / TypeScript / Go, see Integrate the SDK into your CLI.

Pattern

  1. Install l4 on the runner (brew, go install, or a release tarball)
  2. Authenticate via LEVELFOUR_TOKEN env var (no interactive auth login needed in CI)
  3. Invoke l4 ... --json (or --jq for inline filtering, --quiet for boolean checks)
  4. Branch on the exit code: 0 success, 1 error, 2 issues found, 4 auth required, 130 interrupted

Authentication in CI

export LEVELFOUR_TOKEN="$LEVELFOUR_TOKEN"   # from a CI secret
l4 whoami                                   # smoke test

The LEVELFOUR_TOKEN env var bypasses the keychain entirely — no interactive flow, no filesystem state. See Authentication.

Boolean checks: --quiet + exit code

if l4 status -q; then
    echo "API healthy, proceeding"
else
    echo "API down, aborting"
    exit 1
fi

For Terraform CI gates:

l4 diff --base main --fail-above 100 -q ./infra/
case $? in
    0) echo "No cost impact" ;;
    2) echo "Over budget by more than \$100/mo"; exit 1 ;;
    *) echo "l4 diff failed"; exit 1 ;;
esac

2 is ExitIssuesFound — distinct from generic failures (1).

Inline filtering with --jq

--jq runs the filter in-process — no separate jq binary on the runner:

HIGH_VALUE_RECS=$(l4 recommendations list \
    --status available \
    --sort-by monthly_savings --sort-order desc \
    --jq '.data.data.items[0:5] | map(.recommendation_id)')
echo "$HIGH_VALUE_RECS"

For complex multi-step pipelines (slurps, branches), pipe to standalone jq so each stage is debuggable. See Recipes for full examples.

Pulling cost summaries into a shell script

#!/usr/bin/env bash
set -euo pipefail

eval "$(
    l4 costs summary --provider aws --json \
        | jq -r '.data | "MONTHLY=\(.monthly_spending)\nFORECAST=\(.forecasted_monthly_costs)\nSAVINGS=\(.potential_savings)"'
)"

echo "AWS spend this month: \$$MONTHLY"
echo "Forecasted: \$$FORECAST"
echo "Potential savings: \$$SAVINGS"

Wrapping l4 from Make

.PHONY: cost-check
cost-check:
	@l4 diff --base main --fail-above 100 -q ./infra/ \
		|| { echo "Cost gate failed"; exit 1; }

.PHONY: weekly-report
weekly-report:
	@l4 export costs --period $$(date -u +%Y-%m) --format csv > "costs-$$(date -u +%Y-%m).csv"
	@l4 export recommendations --format csv > "recs-$$(date +%Y%m%d).csv"

Wrapping l4 from a Python script (no SDK)

import json
import subprocess

def l4(*args):
    result = subprocess.run(
        ["l4", *args, "--json"],
        capture_output=True,
        text=True,
        check=True,
    )
    return json.loads(result.stdout)

summary = l4("costs", "summary", "--provider", "aws")
print(f"Spend: ${summary['data']['monthly_spending']:,.2f}")

This pattern is useful when you want LevelFour data inside an existing tool but adding an SDK to the dependency tree isn't worth it.

Vendoring the binary in a Docker image

l4 is a single static Go binary, so no runtime libraries to install:

FROM alpine:3
RUN apk add --no-cache ca-certificates curl \
 && curl -fsSL https://github.com/LevelFourAI/levelfour-cli/releases/latest/download/levelfour_latest_linux_amd64.tar.gz \
    | tar xz -C /usr/local/bin levelfour l4 \
 && chmod +x /usr/local/bin/l4 /usr/local/bin/levelfour
ENV LEVELFOUR_TOKEN=""

For GitHub Actions, the GitHub Actions guide shows the canonical setup.

Trade-offs vs embedding the SDK

ConcernShell out to l4Embed the SDK
Language couplingNone — works in bash, Make, anywhereLocked to Python / TS / Go
Adds a dependencyNo (binary install)Yes (PyPI / npm / go get)
Typed objectsNo (you parse JSON)Yes
Best forScripts, CI runners, Makefiles, polyglot toolingInternal CLIs that want native types
AuthenticationLEVELFOUR_TOKEN env varLEVELFOUR_API_KEY env var
CLI's own validation, formatting, TUIAvailable for freeNot available

When in doubt, start with l4: it's faster to integrate and you can always switch to the SDK later if you need richer types. See Integrate the SDK into your CLI for that path.