Every AI API call can be tracked as a transaction.
Basic Flow
- Make your AI provider call
- Extract usage from the response
- 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:
| Field | Required | Description |
|---|
provider | Yes | openai, anthropic, gemini, bedrock, xai, deepseek, custom |
model | Yes | Model identifier (e.g., gpt-4o) |
usage | Yes | What resources the AI call consumed (tokens, images, audio, etc.) |
context.billable_customer_id | Yes | Your 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:
| Field | Description |
|---|
cached_tokens | Tokens served from provider cache |
cache_read_input_tokens | Tokens read from cache |
cache_creation_input_tokens | Tokens used to create cache |
reasoning_tokens | Tokens for reasoning (o1, o3 models) |
thinking_tokens | Alias 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
| Code | Meaning |
|---|
202 | Transaction queued successfully |
207 | Partial success (some failed in bulk) |
400 | Validation error. Check request format. |
401 | Invalid API key |
500 | Server error. Safe to retry. |
Next Steps