Skip to main content

Integrate MarginFront with OpenAI

This recipe shows how to add MarginFront usage tracking to an Express.js app that calls OpenAI. By the end, every OpenAI call your agent makes will automatically show up in your MarginFront dashboard with cost and usage data. What this does for you: You call OpenAI. MarginFront records what model was used, how many tokens were consumed, and which customer it was for. You see the cost on your dashboard. If you have a pricing plan set up, your customer gets billed automatically.

Prerequisites

  1. A MarginFront API key (mf_sk_...). Get one from Developer Zone > API Keys in the dashboard.
  2. An OpenAI API key.
  3. An agent and signal already created in the dashboard (or they’ll be auto-created on first event — see note below).
npm install @marginfront/sdk openai express

Complete working example

This is a full Express.js endpoint you can copy-paste and run. It receives a question from a customer, asks OpenAI for an answer, sends the answer back, and then tells MarginFront what happened.
import express from "express";
import OpenAI from "openai";
import { MarginFrontClient } from "@marginfront/sdk";

const app = express();
app.use(express.json());

// Set up clients once at startup (not per-request)
const openai = new OpenAI(); // reads OPENAI_API_KEY from environment
const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY!);

app.post("/chat", async (req, res) => {
  const { customerId, message } = req.body;

  // --- Step 1: Call OpenAI ---
  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: message }],
  });

  // --- Step 2: Send the answer to your customer ---
  const answer = response.choices[0].message.content;
  res.json({ answer });

  // --- Step 3: Track the usage in MarginFront (AFTER responding) ---
  // This runs after the response is sent. If MarginFront is unreachable,
  // the SDK queues the event locally and retries later. Your customer
  // never waits for MarginFront.
  await mf.usage.record({
    customerExternalId: customerId, // who used it (your customer's ID)
    agentCode: "cs-bot", // which product did the work
    signalName: "messages", // what you're measuring
    model: response.model, // 'gpt-4o' -- straight from OpenAI's response
    modelProvider: "openai", // tells MarginFront which pricing table to check
    inputTokens: response.usage!.prompt_tokens, // from OpenAI's response
    outputTokens: response.usage!.completion_tokens, // from OpenAI's response
  });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

Where the data comes from: field mapping

Every value you pass to MarginFront comes directly from OpenAI’s response object. Here’s where each one lives:
What MarginFront needsWhere to get it from OpenAIExample value
modelresponse.model"gpt-4o"
inputTokensresponse.usage.prompt_tokens523
outputTokensresponse.usage.completion_tokens117
The remaining fields (customerExternalId, agentCode, signalName, modelProvider) come from your own code, not from OpenAI.

What happens if MarginFront is down?

Nothing bad. The SDK runs in fire-and-forget mode by default:
  • MarginFront unreachable? The event goes into a local retry buffer. The SDK retries automatically in the background. Your customer never notices.
  • MarginFront rejects the event? (e.g., validation error) The SDK logs a warning to the console. The event is dropped — but your customer still got their answer.
  • Your server crashes before the event is sent? That one event is lost. This is rare and acceptable for most use cases. If you need guaranteed delivery, set fireAndForget: false and handle errors yourself.
The important thing: MarginFront never blocks your agent. Your customer always gets their answer, whether MarginFront is having a good day or a bad one.

Error handling (if you want more control)

The default fire-and-forget mode is fine for most apps. But if you want to know when tracking fails (for logging, alerting, etc.), turn it off:
// Opt out of fire-and-forget mode
const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY!, {
  fireAndForget: false,
});

// Now you can catch errors
try {
  await mf.usage.record({
    customerExternalId: customerId,
    agentCode: "cs-bot",
    signalName: "messages",
    model: response.model,
    modelProvider: "openai",
    inputTokens: response.usage!.prompt_tokens,
    outputTokens: response.usage!.completion_tokens,
  });
} catch (error) {
  // Log it, alert on it, whatever you need.
  // But don't let it crash your request handler.
  console.error("MarginFront tracking failed:", error);
}

Streaming responses

If you use OpenAI’s streaming mode (stream: true), you won’t get token counts in the stream chunks. You need to wait for the stream to finish and read the usage field from the final response:
const stream = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: message }],
  stream: true,
  stream_options: { include_usage: true }, // required to get token counts
});

let fullContent = "";
let finalUsage: OpenAI.CompletionUsage | undefined;

for await (const chunk of stream) {
  // Send each chunk to the customer as it arrives
  const text = chunk.choices[0]?.delta?.content || "";
  fullContent += text;

  // The very last chunk has the usage data
  if (chunk.usage) {
    finalUsage = chunk.usage;
  }
}

// Now track the event with the final token counts
if (finalUsage) {
  await mf.usage.record({
    customerExternalId: customerId,
    agentCode: "cs-bot",
    signalName: "messages",
    model: "gpt-4o",
    modelProvider: "openai",
    inputTokens: finalUsage.prompt_tokens,
    outputTokens: finalUsage.completion_tokens,
  });
}
Important: You must pass stream_options: { include_usage: true } or OpenAI won’t include token counts in the streamed response.

Multiple models in one agent

If your agent uses different models for different tasks (e.g., GPT-4o for complex questions, GPT-4o-mini for simple ones), just pass whichever model was actually used. MarginFront looks up the cost per model automatically:
// The model might change based on the task
const modelToUse = isSimpleQuestion ? "gpt-4o-mini" : "gpt-4o";

const response = await openai.chat.completions.create({
  model: modelToUse,
  messages: [{ role: "user", content: message }],
});

// Pass response.model -- MarginFront handles the cost lookup
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "cs-bot",
  signalName: "messages",
  model: response.model, // could be 'gpt-4o' or 'gpt-4o-mini'
  modelProvider: "openai",
  inputTokens: response.usage!.prompt_tokens,
  outputTokens: response.usage!.completion_tokens,
});

Auto-provisioning note

If you log an event for a customerExternalId or agentCode that MarginFront hasn’t seen before, it will auto-create a minimal customer or agent record. This is handy for prototyping but can mask typos — if events show up under a customer name you don’t recognize, double-check your IDs in the dashboard.