Go SDK
Installation
go get github.com/LevelFourAI/levelfour-go@v0.1.0Client Setup
import "github.com/LevelFourAI/levelfour-go/levelfour"
client, err := levelfour.NewClient("l4_live_...")
if err != nil {
log.Fatal(err)
}With environment variable auto-detection (pass empty string):
client, err := levelfour.NewClient("")Client 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
Account-wide methods take only ctx. Per-provider variants live on the same namespace and take a providerID string ("aws", "gcp", "azure", "k8s").
ctx := context.Background()
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_123")
page, err := client.Recommendations.List(ctx, &levelfour.ListRecommendationsRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
SortBy: levelfour.String("monthly_savings"),
SortOrder: levelfour.String("desc"),
})
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.String("desc"),
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),
},
)
activity, err := client.Recommendations.GetRecommendationActivity(ctx, "rec_123")
_, err = client.Recommendations.AddRejectionFeedback(ctx, "rec_123",
&levelfour.RejectionFeedbackRequest{
Reason: levelfour.String("not applicable in this account"),
},
)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.String("desc"),
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),
},
)Audit
Top-level realized-savings detail for a single audited recommendation.
detail, err := client.Audit.GetRealizedAuditDetail(ctx, "rec_123")Costs
Account-wide methods take only ctx. Per-provider variants take a providerID.
summary, err := client.Costs.GetSummary(ctx)
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.String("desc"),
})
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)
providerSummary, err := client.Costs.GetProviderSummary(ctx, "aws")
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"),
},
)Providers
providers, err := client.Providers.List(ctx)client.Providers only lists connected providers. Per-provider drill-downs live on the resource-owning namespaces: cost data on client.Costs.GetProviderSummary / client.Costs.ListByProvider / client.Costs.GetProviderTimeline, recommendation data on client.Recommendations.ListByProvider / client.Recommendations.GetProviderOverview, and audit data on client.Recommendations.Audit.ListByProvider / client.Recommendations.Audit.GetProviderSummary.
Accounts
Manage connected cloud accounts and integration installations.
accounts, err := client.Accounts.ListConnectedAccounts(ctx,
&levelfour.ListConnectedAccountsAPIV1AccountsGetRequest{},
)
integration, err := client.Accounts.CreateIntegration(ctx, &levelfour.CreateIntegrationRequest{
Provider: "aws",
AccountID: "123456789012",
Name: "Production",
})
status, err := client.Accounts.GetIntegrationStatus(ctx, "integration_123")
modules, err := client.Accounts.ListCustomerModules(ctx)
installs, err := client.Accounts.ListGithubInstallations(ctx)
_, err = client.Accounts.CompleteGithubIntegration(ctx, &levelfour.GithubCompleteRequest{
InstallationID: 12345,
})Health
API readiness probes. Useful as a CI smoke test before larger calls.
ready, err := client.Health.HealthReady(ctx)
// Lightweight variant: issues a HEAD request and returns no body.
err = client.Health.HealthReadyHead(ctx)API Keys
keys, err := client.APIKeys.List(ctx)
newKey, err := client.APIKeys.Create(ctx, &levelfour.CreateAPIKeyRequest{
Name: "CI Pipeline",
})
_, err = client.APIKeys.Revoke(ctx, "key_123")
rotated, err := client.APIKeys.Rotate(ctx, "key_123")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. You can iterate with the built-in iterator, collect all results, or navigate pages manually.
Auto-iterate with Iterator
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)
}Collect all items
page, err := client.Recommendations.List(ctx, &levelfour.ListRecommendationsRequest{
PageSize: levelfour.Int(50),
})
if err != nil {
log.Fatal(err)
}
allRecs, err := levelfour.CollectAll(ctx, page)
if err != nil {
log.Fatal(err)
}Manual page navigation
page, err := client.Recommendations.List(ctx, &levelfour.ListRecommendationsRequest{
PageSize: levelfour.Int(50),
})
if err != nil {
log.Fatal(err)
}
for {
for _, rec := range page.Results {
fmt.Println(rec.RecommendationID)
}
nextPage, err := page.GetNextPage(ctx)
if errors.Is(err, core.ErrNoPages) {
break
}
if err != nil {
log.Fatal(err)
}
page = nextPage
}See Pagination for more details.
Error Handling
Go errors are typed structs that can be inspected with errors.As:
import "errors"
detail, err := client.Recommendations.Get(ctx, "rec_nonexistent")
if err != nil {
var notFoundErr *levelfour.NotFoundError
var rateLimitErr *levelfour.TooManyRequestsError
var badReqErr *levelfour.BadRequestError
switch {
case errors.As(err, ¬FoundErr):
fmt.Printf("Not found: %v\n", notFoundErr.Body)
case errors.As(err, &rateLimitErr):
fmt.Printf("Rate limited: %v\n", rateLimitErr.Body)
case errors.As(err, &badReqErr):
fmt.Printf("Bad request: %v\n", badReqErr.Body)
default:
fmt.Printf("Error: %v\n", err)
}
}See Error Handling for the full error type hierarchy.
Webhook Verification
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"])Custom timestamp tolerance (default is 5 minutes):
payload, err := verifier.VerifyWithTolerance(r.Header, body, 10*time.Minute)Full HTTP Handler Example
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 payload details.
Request Options
Override client defaults on a per-request basis using the option package:
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 |