Skip to main content
Every AI API call can be tracked as a transaction.

Basic Flow

  1. Make your AI provider call
  2. Extract usage from the response
  3. Send to Fenra’s API
// 1. Make the AI call
const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hello!" }]
});

// 2. Extract usage and send to Fenra
await fetch('https://api.fenra.io/ingest/usage', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': process.env.FENRA_API_KEY
  },
  body: JSON.stringify({
    provider: 'openai',
    model: response.model,
    usage: [{
      type: 'tokens',
      metrics: {
        input_tokens: response.usage.prompt_tokens,
        output_tokens: response.usage.completion_tokens,
        total_tokens: response.usage.total_tokens
      }
    }],
    context: {
      billable_customer_id: 'my-company',
      environment: 'production',
      feature: 'chat'
    }
  })
});

Transaction Schema

Every transaction needs:
FieldRequiredDescription
providerYesopenai, anthropic, gemini, bedrock, xai, deepseek, custom
modelYesModel identifier (e.g., gpt-4o)
usageYesWhat resources the AI call consumed (tokens, images, audio, etc.)
context.billable_customer_idYesYour internal identifier for billing

Context

Only billable_customer_id is required. Everything else is flexible context you can use for filtering, alerts, and reports. Example with common fields:
{
  "context": {
    "billable_customer_id": "acme-corp",
    "environment": "production",
    "feature": "chat-assistant",
    "user_id": "user-123",
    "request_id": "req-abc",
    "session_id": "sess-xyz",
    "team": "growth",
    "experiment": "pricing-v2"
  }
}
What you can do with context fields:
  • Filter in Cost Explorer: Show only production costs, or only a specific feature
  • Create targeted alerts: Alert when a specific feature exceeds a threshold
  • Build custom dashboards: Widgets filtered to specific context
  • Generate filtered reports: Reports scoped to specific environments or teams
Send whatever context makes sense for your use case. If you can filter by it, you can track it.

Usage Types

Tokens (most common)

For text generation, chat, and reasoning models:
{
  "type": "tokens",
  "metrics": {
    "input_tokens": 100,
    "output_tokens": 50,
    "total_tokens": 150
  }
}
Optional token metrics:
FieldDescription
cached_tokensTokens served from provider cache
cache_read_input_tokensTokens read from cache
cache_creation_input_tokensTokens used to create cache
reasoning_tokensTokens for reasoning (o1, o3 models)
thinking_tokensAlias for reasoning_tokens

Images

For image generation:
{
  "type": "images",
  "metrics": {
    "generated": 1,
    "size_px": 1024
  }
}

Audio

For speech-to-text or text-to-speech:
{
  "type": "audio_seconds",
  "metrics": {
    "input_seconds": 30.5,
    "output_seconds": 0,
    "total_seconds": 30.5
  }
}

Video

For video processing:
{
  "type": "video_seconds",
  "metrics": {
    "processed_seconds": 45.0
  }
}

Requests

For flat per-request pricing:
{
  "type": "requests",
  "metrics": {
    "count": 1
  }
}

Custom

For custom billing models:
{
  "type": "custom",
  "metrics": {
    "units": 3.5
  }
}

Bulk Transactions

Send multiple transactions in one request:
await fetch('https://api.fenra.io/ingest/usage', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': process.env.FENRA_API_KEY
  },
  body: JSON.stringify({
    transactions: [
      { provider: 'openai', model: 'gpt-4o', ... },
      { provider: 'anthropic', model: 'claude-3-5-sonnet', ... }
    ]
  })
});

Response Codes

CodeMeaning
202Transaction queued successfully
207Partial success (some failed in bulk)
400Validation error. Check request format.
401Invalid API key
500Server error. Safe to retry.

Next Steps