Server-side metering

Meter every AI call. Keep the payload private.

Record sanitized, request-level AI usage from server code, then join each call to the imported Stripe customer that paid for it. Traket stores metering fields only. Prompts, messages, model outputs, provider headers, and full responses are never collected.
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 clients
Best for most applications. Typed helpers, safe batching, retries, idempotency defaults, and metadata sanitization.
OpenAI wrapper
Best when OpenAI calls are already centralized. It returns the original response and records usage without sending provider payloads.
Best for custom gateways and runtimes without a Traket package. Every SDK sends the same JSON contract.
Historical usage is a separate path. Use dashboard CSV imports for backfill and the SDK or Events API for new request-level activity.

Install 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.

Package install
# TypeScript / JavaScript
pnpm add @traket/sdk

# Python
pip install traket-sdk

# Go
go get github.com/traketai/traket-go

Create 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.

Server environment
TRAKET_WRITE_KEY=traket_live_...
# Optional when using the hosted endpoint:
TRAKET_ENDPOINT=https://traket.ai/api/v1/usage/events

Project scoped

A key can write only to the project that created it. Rotate or revoke it without changing another workspace.

Server only

Never expose the key in browser JavaScript, a mobile bundle, logs, analytics, or customer-visible configuration.

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.

TypeScript quickstart
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.

Python quickstart
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.

Go quickstart
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.

TypeScript / JavaScript
const response = await margins.openai.track(
  () =>
    openai.responses.create({
      model: "gpt-5.4-mini",
      input,
    }),
  {
    externalCustomerId: stripeCustomerId,
    environment: "production",
    feature: "generate_blog_post",
  },
);
Python
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",
    },
)
Go
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",
    },
)
When the provider call fails, the wrapper records an error event and rethrows or returns the original provider error. Your application keeps its existing error semantics.

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.

Manual provider event
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 },
  ],
});
Traket records one request unit for every event automatically. Add token, image, audio, embedding, or tool-call units only; do not add a second request unit.

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.

Batch tracking
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

Transient network, timeout, rate-limit, and server failures retry with exponential jitter. Permanent 4xx failures do not.

Queue safety

An exhausted batch returns to the front of the queue with its original idempotency keys before newer work is sent.

Flush during orderly shutdown so queued events are not left in process memory:

Final flush
// 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

Provider, model, Stripe customer ID, stable feature and operation slugs, token or unit counts, explicit cost, duration, status, and allowlisted scalar metadata.

Never collected

Prompts, messages, outputs, images, files, tool arguments, API keys, authorization headers, secrets, or full provider responses.
  • 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 externalCustomerId only for an imported Stripe customer ID in cus_... format.
Do not send an internal tenant ID in externalCustomerId. The live ingest contract requires a Stripe customer identity that resolves to exactly one customer in the project.