Meter every AI call. Keep the payload private.
On this page
Choose the shortest reliable path
TypeScript, Python, and Go clients share one ingest contract and the same delivery guarantees. Start with an SDK unless you operate a language-agnostic gateway.
SDK clientsOpenAI wrapperInstall a server client
Install the package for the runtime that owns the provider call. The Traket write key belongs only in backend processes, workers, and server routes.
# TypeScript / JavaScript
pnpm add @traket/sdk
# Python
pip install traket-sdk
# Go
go get github.com/traketai/traket-goCreate a project write key
Open Connections → API keys, create a project-scoped key, and copy it when it is shown. Traket stores only its hash and cannot reveal the plaintext again.
TRAKET_WRITE_KEY=traket_live_...
# Optional when using the hosted endpoint:
TRAKET_ENDPOINT=https://traket.ai/api/v1/usage/eventsProject scoped
Server only
TypeScript / JavaScript
track queues a sanitized event. Explicitly flush in short-lived routes and jobs so the process cannot exit before the batch is delivered.
import { createMargins } from "@traket/sdk";
const margins = createMargins({
writeKey: process.env.TRAKET_WRITE_KEY!,
});
await margins.track({
provider: "openai",
externalCustomerId: "cus_123",
environment: "production",
feature: "generate_blog_post",
model: "gpt-5.4-mini",
cost: {
amount: aiCallCost,
currency: "usd",
source: "manual_override",
},
});
await margins.flush();Python
Python accepts snake_case or wire-format camelCase event keys and sends the same payload as the TypeScript client.
import os
from traket import create_margins
margins = create_margins(
write_key=os.environ["TRAKET_WRITE_KEY"],
)
margins.track({
"provider": "openai",
"external_customer_id": "cus_123",
"environment": "production",
"feature": "generate_blog_post",
"model": "gpt-5.4-mini",
"cost": {
"amount": ai_call_cost,
"currency": "usd",
"source": "manual_override",
},
})
margins.flush()Go
The Go client uses language-native structs and serializes them to the shared Events API contract.
client, err := traket.NewClient(traket.Options{
WriteKey: os.Getenv("TRAKET_WRITE_KEY"),
})
if err != nil {
return err
}
_, err = client.Track(ctx, traket.UsageCaptureEvent{
Provider: "openai",
ExternalCustomerID: "cus_123",
Environment: "production",
Feature: "generate_blog_post",
Model: "gpt-5.4-mini",
Cost: &traket.UsageCost{
Amount: aiCallCost,
Currency: "usd",
Source: "manual_override",
},
})
if err != nil {
return err
}
_, _ = client.Flush(ctx)Wrap OpenAI calls
The wrapper returns the original provider response. It records the response and request IDs, model, duration, status, and token counts. Prompts, messages, and response content are never sent to or collected by Traket.
const response = await margins.openai.track(
() =>
openai.responses.create({
model: "gpt-5.4-mini",
input,
}),
{
externalCustomerId: stripeCustomerId,
environment: "production",
feature: "generate_blog_post",
},
);response = margins.openai.track(
lambda: client.responses.create(
model="gpt-5.4-mini",
input=user_input,
),
{
"external_customer_id": stripe_customer_id,
"environment": "production",
"feature": "generate_blog_post",
},
)response, err := traket.TrackOpenAI(
ctx,
client,
func(ctx context.Context) (any, error) {
return openaiClient.Responses.New(ctx, request)
},
traket.OpenAIAttribution{
ExternalCustomerID: stripeCustomerID,
Environment: "production",
Feature: "generate_blog_post",
},
)Track any provider
Use manual tracking for Anthropic, Bedrock, Vertex, Mistral, OpenRouter, Azure OpenAI, or an internal model gateway. Send the Stripe customer ID and the smallest useful set of operational dimensions.
const result = await anthropic.messages.create(request);
await margins.track({
provider: "anthropic",
model: request.model,
operation: "messages.create",
externalCustomerId: stripeCustomerId,
environment: "production",
feature: "support_reply",
cost: {
amount: aiCallCost,
currency: "usd",
source: "manual_override",
},
units: [
{ unitType: "token", unitName: "input", quantity: inputTokens },
{ unitType: "token", unitName: "output", quantity: outputTokens },
],
});captureUsage remains available as a compatibility alias for track.
Batch, retry, and shut down
Use trackBatch in gateways and workers that already collect multiple events. A request accepts between 1 and 100 events.
await margins.trackBatch({
events: rows.map((row) => ({
provider: row.provider,
model: row.model,
operation: row.operation,
externalCustomerId: row.stripeCustomerId,
feature: row.feature,
occurredAt: row.occurredAt,
cost:
row.costUsd === null || row.costUsd === undefined
? undefined
: {
amount: row.costUsd,
currency: "usd",
source: "manual_override",
},
})),
});Bounded retries
Queue safety
Flush during orderly shutdown so queued events are not left in process memory:
// TypeScript
await margins.shutdown();
# Python
margins.shutdown()
// Go
_ = client.Shutdown(ctx)For the exact response envelope, per-event states, payload ceilings, and retryable status codes, see the API response contract.
Prompts are never collected
Traket SDKs are metering clients, not observability payload collectors. Traket never collects or stores prompts, messages, model outputs, or full provider request and response bodies. There is no request-capture mode. Only usage counts, costs, operational identifiers, and customer attribution are sent to Traket.
Send
Never collected
- Use a short allowlist for metadata keys.
- Treat SDK sanitization as a backstop, not permission to pass private objects.
- Input and output token fields are numeric counts only; they never contain input or output text.
- Use
externalCustomerIdonly for an imported Stripe customer ID incus_...format.
externalCustomerId. The live ingest contract requires a Stripe customer identity that resolves to exactly one customer in the project.