SDKs
Go SDK
Installation
go get github.com/LevelFourAI/levelfour-goClient 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
ctx := context.Background()
summary, 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"),
})Recommendations Audit
summary, err := client.Recommendations.Audit.GetSummary(ctx)
page, err := client.Recommendations.Audit.List(ctx, &levelfour.ListSavingsRequest{
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"),
Provider: levelfour.String("aws"),
Service: []string{"EC2", "RDS"},
})Costs
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)Providers
providers, err := client.Providers.List(ctx)
top, err := client.Providers.GetTopRecommendations(ctx, "aws")
recs, err := client.Providers.ListRecommendations(ctx, "aws",
&levelfour.ListRecommendationsProvidersRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
SortBy: levelfour.String("monthly_savings"),
SortOrder: levelfour.String("desc"),
Service: []string{"EC2"},
DisplayStatus: []string{"available", "pending"},
},
)
overview, err := client.Providers.GetRecommendationsOverview(ctx, "aws")
filters, err := client.Providers.GetRecommendationFilters(ctx, "aws")
savings, err := client.Providers.ListRealizedSavings(ctx, "aws",
&levelfour.ListRealizedSavingsProvidersRequest{
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
},
)
savingsSummary, err := client.Providers.GetRealizedSavingsSummary(ctx, "aws")
spending, err := client.Providers.GetCostsSummary(ctx, "aws")
spendingList, err := client.Providers.ListCosts(ctx, "aws",
&levelfour.ListCostsProvidersRequest{
Format: levelfour.String("table"),
Page: levelfour.Int(1),
PageSize: levelfour.Int(50),
},
)
timeline, err := client.Providers.GetCostsTimeline(ctx, "aws",
&levelfour.GetCostsTimelineProvidersRequest{
Start: levelfour.String("2025-01-01T00:00:00.000Z"),
End: levelfour.String("2025-03-31T00:00:00.000Z"),
},
)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 |