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 request and response bodies 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. Live SDK and Events API delivery requires Pro or Enterprise.

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.

A Free workspace can create a key during setup, but live requests receive 402 paid_plan_required until Stripe confirms Pro or an Enterprise entitlement. Historical CSV import remains available.
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. Every 402 retains the batch and honors Retry-After: paid-plan responses wait for an upgrade, while plan limits wait for their reset. Other permanent 4xx failures do not retry.

Queue safety

An exhausted transient or deferred 402 batch returns to the front with its original idempotency keys. A terminal non-402 4xx is reported once and removed instead of being scheduled again.

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 request and response bodies.
  • 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.