Recipes
Every recipe on this page is validated against the live API by npm run validate:recipes in CI. If a recipe stops working, the build fails. Substitute your own recommendation_id values and account IDs.
Top 10 highest-savings recommendations
The biggest wins, sorted server side and projected to the id, the service and the monthly saving:
l4 recommendations list \
--sort-by monthly_savings --sort-order desc \
--page-size 10 \
--jq '.data.data.items[] | {id: .recommendation_id, service, savings: .monthly_savings}'--jq runs the filter in-process, so this recipe needs no pipe.
Total savings grouped by service
This groups a page of recommendations by service and ranks the groups by total potential savings:
l4 recommendations list --page-size 100 --json | jq -r '
.data.data.items
| group_by(.service)
| map({service: .[0].service, count: length, total_savings: (map(.monthly_savings) | add * 100 | round / 100)})
| sort_by(-.total_savings)
| .[]
| [.service, (.count | tostring), "$" + (.total_savings | tostring) + "/mo"] | @tsv
' | column -t -s $'\t'--page-size 100 is the maximum, so this total covers the first page of recommendations and nothing after it. To cover every page, wrap the call in the for loop from Aggregate 100% savings recommendations.Pending recommendations for one account
Filter by account substring. The account field carries both the ID and the friendly name:
l4 recommendations list --page-size 100 --json | jq -r '
[.data.data.items[]
| select(.account | test("123456789012"))
| select(.status == "pending" or .status == "available")
] as $matches
| "\($matches | length) match(es), $\($matches | map(.monthly_savings) | add)/mo"
'That count and that total also cover one page. Read the note under Total savings grouped by service before you quote either number.
For bigger filtering jobs, prefer the server-side flags:
l4 recommendations list --account "123456789012 (prod)" --status pendingAggregate 100% savings recommendations
Find every recommendation with savings_percentage == 100, meaning the resource can be removed entirely, iterate across all pages, and present a compact column-aligned report. CloudWatch entries are common and noisy, so the pipeline rolls them into a single row.
TOTAL=$(l4 recommendations list --page-size 100 --json | jq '.data.data.pagination.total_pages')
for p in $(seq 1 $TOTAL); do
l4 recommendations list --page-size 100 --page $p --json
done \
| jq -rs '
(map(.data.data.items) | add | map(select(.savings_percentage == 100))) as $recs
| ($recs | map(select(.service != "CloudWatch"))) as $detail
| ($recs | map(select(.service == "CloudWatch"))) as $cw
| ($detail[] | [.recommendation_id, .service, (.environment // "-"), (.account // "-"),
"$" + (.monthly_savings | tostring) + "/mo", .actions.overview] | @tsv),
(if ($cw | length) > 0 then
[
"[" + ($cw | length | tostring) + " items]",
"CloudWatch",
([$cw[].environment] | unique | join(", ")),
"multiple",
"$" + ($cw | map(.monthly_savings) | add * 100 | round / 100 | tostring) + "/mo",
"\($cw | length) CloudWatch recs aggregated (run with --service CloudWatch to list)"
] | @tsv
else empty end),
"TOTAL\t\t\t\t$" + (($recs | map(.monthly_savings) | add * 100 | round / 100) | tostring) + "/mo\t(" + ($recs | length | tostring) + " items)"
' \
| column -t -s $'\t'What this does:
- Reads
total_pagesfrom the first response to know how many pages to fetch. - Fetches each page with
--jsonand concatenates the streams. jq -rsslurps all pages into a single array, filters to 100% savings recs, splits CloudWatch off into its own bucket, and emits TSV rows.column -taligns the TSV into a readable table.
Sample output (sanitized):
REC-001 S3 prod 123456789012 $9466.17/mo Convert data lake to Parquet (savings opportunity)
REC-002 RDS gds 234567890123 $1293.74/mo Remove redundant cross-region backup copy
REC-003 RDS prod 123456789012 $1206.43/mo Migrate db.m5 → db.r6g (Graviton)
…
[85 items] CloudWatch multi multiple $255.00/mo 85 CloudWatch recs aggregated (run with --service CloudWatch to list)
TOTAL $3660.52/mo (114 items)JSON paths used: .data.data.pagination.total_pages, .data.data.items[].savings_percentage, .data.data.items[].service, .actions.overview. See l4 recommendations for the full shape.
Fetch full detail for the top five recommendations
Pipe the top recs to xargs to enrich each one with the full view payload:
l4 recommendations list \
--sort-by monthly_savings --sort-order desc --page-size 5 \
--json \
| jq -r '.data.data.items[].recommendation_id' \
| xargs -I{} l4 recommendations view {} --json--jq cannot emit raw strings (why), so this pipeline strips the quotes with a stand-alone jq -r.
Use it for an archival snapshot, or to feed a tool that needs the implementation detail.
Monthly CSV archive
Archive the current month's costs as CSV, for BI tools that prefer flat files:
PERIOD=$(date -u +%Y-%m)
l4 export costs --period "$PERIOD" --format csv > "costs-${PERIOD}.csv"costs-YYYY-MM.csv, named from the PERIOD the first line computes.The --period flag accepts YYYY-MM for billing-month exports. Pair with aws s3 cp (or gcloud storage cp) to push to object storage.
How recipes are validated
The npm run validate:recipes script in levelfour-docs extracts the fenced bash blocks tagged with a {/* recipe: <slug> */} sentinel on this page and on Filtering and sorting, runs each one through bash -o pipefail -c, and asserts a non-empty stdout (or zero-exit for snippets that redirect to a file). Auth resolves the same way as for any l4 command, and the LEVELFOUR_TOKEN environment variable works in CI.
To add a new recipe to this page, follow the same sentinel pattern and confirm the script picks it up:
npm run validate:recipesNext
l4 recommendations: thelistflags, the page cap and the JSON shapel4 export: CSV and JSON dumps of costs and recommendations- Output formats:
--json,--jq,--template,--csvand--quiet l4 diff: gate a build when a Terraform change adds too much monthly cost- CI/CD integration: the GitHub Actions and GitLab CI jobs that run the gate on a pull request
- Filtering and sorting: the filter flags, the
--jqquirks and the shorter worked queries
l4 telemetry
Opt in or out of crash telemetry, what a crash report carries, what is scrubbed from it, and why some builds send nothing even after you opt in.
MCP Overview
A remote MCP server over your cloud spend, savings backlog, and realized savings. Most tools read; the rest record a decision. Connect by signing in, no key to copy.