# Recommendations

> Every recommendations method in the Python, TypeScript and Go SDKs, with its parameters, the row shape it returns, and a worked example.

Source: https://docs.levelfour.ai/resources/recommendations


A recommendation is one pending savings opportunity LevelFour found in your cloud accounts. Each one carries the affected service, the estimated monthly and annual savings, and the actions available on it: accept, reject, execute.

[Pagination](/pagination) has the defaults, the page-size cap, and the auto-paginating iterators the examples below rely on.

## Methods

| Method                                                                  | Returns                                                                                  |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| [Get savings by provider](#get-savings-by-provider)                     | A summary of potential savings grouped by cloud provider                                 |
| [Get potential savings](#get-potential-savings)                         | A potential savings summary grouped by provider                                          |
| [Get overview](#get-overview)                                           | Total spend, available savings, pending savings and saved-to-date, across every provider |
| [List recommendations](#list-recommendations)                           | A paginated list of every recommendation. Sorts, does not filter                         |
| [Get recommendation](#get-recommendation)                               | The full detail for one recommendation                                                   |
| [Get provider overview](#get-provider-overview)                         | The same metrics as Get overview, for one provider                                       |
| [List by provider](#list-by-provider)                                   | A paginated list for one provider, with filters                                          |
| [List in-progress](#list-in-progress)                                   | Every recommendation in `processing` status                                              |
| [Choose the resources](#choose-the-resources)                           | Limits a recommendation to the resources you keep                                        |
| [Say why it was rejected](#say-why-it-was-rejected)                     | Adds a reason to a rejected recommendation                                               |
| [Read the activity timeline](#read-the-activity-timeline)               | Views and decisions on one recommendation, in order                                      |
| [Request execution](#request-execution)                                 | Submits a recommendation for an admin to release                                         |
| [List what is waiting for approval](#list-what-is-waiting-for-approval) | Submissions still waiting for an admin                                                   |
| [Check Automated Savings access](#check-automated-savings-access)       | Whether Automated Savings can run for this recommendation                                |

### Get savings by provider

Returns a summary of potential savings grouped by cloud provider (AWS, GCP, Azure, K8s).

**Python**

```python  
summary = client.recommendations.get_savings_by_provider()
```

**TypeScript**

```typescript  
const summary = await client.recommendations.getSavingsByProvider();
```

**Go**

```go  
summary, err := client.Recommendations.GetSavingsByProvider(ctx)
```

### Get potential savings

Returns a potential savings summary grouped by provider.

**Python**

```python  
potential = client.recommendations.get_potential_savings()
```

**TypeScript**

```typescript  
const potential = await client.recommendations.getPotentialSavings();
```

**Go**

```go  
potential, err := client.Recommendations.GetPotentialSavings(ctx)
```

### Get overview

Returns overview metrics across all providers: total spend, available savings, pending savings, and saved-to-date.

**Python**

```python  
overview = client.recommendations.get_overview()
```

**TypeScript**

```typescript  
const overview = await client.recommendations.getOverview();
```

**Go**

```go  
overview, err := client.Recommendations.GetOverview(ctx)
```

### List recommendations

Returns a paginated list of every recommendation.

**Python**

```python  
page = client.recommendations.list(
    page=1,
    page_size=50,
    sort_by="monthly_savings",
    sort_order="desc",
)

for rec in page:
    print(f"{rec.service}: ${rec.monthly_savings}/mo")
```

**TypeScript**

```typescript  
for await (const rec of await client.recommendations.list({
    page_size: 50,
    sort_by: "monthly_savings",
    sort_order: "desc",
})) {
    console.log(`${rec.service}: $${rec.monthly_savings}/mo`);
}
```

**Go**

```go  
page, err := client.Recommendations.List(ctx, &levelfour.ListRecommendationsRequest{
    PageSize:  levelfour.Int(50),
    SortBy:    levelfour.String("monthly_savings"),
    SortOrder: levelfour.ListRecommendationsRequestSortOrder("desc").Ptr(),
})

iter := page.Iterator()
for iter.Next(ctx) {
    rec := iter.Current()
    fmt.Printf("%s: $%.2f/mo\n", rec.Service, rec.MonthlySavings)
}
```

#### Parameters

| Parameter    | Type     | Description                                                                                                                                           |
| ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `page`       | `int`    | Page number, 1-indexed                                                                                                                                |
| `page_size`  | `int`    | Items per page                                                                                                                                        |
| `sort_by`    | `string` | Sort field: `recommendation_id`, `service`, `environment`, `monthly_savings`, `annual_savings`, `savings_percentage`, `analysis_period`, `created_at` |
| `sort_order` | `string` | `asc` or `desc`                                                                                                                                       |

> **Note**
>
> This list sorts and pages. It takes no filters: to narrow by service, environment, account, tag or status, use [List by provider](#list-by-provider).

### Get recommendation

Returns the full detail for one recommendation.

**Python**

```python  
detail = client.recommendations.get("rec_123")
```

**TypeScript**

```typescript  
const detail = await client.recommendations.get({ recommendation_id: "rec_123" });
```

**Go**

```go  
detail, err := client.Recommendations.Get(ctx, "rec_123")
```

### Get provider overview

Returns the same metrics as [Get overview](#get-overview), scoped to a single provider: total spend, available savings, pending savings, and saved-to-date. Use it for a per-provider dashboard header.

**Python**

```python  
overview = client.recommendations.get_provider_overview("aws")
```

**TypeScript**

```typescript  
const overview = await client.recommendations.getProviderOverview("aws");
```

**Go**

```go  
overview, err := client.Recommendations.GetProviderOverview(ctx, "aws")
```

### List by provider

Returns a paginated per-provider recommendations list, filtered by service, environment, account, tag, and display status. Each row includes monthly and annual savings, current spend, and a savings percentage.

**Python**

```python  
page = client.recommendations.list_by_provider(
    "aws",
    page=1,
    page_size=20,
    service=["EC2", "RDS"],
    environment=["production"],
    display_status=["available"],
    sort_by="monthly_savings",
    sort_order="desc",
)

for rec in page:
    print(f"{rec.service}: ${rec.monthly_savings}/mo")
```

**TypeScript**

```typescript  
for await (const rec of await client.recommendations.listByProvider("aws", {
    page_size: 20,
    service: ["EC2", "RDS"],
    environment: ["production"],
    display_status: ["available"],
    sort_by: "monthly_savings",
    sort_order: "desc",
})) {
    console.log(`${rec.service}: $${rec.monthly_savings}/mo`);
}
```

**Go**

```go  
page, err := client.Recommendations.ListByProvider(ctx, "aws", &levelfour.ListByProviderRecommendationsRequest{
    Page:          levelfour.Int(1),
    PageSize:      levelfour.Int(20),
    Service:       []string{"EC2", "RDS"},
    Environment:   []string{"production"},
    DisplayStatus: []string{"available"},
    SortBy:        levelfour.String("monthly_savings"),
    SortOrder:     levelfour.ListByProviderRecommendationsRequestSortOrder("desc").Ptr(),
})

iter := page.Iterator()
for iter.Next(ctx) {
    rec := iter.Current()
    fmt.Printf("%s: $%.2f/mo\n", rec.Service, rec.MonthlySavings)
}
```

#### Parameters

| Parameter           | Type       | Description                                                                                                                    |
| ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `page`              | `int`      | Page number, 1-indexed                                                                                                         |
| `page_size`         | `int`      | Items per page                                                                                                                 |
| `sort_by`           | `string`   | Sort field: `recommendation_id`, `service`, `environment`, `account`, `tag`, `monthly_savings`, `savings_percentage`, `status` |
| `sort_order`        | `string`   | `asc` or `desc`                                                                                                                |
| `service`           | `string[]` | Filter by service                                                                                                              |
| `environment`       | `string[]` | Filter by environment                                                                                                          |
| `account`           | `string[]` | Filter by account ID                                                                                                           |
| `tag`               | `string[]` | Filter by tag                                                                                                                  |
| `virtual_tag_key`   | `string`   | A virtual tag's name or id. Keeps the opportunities whose resources carry one of `virtual_tag_value` under it                  |
| `virtual_tag_value` | `string[]` | Values of `virtual_tag_key` to keep. `__unallocated__` keeps the opportunities no config assigned                              |
| `display_status`    | `string[]` | Filter by status: `available`, `pending`, `processing`, `optimized`, `rejected`, `unavailable`                                 |

[Filtering and sorting](/guides/filtering-and-sorting) has recipes built on these parameters: the "RDS delete candidates" pattern, top-N-by-savings ranking, and CSV export.

### List in-progress

Returns every recommendation currently in `processing` status.

**Python**

```python  
processing = client.recommendations.list_in_progress()
```

**TypeScript**

```typescript  
const processing = await client.recommendations.listInProgress();
```

**Go**

```go  
processing, err := client.Recommendations.ListInProgress(ctx)
```

## Acting on a recommendation

These calls change one recommendation, or read who did what to it. Accepting or rejecting one happens in the dashboard, with [`l4 recommendations`](/cli/commands/recommendations), or through the [MCP server](/mcp). Choosing resources, adding rejection feedback, requesting execution and reading the approval queue take a read-write key. The activity timeline and the Automated Savings check work with any key. The examples show the Go SDK and plain HTTP; [SDK Overview](/sdks#sub-clients) lists which languages carry each namespace.

### Choose the resources

Limits a recommendation to the resources you keep. Any resource you leave out is removed from it, and the savings figure follows. The list must be a non-empty subset of the resources the recommendation carries.

**Go**

```go  
_, err := client.Recommendations.UpdateResourceSelection(ctx, "REC-1234",
    &levelfour.ResourceSelectionRequest{
        SelectedResources: []string{"vol-0abc1234def567890", "vol-0def5678abc123456"},
    },
)
```

**HTTP**

```bash  
curl -s -X POST "https://api.levelfour.ai/api/v1/recommendations/REC-1234/resource-selection" \
  -H "Authorization: Bearer $LEVELFOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"selected_resources": ["vol-0abc1234def567890", "vol-0def5678abc123456"]}'
```

### Say why it was rejected

Adds a reason to a recommendation that is already rejected. `reason` is one of `operational`, `strategy`, `not_applicable` or `other`, and `explanation` is optional free text.

**Go**

```go  
_, err := client.Recommendations.AddRejectionFeedback(ctx, "REC-1234",
    &levelfour.RejectionFeedbackRequest{
        Reason:      "not_applicable",
        Explanation: levelfour.String("This volume backs a disaster recovery test."),
    },
)
```

**HTTP**

```bash  
curl -s -X POST "https://api.levelfour.ai/api/v1/recommendations/REC-1234/rejection-feedback" \
  -H "Authorization: Bearer $LEVELFOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason": "not_applicable", "explanation": "This volume backs a disaster recovery test."}'
```

### Read the activity timeline

Returns the recommendation's events in order: who viewed it and every decision taken on it.

**Go**

```go  
activity, err := client.Recommendations.GetRecommendationActivity(ctx, "REC-1234")
```

**HTTP**

```bash  
curl -s "https://api.levelfour.ai/api/v1/recommendations/REC-1234/activity" \
  -H "Authorization: Bearer $LEVELFOUR_API_KEY"
```

### Request execution

Puts an accepted recommendation in front of an admin to release. It moves to `pending_approval` until an admin approves it in the dashboard. `implementation_method` picks how the change is delivered:

| Value                | Delivers the change as                                                 |
| -------------------- | ---------------------------------------------------------------------- |
| `one-click`          | Automated Savings: LevelFour applies it through the access you granted |
| `iac`                | A pull request against your infrastructure-as-code repository          |
| `one-click-plus-iac` | Both: LevelFour applies it, and a pull request keeps your code in step |
| `manual`             | Steps your team carries out                                            |

**Go**

```go  
approval, err := client.Recommendations.RequestExecution(ctx, "REC-1234",
    &levelfour.RequestExecutionBody{
        ImplementationMethod: levelfour.String("iac"),
    },
)
```

**HTTP**

```bash  
curl -s -X POST "https://api.levelfour.ai/api/v1/recommendations/REC-1234/execution/request" \
  -H "Authorization: Bearer $LEVELFOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"implementation_method": "iac"}'
```

A pull request needs a repository to land in. [Repository binding](/api-reference/repo-binding) chooses it, and Automated Savings needs a [grant](/api-reference/savings-grants) first.

### List what is waiting for approval

Returns every recommendation a member has submitted for execution that still needs an admin's release.

**Go**

```go  
waiting, err := client.Recommendations.ListPendingApprovals(ctx)
```

**HTTP**

```bash  
curl -s "https://api.levelfour.ai/api/v1/recommendations/pending-approvals" \
  -H "Authorization: Bearer $LEVELFOUR_API_KEY"
```

### Check Automated Savings access

Returns whether the access Automated Savings needs is in place for this recommendation's account, and the link that sets it up when it is not.

**Go**

```go  
access, err := client.Recommendations.GetAutomatedSavings(ctx, "REC-1234")
```

**HTTP**

```bash  
curl -s "https://api.levelfour.ai/api/v1/recommendations/REC-1234/automated-savings" \
  -H "Authorization: Bearer $LEVELFOUR_API_KEY"
```

[Automated Savings](/connectors/aws/automated-savings) covers the access itself.

## RecommendationItem

Returned in paginated list responses.

| Field                | Type      | Description                                                          |
| -------------------- | --------- | -------------------------------------------------------------------- |
| `recommendation_id`  | `string`  | Unique business identifier                                           |
| `service`            | `string`  | Cloud service name                                                   |
| `environment`        | `string?` | Environment tag                                                      |
| `virtual_tags`       | `object?` | The value of each virtual key on this opportunity, key name to value |
| `analysis_period`    | `string?` | Analysis time window                                                 |
| `monthly_savings`    | `float`   | Estimated monthly savings                                            |
| `annual_savings`     | `float`   | Estimated annual savings                                             |
| `current_spending`   | `float`   | Current monthly spend                                                |
| `savings_percentage` | `float`   | Savings as percentage of current spend                               |
| `actions`            | `object?` | Available actions for this recommendation                            |

## Find high-value AWS recommendations

**Python**

```python  
from levelfour import LevelFour

client = LevelFour()

for rec in client.recommendations.list_by_provider(
    "aws",
    page_size=20,
    sort_by="monthly_savings",
    sort_order="desc",
    display_status=["available"],
):
    if rec.monthly_savings > 100:
        print(f"{rec.recommendation_id}: ${rec.monthly_savings}/mo")
```

**TypeScript**

```typescript  
import { LevelFourClient } from "levelfour";

const client = new LevelFourClient();

for await (const rec of await client.recommendations.listByProvider({
    provider_id: "aws",
    page_size: 20,
    sort_by: "monthly_savings",
    sort_order: "desc",
    display_status: ["available"],
})) {
    if (rec.monthly_savings > 100) {
        console.log(`${rec.recommendation_id}: $${rec.monthly_savings}/mo`);
    }
}
```

**Go**

```go  
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/LevelFourAI/levelfour-go/levelfour"
)

func main() {
    client, err := levelfour.NewClient("")
    if err != nil {
        log.Fatal(err)
    }

    page, err := client.Recommendations.ListByProvider(
        context.Background(),
        "aws",
        &levelfour.ListByProviderRecommendationsRequest{
            PageSize:      levelfour.Int(20),
            SortBy:        levelfour.String("monthly_savings"),
            SortOrder:     levelfour.ListByProviderRecommendationsRequestSortOrder("desc").Ptr(),
            DisplayStatus: []string{"available"},
        },
    )
    if err != nil {
        log.Fatal(err)
    }

    iter := page.Iterator()
    for iter.Next(context.Background()) {
        rec := iter.Current()
        if rec.MonthlySavings > 100 {
            fmt.Printf("%s: $%.2f/mo\n", rec.RecommendationID, rec.MonthlySavings)
        }
    }
    if err := iter.Err(); err != nil {
        log.Fatal(err)
    }
}
```

## Next

* [Savings](/resources/savings) is the realized side: what was accepted, implemented and measured
* [Providers](/resources/providers) is the `providers` namespace: provider-scoped recommendations, realized savings, costs and the filter values you can pass
* [Pagination](/pagination) has the iterators, the defaults and the page-size cap
* [Error handling](/error-handling) has the codes these methods raise
