Tracking Usage Events
Every time your AI agent does work for a customer — answers a question, sends an SMS, generates a report — you tell MarginFront about it by sending a usage event. MarginFront figures out the cost, rolls everything up at the end of the billing period, and generates an invoice. Think of it like a utility meter. Each event is a meter reading. MarginFront is the utility company that turns those readings into a bill. This page covers:- How to install and set up the SDK
- What fields to send with every event
- Four real-world examples (copy-paste ready)
- Batch events, error handling, and retry behavior
Install the SDK
Initialize the client
.connect() call required for usage tracking.
What fields do I send?
Required for EVERY event
| Field | Type | What it is |
|---|---|---|
customerExternalId | string | Your customer’s ID in your system. Whatever you use to identify them — "acme-001", "cust_abc123", etc. |
agentCode | string | The code for the agent/product that did the work. You set this in the MarginFront dashboard when you create an agent. Example: "cs-bot-v2". |
signalName | string | The metric being tracked. Matches the signal you set up in the dashboard. Examples: "messages", "sms-sent", "report-pages". |
model | string | The model or service that did the work. Pass whatever your provider returns. Examples: "gpt-4o", "claude-sonnet-4", "twilio-sms". |
modelProvider | string | The provider name, always lowercase. This tells MarginFront which pricing table to look in. Examples: "openai", "anthropic", "twilio", "google", "aws". |
Recommended for LLM events
| Field | Type | What it is |
|---|---|---|
inputTokens | number | The number of prompt tokens (what you sent to the model). Must be 0 or greater. |
outputTokens | number | The number of completion tokens (what the model sent back). Must be 0 or greater. |
For variable-quantity billing
| Field | Type | Default | What it is |
|---|---|---|---|
quantity | number | 1 | How many units of work happened. Use this for per-page, per-minute, per-SMS billing. If you bill the same amount regardless of volume, you can omit this (it defaults to 1). |
Optional
| Field | Type | Default | What it is |
|---|---|---|---|
usageDate | string (ISO 8601) or Date | now | When the work actually happened. Only needed if you’re back-filling historical events. Example: "2026-04-10T14:30:00Z". |
metadata | object | {} | Free-form key-value pairs. MarginFront stores them but does NOT use them for billing or cost calculation. Use metadata for your own debugging, analytics, or audit trail. |
Example 1: LLM Event (the 90% case)
Use case: Your AI customer support bot answers a question using GPT-4o via the OpenAI SDK. You need to track the token usage so MarginFront can calculate cost and bill your customer. Where does the MarginFront call go? After the OpenAI response comes back, in the response handler. You need the token counts from the response, so you can’t send the event before the LLM responds.| OpenAI response field | MarginFront field |
|---|---|
response.model | model |
response.usage.prompt_tokens | inputTokens |
response.usage.completion_tokens | outputTokens |
Example 2: Non-LLM Discrete Event (quantity = 1)
Use case: Your agent sends a Twilio SMS on behalf of a customer. There are no tokens involved — it’s a simple “one SMS was sent” event. Where does the MarginFront call go? After Twilio confirms the SMS was sent. You only want to bill for messages that actually went out.inputTokens or outputTokens here. Those fields are only for LLM calls. For non-LLM services, cost is based on quantity and the pricing you set up in the dashboard.
Example 3: Variable-Quantity Event (quantity = N)
Before you read this example, the one rule that keeps your bill accurate: fire ONE event per business outcome, not one per page, minute, or token. A 50-page report is one event withUse case: Your agent generates a market research report for a customer. Reports vary in size — a 3-page report costs less than a 15-page report. You bill per page. This event has both token counts (because the report was generated by an LLM) and a quantity (because billing is based on pages, not tokens). The tokens track your cost from the LLM provider. The quantity tracks the output size for billing your customer.quantity: 50. A 3-minute call is one event withquantity: 3. Thequantityfield exists so you don’t have to loop. Looping would multiply your invoice and flood your analytics. See Choosing your signal name and quantity for the full mental model and a three-way comparison.
quantity: Any time the amount of work varies and you want billing to reflect that. Pages generated, minutes of audio transcribed, images produced, API calls batched — if the number changes per event, use quantity.
Example 4: Metadata
Use case: You want to attach debugging information to an event — which prompt template was used, which A/B test variant the customer saw, the conversation thread ID. This helps you analyze cost and performance later without affecting billing.- Strings, numbers, booleans, nested objects — any valid JSON.
- There is no schema. MarginFront stores whatever you send.
- Use it for audit trails, debugging, analytics, or linking events back to your own systems.
- It does not affect billing. A
customerTier: "enterprise"in metadata does not change the price. - It does not affect cost calculation. MarginFront ignores it completely for pricing.
- It does not appear on invoices.
Batch Events
When your agent does several things in quick succession (or you’re processing a queue), send them all in one request instead of one at a time. You can send 1 to 100 records per batch.Checking for partial failures
The API returns200 OK even when some records in the batch fail. Always check the response:
Fire-and-Forget Mode
By default,trackEvent() runs in fire-and-forget mode (for both single and batch shapes). This means:
- Your agent never blocks waiting for MarginFront. The call returns immediately.
- Network errors don’t crash your agent. If MarginFront is unreachable, the SDK puts the event into a retry buffer and tries again later.
- Validation errors log a warning and drop (they can’t be fixed by retrying).
How the retry buffer works
When a network error happens:- The failed event goes into an in-memory buffer (holds up to 1,000 events).
- The SDK retries the buffer on a backoff schedule: 10s, 20s, 40s, 60s (caps at 60s).
- Each event gets 5 retry attempts. After 5 failures, it’s dropped with a warning.
- When a retry succeeds, the backoff resets to 10s.
- If the buffer is full (1,000 events), the oldest event is dropped to make room.
Turning off fire-and-forget
If you want to handle errors yourself (for example, to log them to your own monitoring system), turn off fire-and-forget when you create the client:Field Reference
Required for every event
| Field | Type | Description |
|---|---|---|
customerExternalId | string | Your customer’s ID in your system. Must match the externalId you set when creating the customer in MarginFront (or via auto-provisioning). |
agentCode | string | The agent code from the MarginFront dashboard. Identifies which product/agent did the work. |
signalName | string | The name of the metric being tracked. Matches the signal you configured in the dashboard. |
model | string | The model or service identifier. Pass whatever your provider returns (e.g., response.model from OpenAI). Case-insensitive. |
modelProvider | string | The provider name, always lowercase. Tells MarginFront which pricing table to look up. |
Recommended for LLM events
| Field | Type | Description |
|---|---|---|
inputTokens | number | Number of prompt/input tokens. Must be >= 0. Required for MarginFront to calculate LLM cost. |
outputTokens | number | Number of completion/output tokens. Must be >= 0. Required for MarginFront to calculate LLM cost. |
For variable-quantity billing
| Field | Type | Default | Description |
|---|---|---|---|
quantity | number | 1 | Number of billing units. Use for per-page, per-minute, per-image, per-SMS billing. Must be >= 0. |
Optional
| Field | Type | Default | Description |
|---|---|---|---|
usageDate | string (ISO 8601) or Date | now | When the work happened. Use for back-filling historical events. |
metadata | object | {} | Free-form key-value pairs. Stored but NOT used for billing or cost calculation. |
What happens after you send an event
Your agents can always fire events. MarginFront stores every event it receives, even when it can’t figure out the cost yet. The response tells you which of three things happened so you know whether any follow-up action is needed. Think of it like dropping off a package at the post office. The package is always accepted. Sometimes the label is complete and it ships right away. Sometimes a piece of info is missing and it waits in a holding bin until you fill in the blank. Nothing gets thrown out.The three states
| State | Event saved? | Cost calculated? | What it means |
|---|---|---|---|
PROCESSED | Yes | Yes | The event was received, the model was recognized, and the cost was calculated. Nothing for you to do. |
MISSING_VOLUME_DATA | Yes | No (cost is null) | The event was received, but it didn’t include the token counts or quantity needed to price it. Cost stays blank until you backfill the missing numbers. |
NEEDS_COST_BACKFILL | Yes | No (cost is null) | The event was received, but the model + modelProvider you sent isn’t in MarginFront’s pricing table yet. Cost stays blank until you map that model to a known one. |
How to fix each non-PROCESSED state
Both fixes are one-time dashboard actions (or a single API call each). After you apply them, MarginFront retroactively recalculates cost for every affected event that was parked in that state.
If the event came back as MISSING_VOLUME_DATA:
- In the dashboard, go to the “Needs attention” section under Usage Events and fill in the missing token counts or quantity for the flagged events.
- Or call
POST /v1/events/fill-volumewith the event ID and the correct volume numbers.
NEEDS_COST_BACKFILL:
- In the dashboard, go to the “Needs attention” section under Usage Events, find the unrecognized model, and map it to a known one.
- Or call
POST /v1/events/map-modelwith the model name and the service it should be priced as.
PROCESSED immediately. No manual step needed again.
Do NOT retry events that were stored. They’re already in the database. Retrying would create duplicates. The fix happens on MarginFront’s side, not by resending the event.
Error Handling
The API returns 200 even when events fail
This is intentional. A batch of 10 events might have 9 successes and 1 failure. A200 tells you the request was received. The response body tells you what actually happened.
Always check failed > 0 in the response:
NEEDS_COST_BACKFILL — what it means
If you send amodel + modelProvider combination that MarginFront doesn’t recognize (not in the pricing table), this happens:
- The event is saved with
cost = null. It is NOT lost. - The response includes the event in
results.failedwith the codeNEEDS_COST_BACKFILLandstored: true. - In the MarginFront dashboard, go to the “Needs attention” section under Usage Events.
- Map the unrecognized model to a known one. MarginFront creates a permanent mapping and retroactively calculates cost for every event that used that model.
- Future events with that model auto-resolve. No manual step needed again.
stored: true. They are already saved. Retrying would create duplicates.
The “never break the core product” pattern
Your agent exists to serve your customers. MarginFront exists to bill for that work. If billing fails, the customer should still get served. Always wrap usage tracking in a try/catch:fireAndForget: true (the default) and the SDK handles this pattern for you automatically. The call never throws, never blocks, and retries in the background.
Common error codes
| Code | Event saved? | What happened | What to do |
|---|---|---|---|
NEEDS_COST_BACKFILL | Yes | The model+provider isn’t in the pricing table. Event saved with cost = null. | Do NOT retry. Map the model in the dashboard or call POST /v1/events/map-model. Cost backfills automatically. |
MISSING_VOLUME_DATA | Yes | The event is missing token counts or quantity needed to price it. Event saved with cost = null. | Do NOT retry. Fill in the missing numbers in the dashboard or call POST /v1/events/fill-volume. Cost backfills automatically. |
INTERNAL_ERROR | No | Something broke on MarginFront’s side. | Safe to retry. |
VALIDATION_ERROR | No | A required field is missing or has the wrong type. | Fix the data and resend. Check the error message for which field. |
Quick checklist
Before you ship, make sure:- The SDK is initialized with your secret key (
mf_sk_*), not the publishable key -
customerExternalIdmatches the external ID you set up for each customer -
agentCodematches the agent code shown in the dashboard -
signalNamematches the signal name shown in the dashboard - For LLM events, you’re sending
inputTokensandoutputTokensfrom the provider response - The
mf.trackEvent()call is after the work is done (after the LLM responds, after the SMS sends) - The call is wrapped in try/catch (or
fireAndForgetis ON, which is the default) - You’re not retrying events that came back with
stored: true

