# Filtering and sorting

> Filter, sort, group and pipe LevelFour cost and recommendation data from the terminal, with the same parameters in the SDKs.

Source: https://docs.levelfour.ai/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:

| Tier              | What it does                              | When to use                 |
| ----------------- | ----------------------------------------- | --------------------------- |
| Built-in flags    | Server-side filter, sort, group, paginate | Standard queries            |
| Interactive TUI   | Visual drill-down with live filters       | Exploring data              |
| `--jq` expression | Post-process JSON with jq syntax          | Ad-hoc transforms           |
| Shell pipes       | Compose with grep/awk/xargs/jq            | Full grep-style flexibility |

Every recipe below has been validated against the live API. [How recipes are validated](/cli/recipes#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`](/cli/commands/costs#flags) and [`l4 recommendations`](/cli/commands/recommendations#flags). This is what they look like in use:

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

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

```bash
l4 costs breakdown --tui
l4 recommendations list --tui
```

Inside the TUI:

| Key       | Action                                                   |
| --------- | -------------------------------------------------------- |
| `/`       | Live search (substring match across all visible columns) |
| `f`       | Open filter drawer                                       |
| `tab`     | (in filter mode) cycle dimension                         |
| `enter`   | (in filter mode) apply filter and re-fetch               |
| `F`       | Clear all active filters                                 |
| `s` / `S` | Cycle sort column / toggle asc↔desc                      |
| `n` / `p` | Next / previous page                                     |
| `enter`   | Toggle detail pane for selected row                      |
| `?`       | Full keyboard help                                       |
| `q`       | Quit                                                     |

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.

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

## Filtering with `--jq`

`--jq` runs a [jq](https://jqlang.github.io/jq/) expression over the response before display, on every subcommand that returns data. The CLI uses [gojq](https://github.com/itchyny/gojq) internally, so the syntax is compatible with standard jq. [Output formats](/cli/output-formats#--jq) covers the flag itself.

> **Note**
>
> 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:

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

## Recipes

> **Note**
>
> Every recipe below is read-only. None of them accept, reject, or execute recommendations. The write commands are [`l4 recommendations accept`](/cli/commands/recommendations/accept), [`reject`](/cli/commands/recommendations/reject) and [`execute`](/cli/commands/recommendations/execute).

> **Warning**
>
> 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](/cli/recipes#aggregate-100-savings-recommendations) does.

### Cost queries

#### Top services by cost this month

Find the services consuming the largest share of your current cloud spend. The first step in any cost review.

```bash
l4 costs breakdown \
  --preset 30D \
  --sort-by cost --sort-order desc \
  --page-size 10
```

The top 10 services by cost over the last 30 days. Drop `--page-size` for the default 20; raise it to 100 to scan everything.

#### Monthly spend trend (all-time)

See month-over-month spend across your whole organization, for budget reviews and for spotting seasonality.

```bash
l4 costs monthly --json \
  | jq -r '.data.data_points[] | [.month, .amount] | @tsv'
```

Tab-separated `YYYY-MM` and dollar amount, one line per month. Pipe to `column -t` for aligned columns.

#### Daily spend for a specific window

Zoom into a date range to hunt for day-level anomalies. Reach for this when monthly totals look wrong and you want to find the day things spiked.

```bash
l4 costs daily --start 2026-03-01 --end 2026-03-31 --json \
  | jq -r '.data.data_points[] | [.date, .amount] | @tsv'
```

One row per day in the range. Pipe to `sort -k2 -n` to list days from cheapest to most expensive.

#### Cost breakdown by region

Which regions are driving your AWS bill? Groups the service list by region so you can compare.

```bash
l4 costs breakdown \
  --group-by region \
  --sort-by cost --sort-order desc \
  --page-size 20
```

Returns region-scoped cost rows. Add `--service EC2` to scope to a specific service within each region.

#### EC2 cost by region and account

Multi-dimensional grouping: see how EC2 cost breaks down by both region and account, over the last six months.

```bash
l4 costs breakdown \
  --preset 6M \
  --service EC2 \
  --group-by region --group-by account_id \
  --sort-by cost --sort-order desc \
  --page-size 50
```

Rows grouped by both region and account, sorted by cost. Swap `--preset 6M` for `--preset 30D` to see a shorter window.

### Recommendation queries

#### RDS delete candidates (≥99% savings)

Find RDS recommendations where LevelFour estimates 99% or more savings. These are usually idle databases or forgotten snapshots where the right action is delete, which makes them the lowest-effort wins in a cost-optimization sprint.

```bash
l4 recommendations list --service RDS --page-size 100 --json \
  | jq '[.data.data.items[] | select(.savings_percentage >= 99)]'
```

Returns a JSON array of matching recommendations. To get just IDs (useful for scripting):

```bash
l4 recommendations list --service RDS --page-size 100 --json \
  | jq -r '.data.data.items[] | select(.savings_percentage >= 99) | .recommendation_id'
```

#### Top 5 recommendations by monthly savings

Ranked list of the highest-impact recommendations across all services and providers.

```bash
l4 recommendations list \
  --sort-by monthly_savings --sort-order desc \
  --page-size 5
```

A table of the top 5. For a JSON-friendly version suitable for scripts, add `--json` and pipe to jq.

#### Recommendations with ≥50% savings

All recommendations that would cut the underlying resource cost in half or more. Anything above 50% is usually worth the engineering time.

```bash
l4 recommendations list --page-size 100 --json \
  | jq '[.data.data.items[] | select(.savings_percentage >= 50)]'
```

Returns a JSON array. Append `| length` to the jq expression to get just the count.

#### Count recommendations by service

How are your savings opportunities distributed? This groups the current page of recommendations by service and sorts by count.

```bash
l4 recommendations list --page-size 100 --jq \
  '[.data.data.items[] | .service] | group_by(.) | map({service: .[0], count: length}) | sort_by(-.count)'
```

An array of `{service, count}` objects sorted highest-first.

#### Count recommendations by status

See how many recommendations are pending vs optimized vs rejected, for tracking team throughput.

```bash
l4 recommendations list --page-size 100 --jq \
  '[.data.data.items[] | .status] | group_by(.) | map({status: .[0], count: length})'
```

An array of `{status, count}` objects.

#### Bulk-view RDS delete candidates

Pipe the RDS delete-candidate IDs straight into `recommendations view` to walk through each one's full detail in sequence. Built for a review session where you want the full justification before approving.

```bash
l4 recommendations list --service RDS --page-size 100 --json \
  | jq -r '.data.data.items[] | select(.savings_percentage >= 99) | .recommendation_id' \
  | xargs -I{} l4 recommendations view {}
```

Prints the full detail for every matching recommendation, in order. Remove the final `| xargs ...` to just get the ID list.

#### Export top recommendations to CSV

Dump the highest-savings recommendations into CSV for sharing in a spreadsheet or ticket.

```bash
l4 recommendations list \
  --sort-by monthly_savings --sort-order desc \
  --page-size 50 --json \
  | jq -r '.data.data.items[] | [.recommendation_id, .service, .monthly_savings, .savings_percentage] | @csv' \
  > top-recommendations.csv
```

Writes a CSV with four columns and no header. Prepend a header row:

```bash
echo '"id","service","monthly_savings","savings_percentage"' > top-recommendations.csv
l4 recommendations list ... | jq -r '... | @csv' >> top-recommendations.csv
```

#### RDS in one account, sorted by savings

Scope a review to a single account or environment using primary filter flags, then sort by impact.

```bash
l4 recommendations list \
  --service RDS \
  --account 123456789012 \
  --sort-by monthly_savings --sort-order desc \
  --page-size 20
```

A table scoped to the given account. Replace `--account` with `--environment production` or `--tag Environment=production` for tag-based scoping.

#### RDS delete-candidates summary table

The same RDS recommendations at 99% savings or more, formatted as a scannable table with the recommendation title inline (from `actions.overview`). The format for working through delete candidates in one sitting.

```bash
l4 recommendations list --page-size 100 --json \
  | jq -r '.data.data.items[]
    | select(.service == "RDS" and .savings_percentage >= 99)
    | [.recommendation_id, "$" + (.monthly_savings | tostring) + "/mo", .actions.overview]
    | @tsv' \
  | column -t -s $'\t'
```

A three-column table: ID, monthly savings, one-line title. Sorted highest-savings-first (API default). Change `--service RDS` to any other service or drop the `.service` filter for all providers.

#### Recommendations grouped by account with totals

Aggregate RDS delete-candidates by account and sum their savings. Useful for planning cleanup work. You see which accounts hold the biggest concentration of delete candidates and what each is worth.

```bash
l4 recommendations list --page-size 100 --json \
  | jq -r '
    ([.data.data.items[]
      | select(.service == "RDS" and .savings_percentage >= 99)
      | . + {acct_norm: (.account | sub(" \\(.*\\)$"; ""))}
    ]) as $rds
    | ($rds | group_by(.acct_norm) | map({
        account: .[0].acct_norm,
        env: ([.[].environment] | unique | join(", ")),
        count: length,
        monthly: (map(.monthly_savings) | add * 100 | round / 100)
      }) | sort_by(-.monthly)) as $grouped
    | "ACCOUNT\tENVIRONMENT\tCOUNT\tMONTHLY SAVINGS",
      "-------\t-----------\t-----\t---------------",
      ($grouped[] | [.account, .env, .count, "$" + (.monthly | tostring)] | @tsv),
      "-------\t-----------\t-----\t---------------",
      "TOTAL\t\t" + ($rds | length | tostring) + "\t$" + ($rds | map(.monthly_savings) | add * 100 | round / 100 | tostring)
  ' \
  | column -t -s $'\t'
```

A four-column table (account, environment, count, total monthly savings) with divider rows and a TOTAL footer. The `sub(" \\(.*\\)$"; "")` normalizes account IDs so `123456789012 (Live)` and `123456789012` merge into one row.

## 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](/sdks/python), [TypeScript](/sdks/typescript) and [Go](/sdks/go) SDK pages.

**Python**

```python
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]
```

**TypeScript**

```typescript
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);
}
```

**Go**

```go
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)
    }
}
```
