SDKs

Go SDK

Installation

go get github.com/LevelFourAI/levelfour-go

Client 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}),
)
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

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, &notFoundErr):
        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"},
    }),
)
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