SDKs
Go SDK
Every namespace on the Go client, the options NewClient takes, the iterator its paginated methods return, and the typed errors errors.As matches.
The Go client groups the LevelFour API into one namespace per resource on the client struct: client.Costs, client.Recommendations, client.Commitments and the rest. Every method takes a context.Context first.
providerID string after the context ("aws", "gcp", "azure", "k8s"). Methods with optional filters take a request struct next. Pass an empty one, such as &levelfour.GetSummaryCostsRequest{}, to take every default.Installation
go get github.com/LevelFourAI/levelfour-go@v0.2.0Client setup
import "github.com/LevelFourAI/levelfour-go/levelfour"
client, err := levelfour.NewClient("l4_live_...")
if err != nil {
log.Fatal(err)
}Pass an empty string and the client reads the key from the environment instead. Authentication names the variable.
client, err := levelfour.NewClient("")Confirm the client is wired before larger calls. GetWhoami answers only when the base URL resolves and the key is valid, which makes it a good CI smoke test.
me, err := client.Auth.GetWhoami(ctx)Constructor options
client, err := levelfour.NewClient("l4_live_...",
levelfour.WithBaseURL("https://api.staging.levelfour.ai"),
levelfour.WithMaxRetries(3),
levelfour.WithHTTPClient(&http.Client{Timeout: 60 * time.Second}),
)| Option | Function | Default |
|---|---|---|
| Base URL | levelfour.WithBaseURL(url) | https://api.levelfour.ai |
| Max Retries | levelfour.WithMaxRetries(n) | 2 |
| No Retries | levelfour.WithNoRetries() | Retries enabled |
| HTTP Client | levelfour.WithHTTPClient(c) | 30s timeout |
Recommendations
savings, err := client.Recommendations.GetSavingsByProvider(ctx)
potential, err := client.Recommendations.GetPotentialSavings(ctx)
overview, err := client.Recommendations.GetOverview(ctx)
processing, err := client.Recommendations.ListInProgress(ctx)
detail, err := client.Recommendations.Get(ctx, "REC-1234")
page, err := client.Recommendations.List(ctx, &levelfour.ListRecommendationsRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
SortBy: levelfour.String("monthly_savings"),
SortOrder: levelfour.ListRecommendationsRequestSortOrder("desc").Ptr(),
})
top, err := client.Recommendations.GetTop(ctx, "aws")
recs, err := client.Recommendations.ListByProvider(ctx, "aws",
&levelfour.ListByProviderRecommendationsRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
SortBy: levelfour.String("monthly_savings"),
SortOrder: levelfour.ListByProviderRecommendationsRequestSortOrder("desc").Ptr(),
Service: []string{"EC2"},
DisplayStatus: []string{"available", "pending"},
},
)
providerOverview, err := client.Recommendations.GetProviderOverview(ctx, "aws")
providerFilters, err := client.Recommendations.GetProviderFilters(ctx, "aws")
providerPotential, err := client.Recommendations.GetProviderPotentialSavingsSummary(ctx, "aws")
providerPotentialPage, err := client.Recommendations.ListProviderPotentialSavings(ctx, "aws",
&levelfour.ListProviderPotentialSavingsRecommendationsRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
},
)Acting on one recommendation: narrow the resources it covers, record why it was rejected, read its activity, and ask an admin to release it.
_, err := client.Recommendations.UpdateResourceSelection(ctx, "REC-1234",
&levelfour.ResourceSelectionRequest{
SelectedResources: []string{"vol-0abc1234def567890"},
},
)
_, err = client.Recommendations.AddRejectionFeedback(ctx, "REC-1234",
&levelfour.RejectionFeedbackRequest{
Reason: "not_applicable",
Explanation: levelfour.String("This volume backs a disaster recovery test."),
},
)
activity, err := client.Recommendations.GetRecommendationActivity(ctx, "REC-1234")
approval, err := client.Recommendations.RequestExecution(ctx, "REC-1234",
&levelfour.RequestExecutionBody{
ImplementationMethod: levelfour.String("one-click"),
},
)
waiting, err := client.Recommendations.ListPendingApprovals(ctx)Recommendations has what each of these accepts and returns.
Recommendations audit
Realized savings (audited completions). Account-wide and per-provider variants both live on client.Recommendations.Audit.
summary, err := client.Recommendations.Audit.GetSummary(ctx)
page, err := client.Recommendations.Audit.List(ctx, &levelfour.ListAuditRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
SortBy: levelfour.String("monthly_savings"),
SortOrder: levelfour.ListAuditRequestSortOrder("desc").Ptr(),
Start: levelfour.String("2025-01-01"),
End: levelfour.String("2025-03-31"),
})
providerSummary, err := client.Recommendations.Audit.GetProviderSummary(ctx, "aws")
providerPage, err := client.Recommendations.Audit.ListByProvider(ctx, "aws",
&levelfour.ListByProviderAuditRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
},
)Realized savings detail
client.Audit returns realized-savings detail for one audited row. The id is the numeric audit_id a row in the audit list carries. The list and summary views live on client.Recommendations.Audit above.
detail, err := client.Audit.GetRealizedAuditDetail(ctx, 4821)Costs
summary, err := client.Costs.GetSummary(ctx, &levelfour.GetSummaryCostsRequest{})
breakdown, err := client.Costs.List(ctx, &levelfour.ListCostsRequest{
Format: levelfour.String("table"),
Period: levelfour.String("2025-03"),
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
SortBy: levelfour.String("cost"),
SortOrder: levelfour.ListCostsRequestSortOrder("desc").Ptr(),
})
daily, err := client.Costs.GetDailyCosts(ctx, &levelfour.GetDailyCostsCostsRequest{
Start: levelfour.String("2025-03-01T00:00:00.000Z"),
End: levelfour.String("2025-03-31T00:00:00.000Z"),
})
monthly, err := client.Costs.GetMonthlyCosts(ctx)
forecast, err := client.Costs.GetForecast(ctx, &levelfour.GetForecastAPIV1CostsForecastGetRequest{})
growing, err := client.Costs.GetTopGrowing(ctx, &levelfour.GetTopGrowingAPIV1CostsTopGrowingGetRequest{
Limit: levelfour.Int(5),
})
providerSummary, err := client.Costs.GetProviderSummary(ctx, "aws", &levelfour.GetProviderSummaryCostsRequest{})
providerFilters, err := client.Costs.GetProviderFilters(ctx, "aws",
&levelfour.GetProviderFiltersCostsRequest{},
)
providerList, err := client.Costs.ListByProvider(ctx, "aws",
&levelfour.ListByProviderCostsRequest{
Format: levelfour.String("table"),
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
},
)
timeline, err := client.Costs.GetProviderTimeline(ctx, "aws",
&levelfour.GetProviderTimelineCostsRequest{
Start: levelfour.String("2025-01-01T00:00:00.000Z"),
End: levelfour.String("2025-03-31T00:00:00.000Z"),
},
)Costs also covers usage and unit costs, cost by tag, allocation coverage and Google Cloud label grouping.
Providers
providers, err := client.Providers.List(ctx)
topSavers, err := client.Providers.GetProviderTopSavers(ctx, "aws")
invoices, err := client.Providers.GetProviderInvoices(ctx, "gcp",
&levelfour.GetProviderInvoicesAPIV1ProvidersProviderIDCostsInvoicesGetRequest{},
)client.Providers lists connected providers and a few provider-only reads. Most per-provider drill-downs live on the namespace that owns the resource.
| Per-provider data | Namespace | Methods |
|---|---|---|
| Cost | client.Costs | GetProviderSummary, GetProviderFilters, ListByProvider, GetProviderTimeline |
| Recommendations | client.Recommendations | ListByProvider, GetProviderOverview, GetTop |
| Audit | client.Recommendations.Audit | ListByProvider, GetProviderSummary |
| Invoices and top savers | client.Providers | GetProviderInvoices, GetProviderTopSavers |
Commitments
Reserved Instances and Savings Plans. Commitments has what each read returns.
overview, err := client.Commitments.GetOverview(ctx, &levelfour.GetOverviewAPIV1CommitmentsOverviewGetRequest{})
reservations, err := client.Commitments.GetInventory(ctx, &levelfour.GetInventoryAPIV1CommitmentsInventoryGetRequest{
Instrument: "ri",
})
utilization, err := client.Commitments.GetUtilization(ctx, &levelfour.GetUtilizationAPIV1CommitmentsUtilizationGetRequest{
Type: levelfour.String("sp"),
Granularity: levelfour.String("monthly"),
})
coverage, err := client.Commitments.GetCoverageRates(ctx, &levelfour.GetCoverageRatesAPIV1CommitmentsCoverageRatesGetRequest{})
esr, err := client.Commitments.GetEsr(ctx, &levelfour.GetEsrAPIV1CommitmentsEsrGetRequest{})
plan, err := client.Commitments.GetRenewalPlan(ctx, "arn:aws:savingsplans::123456789012:savingsplan/abcd1234")Google Cloud
Reads that only Google Cloud answers. Google Cloud has the full set.
availability, err := client.GoogleCloud.GetDataAvailability(ctx)
budgets, err := client.GoogleCloud.GetBudgets(ctx)
utilization, err := client.GoogleCloud.GetUtilizationSummary(ctx)
access, err := client.GoogleCloud.GetAccess(ctx)Accounts
Connected cloud accounts, the modules your organization has, and connected work tools.
accounts, err := client.Accounts.ListConnectedAccounts(ctx,
&levelfour.ListConnectedAccountsAPIV1AccountsGetRequest{},
)
connections, err := client.Accounts.ListConnections(ctx)
modules, err := client.Accounts.ListCustomerModules(ctx)
installs, err := client.Accounts.ListGithubInstallations(ctx)Accounts covers the rest.
Anomalies
summary, err := client.Anomalies.GetSummary(ctx, &levelfour.GetSummaryAPIV1AnomaliesSummaryGetRequest{})
anomalies, err := client.Anomalies.ListAnomalies(ctx, &levelfour.ListAnomaliesAPIV1AnomaliesGetRequest{
Provider: levelfour.String("aws"),
PageSize: levelfour.Int(20),
})
anomaly, err := client.Anomalies.GetAnomaly(ctx, "anomaly_id")Cost views
A cost view is a saved set of filters and groupings you can read back and reuse.
views, err := client.CostViews.ListViews(ctx)
view, err := client.CostViews.CreateView(ctx, &levelfour.CostViewBody{
Name: "Production compute",
Providers: []string{"aws"},
Filters: map[string][]string{"service": {"Amazon Elastic Compute Cloud - Compute"}},
GroupBy: []string{"account_id"},
})Automated Savings grants
A grant is scoped, time-limited access that lets LevelFour apply a saving for you. Create one for a recommendation, or for a batch, then poll it until it reports connected.
grant, err := client.SavingsGrants.CreateGrant(ctx, &levelfour.CreateGrantRequest{
BatchParentID: "REC-1234",
})
status, err := client.SavingsGrants.GetGrantStatus(ctx, "grant_id")Automated Savings covers the access the grant stands on.
Repository binding
For a saving delivered as an infrastructure-as-code pull request, choose which repositories it opens against.
installs, err := client.RepoBinding.ListInstallations(ctx)
targets, err := client.RepoBinding.BindRepos(ctx, "REC-1234", &levelfour.BindReposRequest{
InstallationID: 12345678,
Repos: []string{"acme/infrastructure"},
})
bound, err := client.RepoBinding.ListTargets(ctx, "REC-1234")Integrations
Read a connected work tool and file a task in it. tool is the tool's name, such as jira.
connection, err := client.Integrations.GetToolConnection(ctx, "jira")
task, err := client.Integrations.CreateToolTask(ctx, "jira", &levelfour.CreateTaskPayload{
RecommendationID: "REC-1234",
Title: "Delete the unattached volume in us-east-1",
})Webhooks
endpoints, err := client.Webhooks.List(ctx)
endpoint, err := client.Webhooks.Register(ctx, &levelfour.RegisterEndpointRequest{
URL: "https://example.com/webhook",
EventTypes: []string{"recommendation.accepted", "optimization.completed"},
})
_, err = client.Webhooks.Delete(ctx, "ep_123")Auth
me, err := client.Auth.GetWhoami(ctx)Pagination
Paginated methods return a core.Page with typed items and an iterator that fetches the next page for you.
page, err := client.Recommendations.List(ctx, &levelfour.ListRecommendationsRequest{
PageSize: levelfour.Int(50),
})
if err != nil {
log.Fatal(err)
}
iter := page.Iterator()
for iter.Next(ctx) {
rec := iter.Current()
fmt.Printf("%s: $%.2f/mo\n", rec.Service, rec.MonthlySavings)
}
if err := iter.Err(); err != nil {
log.Fatal(err)
}Pagination has the collect-all and manual-navigation forms, and the page size defaults.
Error handling
Every error is a typed struct. Match one with errors.As and add a case per type you handle.
import "errors"
detail, err := client.Recommendations.Get(ctx, "rec_nonexistent")
if err != nil {
var notFoundErr *levelfour.NotFoundError
switch {
case errors.As(err, ¬FoundErr):
fmt.Printf("Not found: %v\n", notFoundErr.Body)
default:
fmt.Printf("Error: %v\n", err)
}
}Error Handling has the remaining types, and the failures the client retries before you ever see them.
Webhook verification
Inside your HTTP handler, where r is the request and body its bytes:
import "github.com/LevelFourAI/levelfour-go/levelfour/webhooks"
verifier, err := webhooks.NewVerifier("whsec_your_signing_secret")
if err != nil {
log.Fatal(err)
}
payload, err := verifier.Verify(r.Header, body)
if err != nil {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
fmt.Printf("Verified event: %v\n", payload["type"])Full HTTP handler
package main
import (
"fmt"
"io"
"log"
"net/http"
"github.com/LevelFourAI/levelfour-go/levelfour/webhooks"
)
func main() {
verifier, err := webhooks.NewVerifier("whsec_your_signing_secret")
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
payload, err := verifier.Verify(r.Header, body)
if err != nil {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
fmt.Printf("Received event: %v\n", payload)
w.WriteHeader(http.StatusOK)
})
log.Println("Listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}See Webhooks for event types and payloads, and Timestamp tolerance for the replay window and how to widen it.
Request options
The option package overrides client defaults for one request.
import "github.com/LevelFourAI/levelfour-go/option"
summary, err := client.Recommendations.GetSavingsByProvider(ctx,
option.WithMaxAttempts(5),
option.WithHTTPHeader(http.Header{
"X-Request-Id": []string{"abc123"},
}),
)| Option | Function | Description |
|---|---|---|
| Base URL | option.WithBaseURL(url) | Override base URL |
| HTTP Client | option.WithHTTPClient(c) | Custom HTTP client |
| Headers | option.WithHTTPHeader(h) | Extra headers |
| Max Attempts | option.WithMaxAttempts(n) | Override retry attempts |
| Auth Token | option.WithToken(t) | Override Bearer token |
| Query Params | option.WithQueryParameters(v) | Extra query parameters |
| Body Properties | option.WithBodyProperties(m) | Extra body properties |
Next
- Resources has the parameters and response shapes behind these methods
- Pagination has the page size defaults and iteration in the other two languages
- Error Handling has the full status-code table and what each error carries
- Webhooks has the event types, the payloads and the signature contract
- Authentication covers key formats, scopes, rotation and the
whoamicheck - SDK Overview compares the three clients option by option
TypeScript SDK
Installing and constructing the TypeScript client, and the behavior that is specific to it. Constructor and per-request options, raw responses, pagination, errors and webhook verification.
CLI Overview
The l4 command line tool. Install it, sign in, and run your first commands, then the command groups, the global flags every command takes, and the exit codes to script against.