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.

Per-provider methods take a 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.0

Client 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}),
)
OptionFunctionDefault
Base URLlevelfour.WithBaseURL(url)https://api.levelfour.ai
Max Retrieslevelfour.WithMaxRetries(n)2
No Retrieslevelfour.WithNoRetries()Retries enabled
HTTP Clientlevelfour.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 dataNamespaceMethods
Costclient.CostsGetProviderSummary, GetProviderFilters, ListByProvider, GetProviderTimeline
Recommendationsclient.RecommendationsListByProvider, GetProviderOverview, GetTop
Auditclient.Recommendations.AuditListByProvider, GetProviderSummary
Invoices and top saversclient.ProvidersGetProviderInvoices, 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, &notFoundErr):
        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

main.go
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"},
    }),
)
OptionFunctionDescription
Base URLoption.WithBaseURL(url)Override base URL
HTTP Clientoption.WithHTTPClient(c)Custom HTTP client
Headersoption.WithHTTPHeader(h)Extra headers
Max Attemptsoption.WithMaxAttempts(n)Override retry attempts
Auth Tokenoption.WithToken(t)Override Bearer token
Query Paramsoption.WithQueryParameters(v)Extra query parameters
Body Propertiesoption.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 whoami check
  • SDK Overview compares the three clients option by option

On this page

Ask the FinOps Agent about your cloud spend