Guides

Filtering and sorting

LevelFour narrows a query at four tiers, from server-side flags to shell pipes. Pick the one that fits the task:

TierWhat it doesWhen to use
Built-in flagsServer-side filter, sort, group, paginateStandard queries
Interactive TUIVisual drill-down with live filtersExploring data
--jq expressionPost-process JSON with jq syntaxAd-hoc transforms
Shell pipesCompose with grep/awk/xargs/jqFull grep-style flexibility

Every recipe below has been validated against the live API. How recipes are validated covers the harness.

Built-in filter flags

Every list-style subcommand takes repeatable filter flags, a date window, a sort and a page. The full flag tables live on l4 costs and l4 recommendations. This is what they look like in use:

# Filter by one or more services, regions, accounts, environments, or tags
l4 costs breakdown \
  --service EC2 --service RDS \
  --region us-east-1 \
  --account 123456789012 \
  --tag-key Environment --tag-value production

# Date window (preset or explicit)
l4 costs breakdown --preset 30D         # 30D, 6M, 12M
l4 costs breakdown --start 2026-01-01 --end 2026-01-31

# Granularity
l4 costs breakdown --granularity daily   # or monthly

# Multi-dimension grouping
l4 costs breakdown --group-by service --group-by region

# Sort
l4 costs breakdown --sort-by cost --sort-order desc

# Pagination (max 100 per page)
l4 costs breakdown --page 2 --page-size 50

Before filtering, list what values are actually present in your data for a given dimension. This is the same set the dashboard offers in its autocomplete dropdowns:

l4 costs filters                 # summary of all dimensions
l4 costs filters service         # all services
l4 costs filters region          # all regions
l4 costs filters account         # all accounts
l4 costs filters tag-key         # all tag keys

The bare l4 costs filters prints a summary table with counts and sample values.

Interactive TUI

Add --tui to either costs breakdown or recommendations list for visual exploration:

l4 costs breakdown --tui
l4 recommendations list --tui

Inside the TUI:

KeyAction
/Live search (substring match across all visible columns)
fOpen filter drawer
tab(in filter mode) cycle dimension
enter(in filter mode) apply filter and re-fetch
FClear all active filters
s / SCycle sort column / toggle asc↔desc
n / pNext / previous page
enterToggle detail pane for selected row
?Full keyboard help
qQuit

Any flags passed on the command line seed the TUI's initial state, so l4 costs breakdown --service RDS --tui opens with service=RDS already applied.

Active filters render in the footer. That row is how you confirm a filter took.

Filtering with --jq

--jq runs a jq expression over the response before display, on every subcommand that returns data. The CLI uses gojq internally, so the syntax is compatible with standard jq. Output formats covers the flag itself.

Response envelopes differ between commands. costs breakdown wraps data at .data.items[] and recommendations list wraps at .data.data.items[], because recommendations list bundles the provider overview alongside the list. Copy the envelope path from the recipes below.

The -r (raw output) flag is not available via --jq. For raw strings in pipelines, use --json | jq -r '...' with your system jq instead.

Shell pipes

When stdout is not a terminal the CLI emits no spinner and no ANSI escapes, so pipelines compose cleanly:

l4 recommendations list --json | jq '.data.data.items[].service' | sort | uniq -c

Recipes

Every recipe below is read-only. None of them accept, reject, or execute recommendations. The write commands are l4 recommendations accept, reject and execute.
Every recipe that aggregates with jq sees a single page. A count or a TOTAL computed from one describes that page, not your whole backlog. To cover everything, read total_pages and loop, the way the multi-page aggregator does.

Cost queries

Recommendation queries

SDK equivalents

The same filters and sort fields are typed parameters on the SDK list methods. Use these when you are scripting in a programming language instead of the shell. Client construction lives on the Python, TypeScript and Go SDK pages.

from levelfour import LevelFour

client = LevelFour()

# RDS delete candidates
recs = client.recommendations.list_by_provider(
    "aws",
    service=["RDS"],
    page_size=100,
)
candidates = [r for r in recs if r.savings_percentage >= 99]
import { LevelFourClient } from "levelfour";

const client = new LevelFourClient();

const candidates = [];
for await (const rec of await client.recommendations.listByProvider("aws", {
    service: ["RDS"],
    page_size: 100,
})) {
    if (rec.savings_percentage >= 99) candidates.push(rec);
}
ctx := context.Background()
page, err := client.Recommendations.ListByProvider(ctx, "aws",
    &levelfour.ListByProviderRecommendationsRequest{
        Service:  []string{"RDS"},
        PageSize: levelfour.Int(100),
    })
if err != nil { log.Fatal(err) }

iter := page.Iterator()
for iter.Next(ctx) {
    rec := iter.Current()
    if rec.SavingsPercentage >= 99 {
        fmt.Println(rec.RecommendationID)
    }
}