# Integrate l4 into your CLI

> Shell out to the l4 binary from scripts, Makefiles and CI runners. Machine-readable output through --json and --jq, and stable exit codes to branch on.

Source: https://docs.levelfour.ai/guides/integrate-l4-into-your-cli


Any script, Makefile or CI runner can read LevelFour data by shelling out to the `l4` binary, with no SDK in its dependency tree. `--json`, `--jq`, `--template` and `--quiet` give it machine-readable output, and the [exit codes](/cli#exit-codes) are stable enough to branch on.

For typed objects in Python, TypeScript or Go, see [Integrate the SDK into your CLI](/guides/integrate-sdk-into-your-cli).

## Wire it into a runner

### Install the binary

Put `l4` on the runner. [Installation](/cli/installation) covers Homebrew, `go install`, the release tarballs, and pinning a version inside a CI image.

### Authenticate from a secret

`LEVELFOUR_TOKEN` bypasses the keychain, so a runner needs no interactive `l4 auth login` and nothing lands on the filesystem:

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

[Authentication](/cli/authentication) has the resolution order and the read versus read-write split.

> **Success**
>
> A `whoami` that names your identity and organization is the proof the secret reached the runner. Exit `4` means no credential resolved at all.

### Ask for machine-readable output

`--json` prints the same envelope the API returns. `--jq` filters it in process, so a runner with no `jq` binary still works:

```bash
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"
```

[Output formats](/cli/output-formats) has the rest of the flags, and [Recipes](/cli/recipes) has the multi-stage pipelines.

### Branch on the exit code

With `-q` the exit code is the only signal, which makes any command a boolean:

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

A cost gate on Terraform or CloudFormation wants the three-way version:

```bash
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
```

> **Warning**
>
> Exit `2` is `ExitIssuesFound`, not a crash. A script that treats every non-zero exit as a failure reports an over-budget cost gate and a broken command identically. Branch on the code. [Exit codes](/cli#exit-codes) lists them all.

## Wrap it from your own tooling

**Bash**

Pull a cost summary into shell variables:

```bash title="cost-summary.sh"
#!/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"
```

**Make**

A cost gate and a monthly export, as targets:

```make title="Makefile"
.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"
```

**Python**

Wrap the binary in a function and parse `--json`:

```python title="l4_wrapper.py"
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}")
```

## Vendor the binary into a CI image

`l4` is a single static Go binary, so an image needs no runtime libraries:

```dockerfile title="Dockerfile"
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=""
```

> **Warning**
>
> Leave `LEVELFOUR_TOKEN` empty in the image. An `ENV` value is baked into the layers and ships with them, so inject the token at run time from your CI secret store.

[Installation](/cli/installation) pins a version instead of tracking the latest release. For a complete GitHub Actions or GitLab job built on this binary, see [CI/CD Integration](/guides/ci-cd).

## Shell out or embed

Reach for the binary in scripts, Makefiles, CI runners and polyglot tooling. Reach for the SDK inside an internal CLI that wants native types. Start with `l4` when you are unsure: it integrates fastest, and the SDK path stays open.

The binary reads `LEVELFOUR_TOKEN`, the SDKs read `LEVELFOUR_API_KEY`. Shelling out also gets the CLI's own argument validation, output formatting and TUI for nothing, and an embedded SDK has none of it. [Integrate the SDK into your CLI](/guides/integrate-sdk-into-your-cli) compares the two in full.
