Axemere Gateway — Go SDK

A typed Go client for the Axemere AI Gateway. Explicit request/response structs, context-aware cancellation, and idiomatic error handling: built for production Go services.

GitHub → Axemere-LLC/axemere-go


Install

go get github.com/Axemere-LLC/axemere-go/gateway

Requires Go 1.24+.


Quick start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/Axemere-LLC/axemere-go/gateway"
)

func main() {
    // reads AXEMERE_GATEWAY_URL and AXEMERE_GATEWAY_TOKEN from env
    cfg, err := gateway.NewConfig()
    if err != nil {
        log.Fatal(err)
    }
    client, err := gateway.NewClient(cfg)
    if err != nil {
        log.Fatal(err)
    }

    result, err := client.Execute(context.Background(), gateway.ExecuteRequest{
        Provider: "openai",
        Model:    "gpt-4o-mini",
        Messages: []gateway.Message{
            {Role: "user", Content: "Hello"},
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.Content)
    fmt.Printf("Cost: $%s  Tokens in: %d  Tokens out: %d\n",
        result.Metering.CostUSD, result.Metering.TokensIn, result.Metering.TokensOut)
}

Configuration

Config

type Config struct {
    GatewayURL      string
    GatewayToken    string
    DefaultProvider string
    DefaultModel    string
    WorkloadID      string
    ProjectID       string
    AccountID       string
    CustomerID      string
    Labels          map[string]string
    ProviderAPIKey  string
    Timeout         time.Duration // default 120s
}
FieldEnv varDescription
GatewayURLAXEMERE_GATEWAY_URLGateway base URL (required)
GatewayTokenAXEMERE_GATEWAY_TOKENBearer token for authentication
DefaultProviderAXEMERE_PROVIDERProvider used when ExecuteRequest.Provider is empty
DefaultModelAXEMERE_MODELModel used when ExecuteRequest.Model is empty
WorkloadIDAXEMERE_WORKLOAD_IDWorkload ID for attribution and policy matching
ProjectIDAXEMERE_PROJECT_IDProject ID for budget controls and reporting
AccountIDAXEMERE_ACCOUNT_IDAccount ID for attribution
CustomerIDAXEMERE_CUSTOMER_IDCustomer ID for attribution
LabelsAXEMERE_LABELS (JSON)Arbitrary key-value labels attached to every request
ProviderAPIKeyAXEMERE_PROVIDER_API_KEYBYOK provider key forwarded to upstream
TimeoutAXEMERE_TIMEOUT_SECONDSHTTP timeout; default 120s

NewConfig(opts ...Option) (*Config, error)

Reads environment variables, then applies functional options. Options take precedence over env vars. Returns an error if AXEMERE_LABELS contains invalid JSON.

cfg, err := gateway.NewConfig(
    gateway.WithGatewayURL("http://localhost:7080"),
    gateway.WithGatewayToken("tok_..."),
    gateway.WithWorkloadID("wl_my_service"),
)

Functional options

OptionSets
WithGatewayURL(url string)GatewayURL
WithGatewayToken(token string)GatewayToken
WithDefaultProvider(p string)DefaultProvider
WithDefaultModel(m string)DefaultModel
WithWorkloadID(id string)WorkloadID
WithProjectID(id string)ProjectID
WithAccountID(id string)AccountID
WithCustomerID(id string)CustomerID
WithLabels(labels map[string]string)Labels
WithProviderAPIKey(key string)ProviderAPIKey
WithTimeout(d time.Duration)Timeout

(*Config).SetDefaults(opts ...Option)

Applies options in place. Each option replaces the corresponding field; it does not merge. Useful for switching providers or models inside a request loop.

(*Config).ProxyURL(provider string) string

Returns the proxy base URL for the given provider. Use as the base URL for an existing provider SDK (see Proxy mode).

Format: {GatewayURL}/proxy/{provider}[/k/{token}][/w/{workload}][/p/{project}][/a/{account}][/c/{customer}]/


Client

NewClient(cfg *Config) (*Client, error)

Creates a new client. Returns an error if cfg is nil or GatewayURL is empty. Safe for concurrent use.

(*Client).Execute(ctx context.Context, req ExecuteRequest) (*ExecuteResponse, error)

Sends a governed request and returns the full response.

result, err := client.Execute(ctx, gateway.ExecuteRequest{
    Provider: "anthropic",
    Model:    "claude-haiku-4-5-20251001",
    Messages: []gateway.Message{
        {Role: "user", Content: "What is 2+2?"},
    },
    Params: map[string]any{
        "max_tokens": 128,
    },
})

(*Client).Stream(ctx context.Context, req ExecuteRequest) (<-chan StreamChunk, error)

Sends a governed streaming request. Returns a channel that delivers StreamChunk values and is closed after the final chunk or on error.

Important: Cancel the context when finished reading, even if you break out of the range loop early. A background goroutine reads from the network and only unblocks when the channel is drained or the context is cancelled.

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

ch, err := client.Stream(ctx, gateway.ExecuteRequest{
    Provider: "openai",
    Model:    "gpt-4o-mini",
    Messages: []gateway.Message{{Role: "user", Content: "Count to 5"}},
})
if err != nil {
    log.Fatal(err)
}

for chunk := range ch {
    if chunk.Err != nil {
        log.Fatal(chunk.Err)
    }
    if chunk.IsFinal {
        fmt.Printf("\nRecord: %s\n", chunk.RecordID)
        break
    }
    fmt.Print(chunk.Content)
}

Request types

ExecuteRequest

FieldTypeDescription
Messages[]MessageConversation turns to send
ProviderstringProvider name; overrides Config.DefaultProvider
ModelstringModel identifier; overrides Config.DefaultModel
WorkloadIDstringOverrides Config.WorkloadID for this request
ProjectIDstringOverrides Config.ProjectID for this request
AccountIDstringOverrides Config.AccountID for this request
CustomerIDstringOverrides Config.CustomerID for this request
Labelsmap[string]stringMerged on top of Config.Labels; per-request keys win
ProviderAPIKeystringBYOK key for this request; overrides Config.ProviderAPIKey
DelegationTokenstringDelegation token for scoped access
Paramsmap[string]anyExtra provider parameters merged into action.params

Message

gateway.Message{Role: "user", Content: "Hello"}
gateway.Message{Role: "assistant", Content: "Hi there!"}
gateway.Message{Role: "system", Content: "You are a helpful assistant."}

Response types

ExecuteResponse

FieldTypeDescription
ContentstringAssistant text extracted from the provider response
RecordIDstringGateway record ID for audit lookup
RecordHashstringCryptographic hash of the record
ProviderstringProvider that handled the request
ModelstringModel that generated the response
MeteringMeteringToken counts and cost
ProviderResponsemap[string]anyRaw provider response body

StreamChunk

FieldTypeDescription
ContentstringText delta (empty on final chunk)
IsFinalbooltrue on the last chunk
RecordIDstringPopulated on the final chunk
Metering*MeteringPopulated on the final chunk
ErrerrorNon-nil when the stream terminates with an error

Metering

FieldTypeDescription
CostUSDstringTotal request cost in USD
TokensInintInput token count
TokensOutintOutput token count
BytesInintRequest bytes
BytesOutintResponse bytes
CacheHitTokens*intCached prompt tokens (Anthropic)
CacheMissTokens*intUncached prompt tokens (Anthropic)
CacheCreationTokens*intCache-write tokens (Anthropic)
ReasoningTokens*intReasoning tokens (o-series models)
MarkupMultiplierAppliedstringOrg pricing markup factor
CostBreakdown[]CostBreakdownItemPer-tier cost line items

Errors

import "errors"

result, err := client.Execute(ctx, req)
if err != nil {
    var denied *gateway.PolicyDeniedError
    var quota *gateway.QuotaExceededError
    var timeout *gateway.GatewayTimeoutError
    var gwErr *gateway.GatewayError

    switch {
    case errors.As(err, &denied):
        log.Printf("Policy denied: %s (record: %s)", denied.Reason, denied.RecordID)
    case errors.As(err, &quota):
        log.Printf("Quota exceeded. Upgrade: %s", quota.UpgradeURL)
    case errors.As(err, &timeout):
        log.Printf("Gateway timed out")
    case errors.As(err, &gwErr):
        log.Printf("Gateway error (HTTP %d): %s", gwErr.StatusCode, gwErr.Message)
    }
}
TypeWhen returnedExtra fields
*GatewayErrorBase type; network failure, non-2xx HTTP, malformed responseMessage, StatusCode, ResponseBody, Err
*PolicyDeniedErrorRequest denied by gateway policy (HTTP 403)Reason, Trace, RecordID
*QuotaExceededErrorBudget or quota exceeded (HTTP 429)UpgradeURL, RetryAfter
*GatewayTimeoutErrorConnector timed out (HTTP 504)

All types implement error. GatewayError implements Unwrap() so errors.Is and errors.As traverse the chain correctly.


Proxy mode

Use cfg.ProxyURL() to point any existing Go provider SDK at the gateway without changing packages.

import (
    "github.com/Axemere-LLC/axemere-go/gateway"
    "github.com/openai/openai-go"
    "github.com/openai/openai-go/option"
)

cfg, _ := gateway.NewConfig()
client := openai.NewClient(
    option.WithBaseURL(cfg.ProxyURL("openai")),
    option.WithAPIKey("placeholder"), // gateway handles auth
)

Note: The gateway token is embedded in the proxy URL path (/k/{token}). URLs appear in access logs. Prefer Client.Execute() / Client.Stream(): they send the token in the Authorization header.


Get a gateway

  • Free tier — self-hosted, no account required → Install
  • Self-Hosted — team features + control plane → Get started
  • Managed — fully hosted, zero-ops → Get started