README
¶
Revenium Go SDK
Go SDK for automatic AI usage metering, cost tracking, and analytics across 13 providers.
Revenium wraps your existing AI provider clients (OpenAI, Anthropic, Google, etc.) and sends metering data asynchronously to the Revenium platform without blocking or altering your API calls. Each provider is its own Go module -- install only what you need.
Table of Contents
- How It Works
- Supported Providers
- Installation
- Authentication
- Quick Start
- Provider Guides
- Streaming
- Error Handling
- Usage Metadata & Context
- Tool Metering
- Job Outcomes
- API Reference
- Configuration Reference
- Cost Controls / Enforcement
- Troubleshooting
- Data & Privacy
- Versioning & Stability
- Architecture
- Development
- Examples
- Contributing
How It Works
- You call
Initialize()once at startup to configure the middleware GetClient()returns a wrapped provider client with the same API as the upstream SDK- Every API call is intercepted to collect usage metrics (tokens, latency, model, cost)
- Metrics are sent asynchronously via goroutines -- your request path is never blocked
- If metering fails, errors are logged and swallowed (configurable via
REVENIUM_FAIL_SILENT)
Clients returned by GetClient() are safe for concurrent use. Call Initialize() once; use the client from any goroutine.
Features
- Multi-Provider Support — OpenAI, Azure OpenAI, Anthropic, Anthropic on Bedrock, Google GenAI, Google Vertex AI, Perplexity, LiteLLM, fal.ai, Runway, Ollama, Groq, Grok (xAI)
- Consistent API — Same
Initialize()/GetClient()/NewRevenium*()pattern across all providers - Multi-Module Layout — Each provider is its own Go module; pull only what you import
- Fire-and-Forget Metering — Async sends via goroutines; never blocks your request path
- Streaming Support — First-class streaming wrappers for OpenAI, Anthropic, Google, Perplexity, LiteLLM, and fal.ai with token accumulation and first-token timing
- Resilience Built-in — Circuit breaker, exponential-backoff retry with jitter, and error classification shipped in
core/resilience - Tool & Job Metering — Report custom tool calls and long-running job outcomes via
core/meteringandcore/jobs - Prompt Capture — Optional, credential-sanitizing capture of system / input / output prompts
- Automatic .env Loading —
core.LoadEnvFiles()picks up.envautomatically in local development
Supported Providers
| Provider | Import Path | API Pattern |
|---|---|---|
| OpenAI | github.com/revenium/revenium-go-sdk/openai |
Initialize(opts...) / GetClient() |
| Azure OpenAI | github.com/revenium/revenium-go-sdk/openai |
Initialize(opts...) / GetClient() (auto-detected) |
| Anthropic | github.com/revenium/revenium-go-sdk/anthropic |
Initialize(opts...) / GetClient() |
| Anthropic Bedrock | github.com/revenium/revenium-go-sdk/anthropic |
Auto-detected when AWS env vars are present |
| Google GenAI | github.com/revenium/revenium-go-sdk/google |
Initialize(opts...) / GetClient() |
| Google Vertex AI | github.com/revenium/revenium-go-sdk/google |
Auto-detected when GOOGLE_CLOUD_PROJECT is set |
| Perplexity | github.com/revenium/revenium-go-sdk/perplexity |
Initialize(opts...) / GetClient() |
| LiteLLM | github.com/revenium/revenium-go-sdk/litellm |
Initialize(opts...) / Enable() / Disable() |
| fal.ai | github.com/revenium/revenium-go-sdk/fal |
Initialize(opts...) / Run() / Subscribe() / Stream() |
| Runway | github.com/revenium/revenium-go-sdk/runway |
Initialize(opts...) / GetClient() |
| Ollama | github.com/revenium/revenium-go-sdk/ollama |
Initialize(opts...) / GetClient() |
| Groq | github.com/revenium/revenium-go-sdk/groq |
Initialize(opts...) / GetClient() |
| Grok (xAI) | github.com/revenium/revenium-go-sdk/grok |
Initialize(opts...) / GetClient() |
| Tool Metering | github.com/revenium/revenium-go-sdk/core/metering |
ToolEventBuilder / MeteringClient.SendToolEvent() |
| Job Outcomes | github.com/revenium/revenium-go-sdk/core/jobs |
JobClient.ReportJobOutcome() / ListJobs() / etc. |
Installation
go get github.com/revenium/revenium-go-sdk/openai
go get github.com/revenium/revenium-go-sdk/anthropic
go get github.com/revenium/revenium-go-sdk/google
Each provider module pulls core and its upstream SDK transitively. Install only the providers you need.
Requirements: Go 1.22+
Authentication
The SDK requires a Revenium API key plus your provider API key(s). The recommended approach is environment variables, but you can also configure programmatically.
Via environment variables (recommended):
REVENIUM_METERING_API_KEY=hak_your_api_key
REVENIUM_METERING_BASE_URL=https://api.revenium.ai
OPENAI_API_KEY=sk-your-openai-key
Via options:
reveniumopenai.Initialize(
reveniumopenai.WithReveniumAPIKey("hak_your_api_key"),
reveniumopenai.WithOpenAIAPIKey("sk-your-openai-key"),
)
The SDK also supports automatic .env file loading via core.LoadEnvFiles().
Quick Start
OpenAI
package main
import (
"context"
openai "github.com/openai/openai-go/v3"
reveniumopenai "github.com/revenium/revenium-go-sdk/openai"
)
func main() {
if err := reveniumopenai.Initialize(); err != nil {
panic(err)
}
client, err := reveniumopenai.GetClient()
if err != nil {
panic(err)
}
defer client.Close()
resp, err := client.Chat().Completions().New(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-4o-mini",
Messages: []openai.ChatCompletionMessageParamUnion{
openai.UserMessage("Hello!"),
},
})
if err != nil {
panic(err)
}
println(resp.Choices[0].Message.Content)
}
Provider Guides
Azure OpenAI
Azure is auto-detected when AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT are set. Same Initialize() / GetClient() API -- the model field should be the Azure deployment name.
Anthropic
package main
import (
"context"
"log"
anthropic "github.com/anthropics/anthropic-sdk-go"
reveniumanthropic "github.com/revenium/revenium-go-sdk/anthropic"
)
func main() {
if err := reveniumanthropic.Initialize(); err != nil {
log.Fatal(err)
}
client, err := reveniumanthropic.GetClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
msg, err := client.Messages().CreateMessage(context.Background(), anthropic.MessageNewParams{
Model: "claude-sonnet-4-6",
MaxTokens: 1024,
Messages: []anthropic.MessageParam{anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!"))},
})
if err != nil {
log.Fatal(err)
}
_ = msg
}
Bedrock is auto-detected when AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set, or when BaseURL contains amazonaws.com. Disable with REVENIUM_BEDROCK_DISABLE=true. To use the Converse API instead of InvokeModel, set REVENIUM_BEDROCK_USE_CONVERSE=true.
Google GenAI / Vertex AI
package main
import (
"context"
"log"
reveniumgoogle "github.com/revenium/revenium-go-sdk/google"
"google.golang.org/genai"
)
func main() {
if err := reveniumgoogle.Initialize(); err != nil {
log.Fatal(err)
}
client, err := reveniumgoogle.GetClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
resp, err := client.Models().GenerateContent(
context.Background(),
"gemini-2.0-flash",
[]*genai.Content{genai.NewContentFromText("Hello!", "user")},
nil,
)
if err != nil {
log.Fatal(err)
}
_ = resp
}
Vertex AI is auto-detected when GOOGLE_CLOUD_PROJECT is set (uses GOOGLE_APPLICATION_CREDENTIALS for auth).
Perplexity
package main
import (
"context"
"log"
openai "github.com/openai/openai-go/v3"
reveniumperplexity "github.com/revenium/revenium-go-sdk/perplexity"
)
func main() {
if err := reveniumperplexity.Initialize(); err != nil {
log.Fatal(err)
}
client, err := reveniumperplexity.GetClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
resp, err := client.Chat().Completions().New(context.Background(), openai.ChatCompletionNewParams{
Model: "sonar",
Messages: []openai.ChatCompletionMessageParamUnion{openai.UserMessage("Hello!")},
})
if err != nil {
log.Fatal(err)
}
_ = resp
}
LiteLLM
package main
import (
"context"
"log"
reveniumlitellm "github.com/revenium/revenium-go-sdk/litellm"
)
func main() {
if err := reveniumlitellm.Initialize(); err != nil {
log.Fatal(err)
}
client, err := reveniumlitellm.GetClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
resp, err := client.Chat().Completions().New(context.Background(), reveniumlitellm.ChatCompletionRequest{
Model: "openai/gpt-4o-mini",
Messages: []reveniumlitellm.ChatMessage{
{Role: "user", Content: "Hello!"},
},
})
if err != nil {
log.Fatal(err)
}
_ = resp
}
LiteLLM also supports runtime Enable() / Disable() and GetStatus() for introspection.
fal.ai
package main
import (
"context"
"log"
reveniumfal "github.com/revenium/revenium-go-sdk/fal"
)
func main() {
if err := reveniumfal.Initialize(); err != nil {
log.Fatal(err)
}
client, err := reveniumfal.GetClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.Run(context.Background(),
"fal-ai/flux/schnell",
map[string]interface{}{"prompt": "a futuristic cityscape at sunset"},
nil,
)
if err != nil {
log.Fatal(err)
}
_ = result
}
The fal.ai middleware automatically detects the media type (image, video, audio, chat) from the endpoint ID. Also supports Subscribe() for queue-based execution and Stream() for streaming. Accepts FAL_KEY or FAL_API_KEY.
Streaming Example — OpenAI
All chat-capable providers (OpenAI, Anthropic, Google, Perplexity, LiteLLM, fal.ai) expose streaming wrappers. OpenAI example:
package main
import (
"context"
"fmt"
openai "github.com/openai/openai-go/v3"
reveniumopenai "github.com/revenium/revenium-go-sdk/openai"
)
func main() {
if err := reveniumopenai.Initialize(); err != nil {
panic(err)
}
client, err := reveniumopenai.GetClient()
if err != nil {
panic(err)
}
defer client.Close()
stream, err := client.Chat().Completions().NewStreaming(context.Background(), openai.ChatCompletionNewParams{
Model: "gpt-4o-mini",
Messages: []openai.ChatCompletionMessageParamUnion{openai.UserMessage("Write a haiku about Go")},
})
if err != nil {
panic(err)
}
for stream.Next() {
chunk := stream.Current()
if len(chunk.Choices) > 0 {
fmt.Print(chunk.Choices[0].Delta.Content)
}
}
if err := stream.Err(); err != nil {
panic(err)
}
// Close() triggers the final metering payload with isStreamed=true and timeToFirstToken.
if err := stream.Close(); err != nil {
panic(err)
}
}
The same Next() / Current() / Err() / Close() pattern applies to Anthropic (Messages().CreateMessageStream()), Google (Models().GenerateContentStream()), Perplexity (Chat().Completions().NewStreaming()), and LiteLLM (Chat().Completions().NewStreaming()). fal.ai streaming uses a channel: events, err := client.Stream(ctx, endpointID, input, metadata).
Error Handling Pattern
Metering errors never surface to your application — they are logged and swallowed (respecting REVENIUM_FAIL_SILENT). Upstream provider errors are returned normally:
resp, err := client.Chat().Completions().New(ctx, params)
if err != nil {
var revErr *core.ReveniumError
if errors.As(err, &revErr) {
// Check the typed error category
switch revErr.Type {
case core.ErrorTypeNetwork:
// retryable transport failure
case core.ErrorTypeValidation:
// 4xx from the provider
case core.ErrorTypeProvider:
// 5xx from the provider
}
}
return err
}
The core.ReveniumError type wraps HTTP status, category, and an optional underlying error. Use core.IsConfigError(err), errors.As, or revErr.Type to branch.
Groq / Grok / Ollama / Runway
All follow the same Initialize() / GetClient() / Close() pattern:
import reveniumgroq "github.com/revenium/revenium-go-sdk/groq"
if err := reveniumgroq.Initialize(); err != nil {
log.Fatal(err)
}
client, err := reveniumgroq.GetClient()
if err != nil {
log.Fatal(err)
}
defer client.Close()
Usage Metadata & Context
Attach per-request metadata via context.Context. Metering payloads automatically pick up these fields.
import "github.com/revenium/revenium-go-sdk/core"
ctx := core.WithUsageMetadata(context.Background(), map[string]interface{}{
"traceId": "session-123",
"productName": "my-product",
"taskType": "chat",
"agent": "my-agent",
})
resp, _ := client.Chat().Completions().New(ctx, req)
Or use a typed subscriber:
ctx = core.WithSubscriber(ctx, &core.Subscriber{
ID: "user-42",
Email: "user@example.com",
})
API Reference
OpenAI
| Function | Description |
|---|---|
Initialize(opts ...Option) |
Initialize global middleware from env + opts |
GetClient() |
Return the global *ReveniumOpenAI instance |
NewReveniumOpenAI(cfg) |
Construct a standalone instance |
IsInitialized() |
Report global initialization state |
GetOpenAIClient() |
Return the underlying wrapped openai.Client |
GetProvider() |
ProviderOpenAI / ProviderAzure |
Chat() / Embeddings() / Images() / Audio() / Responses() |
Typed interfaces for each operation |
Flush() / Close() |
Flush pending metering / close client |
Anthropic
| Function | Description |
|---|---|
Initialize(opts ...Option) |
Initialize global middleware |
GetClient() |
Return the global *ReveniumAnthropic |
NewReveniumAnthropic(cfg) |
Construct standalone instance |
Reset() |
Reset global state |
Messages().CreateMessage() |
Non-streaming message creation |
Messages().CreateMessageStream() |
Streaming wrapper |
ReconstructResponseFromChunks() |
Rebuild *anthropic.Message from a streaming wrapper |
Google (GenAI + Vertex AI)
| Function | Description |
|---|---|
Initialize(opts ...Option) |
Initialize global middleware |
GetClient() |
Return the global *ReveniumGoogle |
NewReveniumGoogle(cfg) |
Construct standalone instance |
Reset() |
Reset global state |
Models().GenerateContent() / GenerateContentStream() |
Chat / streaming |
Models().CreateEmbedding() |
Embeddings |
Models().GenerateImage() / EditImage() / UpscaleImage() |
Image gen/edit |
ExtractConfidenceScore() |
Extract confidence from candidate logprobs |
LiteLLM
| Function | Description |
|---|---|
Initialize(opts ...Option) |
Initialize from env / options |
GetClient() |
Return the global *ReveniumLiteLLM |
NewReveniumLiteLLM(cfg) |
Construct standalone instance |
ResetGlobalState() |
Reset global state |
Enable() / Disable() |
Toggle metering emission at runtime |
IsEnabled() |
Report current enable state |
GetStatus() |
MiddlewareStatus{Initialized, Enabled, HasConfig, ProxyURL} |
ExtractProvider() / ExtractModelSource() / ExtractModelName() |
Provider detection from LiteLLM model IDs |
IsValidModelFormat() |
Validate model ID format |
fal.ai
| Function | Description |
|---|---|
Initialize(opts ...Option) |
Initialize from env / options |
GetClient() |
Return the global *ReveniumFal |
NewReveniumFal(cfg) |
Construct standalone instance |
Reset() |
Reset global state |
Enable() / Disable() |
Toggle metering emission at runtime |
GetStatus() |
MiddlewareStatus{Initialized, Enabled, HasConfig, BaseURL} |
Client Methods:
| Method | Description |
|---|---|
client.Run(ctx, endpointID, input, metadata) |
Direct execution; auto-detected media type |
client.Subscribe(ctx, endpointID, input, metadata) |
Queue-based execution with polling |
client.Stream(ctx, endpointID, input, metadata) |
Streaming execution returning <-chan StreamEvent |
client.GenerateImage() / GenerateVideo() / GenerateAudio() |
Legacy typed helpers (delegate to Run) |
DetectFromEndpointID() / CorrectFromResponse() / DetectMediaType() |
Media type detection helpers |
Media Type Routing:
| Media Type | Metering Endpoint | Detection Examples | Billing Metric |
|---|---|---|---|
| IMAGE | /ai/images |
flux, stable-diffusion, recraft, sdxl | Per image (+ resolution) |
| VIDEO | /ai/video |
kling-video, veo, sora, runway, luma, \bwan- |
Seconds of video |
| AUDIO | /ai/audio |
kokoro, chatterbox, whisper, f5-tts, \bdia\b |
Chars/minutes/seconds |
| CHAT | /ai/completions |
openrouter, llm, text-generation | Token usage |
Detection is two-phase: regex over the endpoint ID, then corrected by inspecting response shape (images, video, audio_url, usage). Unknown endpoints default to IMAGE.
Tool Metering
Report custom external tool / API calls via the core/metering builder:
import (
"time"
"github.com/revenium/revenium-go-sdk/core/metering"
)
mc, _ := metering.NewMeteringClient(metering.MeteringClientConfig{
APIKey: os.Getenv("REVENIUM_METERING_API_KEY"),
})
defer mc.Close()
payload := metering.NewToolEvent("weather-api").
WithOperation("get_forecast").
WithDuration(245 * time.Millisecond).
WithSuccess(true).
Build()
mc.SendToolEvent(payload)
Job Outcomes
Track and amend long-running job outcomes with ROI metrics via core/jobs:
import (
"errors"
"github.com/revenium/revenium-go-sdk/core/jobs"
)
client, _ := jobs.NewJobClient(jobs.JobClientConfig{
APIKey: os.Getenv("REVENIUM_METERING_API_KEY"),
TeamID: os.Getenv("REVENIUM_TEAM_ID"),
})
_, err := client.ReportJobOutcome("job-123", &jobs.JobOutcome{
ExecutionStatus: jobs.ExecutionStatusSuccess,
OutcomeType: jobs.OutcomeConverted,
})
var alreadyReported *jobs.OutcomeAlreadyReportedError
if errors.As(err, &alreadyReported) {
// Outcome was already reported, amend it instead
}
Amending an Outcome
Outcomes are mutable. Use AmendJobOutcome to correct or update a previously reported outcome:
amended, err := client.AmendJobOutcome("job-123", &jobs.JobOutcomeAmendment{
Reason: "correcting outcome after manual review",
ExecutionStatus: jobs.ExecutionStatusFailed,
OutcomeType: jobs.OutcomeUnsuccessful,
})
Retrieve the full amendment history for a job:
history, _ := client.GetJobOutcomeHistory("job-123")
for _, entry := range history {
// entry.AmendmentSequence: 1 = initial report, 2+ = amendments
// entry.Reason: nil for initial report, set for amendments
}
Metadata Fields
Attached via core.WithUsageMetadata(ctx, map[string]interface{}{...}) or via core.WithSubscriber(ctx, ...).
| Field | Type | Description |
|---|---|---|
traceId |
string | Unique identifier for session / conversation |
taskType |
string | Type of AI task (e.g. "chat", "embedding") |
agent |
string | AI agent / bot identifier |
organizationName |
string | Organization or company name |
productName |
string | Product or feature name |
subscriptionId |
string | Subscription plan identifier |
responseQualityScore |
float64 | Custom quality rating (0.0–1.0) |
subscriber.id |
string | Unique user identifier |
subscriber.email |
string | User email address |
subscriber.credential |
object | Authentication credential (name and value) |
Trace Visualization Fields
Environment variables picked up automatically for distributed tracing and analytics:
| Environment Variable | Description |
|---|---|
REVENIUM_ENVIRONMENT |
Deployment environment (production, staging, development) |
REVENIUM_REGION |
Cloud region (auto-detected from AWS/Azure/GCP if not set) |
REVENIUM_CREDENTIAL_ALIAS |
Human-readable credential name |
REVENIUM_TRACE_TYPE |
Categorical identifier (alphanumeric, hyphens, underscores, max 128 chars) |
REVENIUM_TRACE_NAME |
Human-readable label for trace instances (max 256 chars) |
REVENIUM_PARENT_TRANSACTION_ID |
Parent transaction reference for distributed tracing |
REVENIUM_TRANSACTION_NAME |
Human-friendly operation label |
REVENIUM_RETRY_NUMBER |
Retry attempt number (0 for first attempt) |
Configuration Options
Common Environment Variables
| Variable | Required | Description |
|---|---|---|
REVENIUM_METERING_API_KEY |
Yes | Revenium API key (starts with hak_ or rev_) |
REVENIUM_METERING_BASE_URL |
No | Revenium API endpoint (default: https://api.revenium.ai) |
REVENIUM_DEBUG |
No | Enable debug logging (true/false) |
REVENIUM_PRINT_SUMMARY |
No | Terminal summary (true, human, json, false) |
REVENIUM_TEAM_ID |
No | Team ID for cost display in terminal summary |
REVENIUM_CAPTURE_PROMPTS |
No | Enable prompt capture (true/false) |
REVENIUM_MAX_PROMPT_SIZE |
No | Max bytes per captured prompt (default: 50000) |
REVENIUM_FAIL_SILENT |
No | Swallow metering errors (default: true) |
REVENIUM_API_TIMEOUT |
No | Metering HTTP timeout (default: 5s) |
REVENIUM_ORGANIZATION_NAME |
No | Default organization name |
Provider-Specific Variables
| Variable | Provider | Description |
|---|---|---|
OPENAI_API_KEY |
OpenAI | OpenAI API key |
AZURE_OPENAI_API_KEY |
Azure OpenAI | Azure OpenAI API key |
AZURE_OPENAI_ENDPOINT |
Azure OpenAI | Azure resource endpoint URL |
AZURE_OPENAI_API_VERSION |
Azure OpenAI | API version (default: 2024-02-15-preview) |
ANTHROPIC_API_KEY |
Anthropic | Anthropic API key |
AWS_ACCESS_KEY_ID |
Anthropic Bedrock | AWS access key (auto-enables Bedrock when paired with secret key) |
AWS_SECRET_ACCESS_KEY |
Anthropic Bedrock | AWS secret key |
REVENIUM_BEDROCK_DISABLE |
Anthropic Bedrock | Disable Bedrock transport (true) |
REVENIUM_BEDROCK_USE_CONVERSE |
Anthropic Bedrock | Use Converse API instead of InvokeModel (true) |
GOOGLE_API_KEY |
Google GenAI | Google AI Studio API key |
GOOGLE_CLOUD_PROJECT |
Google Vertex | GCP project ID (enables Vertex mode) |
GOOGLE_APPLICATION_CREDENTIALS |
Google Vertex | Path to service account key file |
GOOGLE_CLOUD_LOCATION |
Google Vertex | GCP region (default: us-central1) |
PERPLEXITY_API_KEY |
Perplexity | Perplexity API key |
LITELLM_PROXY_URL |
LiteLLM | LiteLLM proxy URL (e.g. http://localhost:4000) |
LITELLM_API_KEY |
LiteLLM | LiteLLM proxy API key |
FAL_KEY / FAL_API_KEY |
fal.ai | fal.ai API key (either is accepted) |
FAL_BASE_URL |
fal.ai | Override fal base URL (default: https://fal.run) |
FAL_QUEUE_BASE_URL |
fal.ai | Override fal queue URL (default: https://queue.fal.run) |
FAL_REQUEST_TIMEOUT |
fal.ai | Request timeout (default: 30m) |
RUNWAY_API_KEY |
Runway | Runway API key |
RUNWAY_BASE_URL |
Runway | Runway base URL (default: https://api.dev.runwayml.com) |
RUNWAY_VERSION |
Runway | Runway API version (default: 2024-11-06) |
OLLAMA_BASE_URL |
Ollama | Ollama base URL (default: http://localhost:11434/v1) |
GROQ_API_KEY |
Groq | Groq API key |
GROQ_BASE_URL |
Groq | Groq base URL (default: https://api.groq.com/openai/v1) |
XAI_API_KEY |
Grok | xAI API key |
XAI_BASE_URL |
Grok | xAI base URL (default: https://api.x.ai/v1) |
Cost Controls / Enforcement
Per-call enforcement blocks outgoing provider requests when a subscriber has a breached BLOCK cost control configured in Revenium. The Google and OpenAI middleware gate both sync and streaming paths; other providers will adopt the same gate in follow-up work (unified exception + Anthropic wiring + Node/Python/Go env-var normalization).
Terminology note: The customer-facing entity is called a cost control, served by the backend at
/v2/api/ai/cost-controls. This SDK polls a separate compiled-rules feed at/v2/api/ai/enforcement-rules/{teamId}and is unaffected by changes to the CRUD path — no SDK upgrade is required.
Environment variables
| Variable | Required | Description |
|---|---|---|
REVENIUM_METERING_API_KEY |
Yes | Revenium API key (starts with hak_). The _METERING_ infix is load-bearing — not REVENIUM_API_KEY. |
REVENIUM_TEAM_ID |
Yes for enforcement | Hashed team ID. If unset, the engine starts dormant (warns once, skips rule fetch) and all provider calls pass through. |
REVENIUM_ENFORCEMENT_BASE_URL |
No | Base URL for the enforcement API. Falls back to REVENIUM_METERING_BASE_URL when unset. Use https://api.dev.hcapp.io/profitstream for dev. |
The engine refuses to start if the resolved base URL is not an absolute http(s):// URL with a host — a safety check so a misconfigured env var cannot ship the API key to an unexpected scheme.
How it works
Each provider's Initialize() / NewReveniumX() constructor calls enforcement.Start(baseURL, apiKey, teamID), which boots a background poller on /v2/api/ai/enforcement-rules/{teamId} every 30s ± 5s jitter. Fetched rules are cached in memory; a 204 No Content response caches an empty ruleset.
Before every provider call, the middleware builds an EvalContext from the request (subscriber from core.WithSubscriber, model from the request params, provider/productName where applicable) and calls enforcement.Check(ctx). The evaluator:
- skips rules where
breached == false; - logs a warning and continues past rules where
shadowMode == trueoraction ∈ {WARN_ONLY, THROTTLE}— these are observation-only client-side, server still enforces server-side throttling; - returns
*ErrCostLimitExceededon the first matchingBLOCKcost control.
The engine fails open: fetch errors, network outages, missing team IDs, and an unreachable Revenium API all result in Check returning nil (request passes). Enforcement errors never bubble to user code as metering failures.
Error shape
type ErrCostLimitExceeded struct {
RuleID string // numeric server ruleId, stringified for Node SDK parity
RuleName string // server-provided human-readable rule name
Threshold float64 // rule limit
CurrentValue float64 // subscriber's metered value at block time
PeriodType string // DAILY / WEEKLY / MONTHLY / QUARTERLY
Action Action // always BLOCK today; retained for future hard-stop actions
Context EvalContext // subscriberId / productName / model / provider at call time
}
Recover the structured fields with errors.As:
resp, err := client.Chat().Completions().New(ctx, params)
if err != nil {
var ece *enforcement.ErrCostLimitExceeded
if errors.As(err, &ece) {
log.Printf("blocked by rule %s: $%.2f of $%.2f %s limit",
ece.RuleID, ece.CurrentValue, ece.Threshold, ece.PeriodType)
return // or degrade gracefully
}
return // not an enforcement error — handle as usual
}
Rule.Action enum
const (
ActionBlock Action = "BLOCK" // reject the call with ErrCostLimitExceeded
ActionThrottle Action = "THROTTLE" // observed client-side; server applies the rate limit
ActionWarnOnly Action = "WARN_ONLY" // observed client-side; log only
)
shadowMode: true on any rule (including action: BLOCK) downgrades it to observation-only for this SDK — the rule is logged but never throws. This matches the Node SDK semantics so a rule can be safely rolled out in shadow mode before flipping to enforcement.
End-to-end example
package main
import (
"context"
"errors"
"log"
oai "github.com/openai/openai-go/v3"
"github.com/revenium/revenium-go-sdk/core"
"github.com/revenium/revenium-go-sdk/core/enforcement"
revopenai "github.com/revenium/revenium-go-sdk/openai"
)
func main() {
if err := revopenai.Initialize(); err != nil {
log.Fatal(err)
}
client, _ := revopenai.GetClient()
ctx := core.WithSubscriber(context.Background(), &core.Subscriber{
ID: "samuel.combs@revenium.io",
Email: "samuel.combs@revenium.io",
})
_, err := client.Chat().Completions().New(ctx, oai.ChatCompletionNewParams{
Model: "gpt-4o-mini",
Messages: []oai.ChatCompletionMessageParamUnion{oai.UserMessage("hello")},
})
var ece *enforcement.ErrCostLimitExceeded
switch {
case errors.As(err, &ece):
log.Printf("cost limit hit: rule=%s current=$%.2f threshold=$%.2f",
ece.RuleID, ece.CurrentValue, ece.Threshold)
case err != nil:
log.Printf("provider error: %v", err)
}
}
Scope safety
A cost control with every scope field (subscriberId / productName / model / provider) empty would act as a team-wide global block. The evaluator skips such cost controls with a warning rather than letting one misconfigured API response deny service across every tenant.
End-to-end smoke test
scripts/e2e-enforcement/ ships a 6-phase smoke that exercises live dev, exception contract, shadow mode, fail-open paths, and the wrapped provider block. Set DEV_API_BASE_URL / DEV_API_KEY / DEV_TEAM_ID / DEV_USER_EMAIL in ~/revenium/.env, then:
set -a ; source ~/revenium/.env ; set +a
cd scripts/e2e-enforcement
go run .
Results land in scripts/e2e-enforcement/.e2e-report.md. Exit code is non-zero if any phase fails.
Troubleshooting
No tracking data appears
- Verify environment variables are set correctly (
.envin project root or exported in shell). - Enable debug logging:
REVENIUM_DEBUG=true. - Check console for
[Revenium DEBUG]/[Revenium INFO]log messages. - Verify your
REVENIUM_METERING_API_KEYis valid (starts withhak_orrev_).
middleware not initialized error
- Make sure you call
Initialize()beforeGetClient(). - Check that your
.envis readable from the working directory (or pre-export env vars). - Verify
REVENIUM_METERING_API_KEYis set.
Azure OpenAI not metering
- Confirm
AZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINT,AZURE_OPENAI_API_VERSIONare all set. - The
modelfield should be the Azure deployment name, not the base OpenAI model name.
fal.ai FAL_API_KEY is required
- fal.ai's official env var is
FAL_KEY; this SDK accepts bothFAL_KEYandFAL_API_KEY.
Debug Mode
REVENIUM_DEBUG=true
Then every outgoing metering payload is logged to stderr in full.
Architecture
This is a multi-module Go repository:
core/— Shared utilities: config, errors, logger, context helpers, metering client, resilience (circuit breaker, retry, error classification), prompt capture, job tracking.core/testutil/—MockMeteringServerfor offline tests.openai/,anthropic/,google/,litellm/,perplexity/,fal/,runway/,ollama/,groq/,grok/— Provider-specific middleware modules.go.work— Workspace file for local development across modules.
Each provider has its own go.mod with a replace directive pointing to local ../core during development. In production, consumers pull published versions of each module independently.
Development
make deps # Download all module dependencies
make build-all # Build all modules
make test-all # Run all tests
make lint-all # go vet all modules
make fmt-all # gofmt all modules
# Run tests for a single module
cd openai && go test -race -count=1 ./...
# Run with coverage
go test -cover ./...
# Sync the workspace
go work sync
Data & Privacy
By default, the SDK transmits only usage metrics to Revenium:
- Provider name and model identifier
- Token counts (input, output, total)
- Request latency and timing
- Transaction identifiers and stop reasons
No prompts, responses, or API keys are sent by default. Prompt capture is opt-in via REVENIUM_CAPTURE_PROMPTS=true, and when enabled, credentials are automatically sanitized before transmission.
Versioning & Stability
This SDK follows Semantic Versioning. The API is stable and ready for production use.
- Current version: v1.x (stable)
- Backward compatibility: Guaranteed within major versions
- Go versions: 1.22 and 1.23 tested in CI
- Upstream SDKs: Compatible with
openai-go/v3,anthropic-sdk-go v1.x,google.golang.org/genai v1.x
See CHANGELOG.md for release history.
Examples
For complete, runnable examples for each provider, see the examples/ directory:
examples/openai/-- Chat, streaming, embeddings, metadata, prompt captureexamples/anthropic/-- Chat, streamingexamples/google/-- Chat, streamingexamples/litellm/-- Chat via LiteLLM proxyexamples/perplexity/-- Chatexamples/fal/-- Image generationexamples/tool-metering/-- Custom tool event reportingexamples/job-metering/-- Job outcome tracking
Contributing
See CONTRIBUTING.md for development setup, how to add a new provider, and PR guidelines.
Security
See SECURITY.md. Report vulnerabilities to support@revenium.io -- do not create public issues.
License
MIT -- see LICENSE.
Getting Help
- Documentation: docs.revenium.io
- Bug Reports: GitHub Issues
- Email: support@revenium.io
- Website: www.revenium.ai
Directories
¶
| Path | Synopsis |
|---|---|
|
anthropic
module
|
|
|
core
module
|
|
|
examples
module
|
|
|
amend-outcome
command
|
|
|
anthropic/chat
command
|
|
|
anthropic/streaming
command
|
|
|
fal/image
command
|
|
|
google/chat
command
|
|
|
google/streaming
command
|
|
|
job-metering
command
|
|
|
litellm/chat
command
|
|
|
openai/chat
command
|
|
|
openai/embeddings
command
|
|
|
openai/metadata
command
|
|
|
openai/prompt-capture
command
|
|
|
openai/streaming
command
|
|
|
perplexity/chat
command
|
|
|
runway/video
command
|
|
|
tool-metering
command
|
|
|
fal
module
|
|
|
google
module
|
|
|
grok
module
|
|
|
groq
module
|
|
|
litellm
module
|
|
|
ollama
module
|
|
|
openai
module
|
|
|
perplexity
module
|
|
|
runway
module
|