Skip to main content

Integrate MarginFront with Non-LLM Tools

Not everything your agent does is an LLM call. Your agent might send SMS messages via Twilio, scrape web pages, generate PDFs, send emails, process images, or transcribe audio. MarginFront tracks all of it through the same event endpoint. What this does for you: Every tool your agent uses — LLM or not — shows up in one dashboard. You see the full cost picture, not just the AI part.

How non-LLM events differ from LLM events

Same endpoint (POST /v1/usage/record), same SDK method (mf.usage.record()). The difference is which fields carry the “how much” information:
Event type”How much” fieldExample
LLMinputTokens + outputTokens523 input tokens, 117 output tokens
Non-LLMquantity1 SMS sent, 12 pages scraped
For non-LLM events, model and modelProvider are descriptive labels you choose. Use something readable — they’ll show up in your dashboard and analytics. inputTokens and outputTokens are always optional; leave them out for non-LLM work.

Prerequisites

npm install @marginfront/sdk
import { MarginFrontClient } from "@marginfront/sdk";

// One client, reused for all events (LLM and non-LLM)
const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY!);

Example 1: Twilio SMS

Your agent sends a text message to a customer’s phone number. You want to track each SMS as one unit of usage.
import twilio from "twilio";

const twilioClient = twilio(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN,
);

// Send the SMS first
const smsResult = await twilioClient.messages.create({
  body: "Your order has shipped! Tracking: ABC123",
  from: process.env.TWILIO_PHONE_NUMBER,
  to: customerPhone,
});

// Then tell MarginFront about it
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "notification-bot",
  signalName: "sms-sent",
  model: "sms-send", // descriptive label -- you pick this
  modelProvider: "twilio", // who provided the service
  quantity: 1, // one SMS was sent
});
Why model: 'sms-send'? For non-LLM services, model is just a label that helps you identify what happened when you look at the dashboard later. Pick something readable. Other good options: 'outbound-sms', 'sms-notification', 'transactional-sms'.

Example 2: Web scraping (variable quantity)

Your research agent scrapes a website and returns multiple pages of content. The number of pages varies per job, so quantity changes each time.
// Scrape the target URL
const pages = await scraper.scrape(targetUrl);

// Process the scraped content (whatever your agent does with it)
const summary = await processPages(pages);

// Track the usage -- quantity is how many pages were scraped
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "research-bot",
  signalName: "pages-scraped",
  model: "web-scraper", // descriptive label for the tool
  modelProvider: "internal", // "internal" works for tools you built yourself
  quantity: pages.length, // could be 3, could be 50 -- depends on the site
});
Why modelProvider: 'internal'? If the tool is something you built (not a third-party service), use 'internal' as the provider. MarginFront won’t find it in any pricing table, so the cost will be null — which is fine. You’re tracking the signal quantity for billing purposes, not calculating LLM costs.

Example 3: PDF generation

Your reporting agent generates a multi-page PDF for a customer. You bill by the number of pages in the report.
// Generate the PDF
const pdf = await generateReport(customerData);

// Send it to the customer
res.setHeader("Content-Type", "application/pdf");
res.send(pdf.buffer);

// Track the usage
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "report-bot",
  signalName: "report-pages",
  model: "pdf-generator", // descriptive label
  modelProvider: "internal", // you built this tool
  quantity: pdf.pageCount, // 1-page report costs less than a 20-page report
});

Example 4: Email sending (via Resend, SendGrid, etc.)

import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

await resend.emails.send({
  from: "agent@yourapp.com",
  to: recipientEmail,
  subject: "Your weekly report",
  html: reportHtml,
});

await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "notification-bot",
  signalName: "emails-sent",
  model: "transactional-email",
  modelProvider: "resend",
  quantity: 1,
});

Example 5: Mixing LLM and non-LLM in one workflow

Real agents often use an LLM to generate content and then a non-LLM tool to deliver it. Track both:
// Step 1: Use Claude to write the report
const llmResponse = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  max_tokens: 4096,
  messages: [{ role: "user", content: `Write a market report for ${topic}` }],
});

// Track the LLM usage (tokens)
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "report-bot",
  signalName: "analyses",
  model: llmResponse.model,
  modelProvider: "anthropic",
  inputTokens: llmResponse.usage.input_tokens,
  outputTokens: llmResponse.usage.output_tokens,
});

// Step 2: Convert the report to PDF
const reportText =
  llmResponse.content[0].type === "text" ? llmResponse.content[0].text : "";
const pdf = await generatePdf(reportText);

// Track the PDF generation (quantity)
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "report-bot",
  signalName: "report-pages",
  model: "pdf-generator",
  modelProvider: "internal",
  quantity: pdf.pageCount,
});
Both events show up in your dashboard. The LLM event has a calculated cost (from MarginFront’s pricing table). The PDF event has cost = null (because 'pdf-generator' isn’t in any pricing table), but the quantity is tracked for billing through your pricing plan.

What you pick vs what MarginFront calculates

For non-LLM events, here’s what’s yours to define and what MarginFront handles:
FieldYou set itMarginFront calculates it
modelYes — pick a descriptive labelNo
modelProviderYes — the service name or 'internal'No
quantityYes — how many units of workNo
inputTokensOptional (leave out for non-LLM)No
outputTokensOptional (leave out for non-LLM)No
Service costNoYes, if model+provider is in the pricing table. Otherwise null.
RevenueNoYes, from pricing plan: quantity x price_per_unit

Including tokens AND quantity

Some events straddle both worlds. For example, an image generation call uses tokens (for the prompt) but also produces a quantity (number of images):
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "creative-bot",
  signalName: "images-generated",
  model: "dall-e-3",
  modelProvider: "openai",
  quantity: 4, // four images generated
  inputTokens: 85, // tokens used in the prompt (optional)
  // no outputTokens for image generation
});
All three fields (quantity, inputTokens, outputTokens) are always optional. Use whichever ones are relevant to the work that happened.

Cost tracking for non-LLM tools

MarginFront’s built-in pricing table covers 300+ LLM models. For non-LLM tools like Twilio, your PDF generator, or your web scraper, the model+provider won’t be in the table. That’s fine:
  • The event saves with cost = null (never dropped, never zero).
  • Revenue is still calculated from your pricing plan (quantity x price_per_unit).
  • If you want to track the actual cost of a non-LLM tool (e.g., Twilio charges $0.0079 per SMS), you can map the model in the dashboard to a custom pricing entry. But this is optional — many users only care about the revenue side for non-LLM tools.

Fire-and-forget still applies

Just like with LLM events, the SDK’s default fire-and-forget mode means:
  • If MarginFront is down, events retry automatically from a local buffer.
  • If there’s a validation error, the SDK logs a warning and moves on.
  • Your agent never stalls waiting for MarginFront.
This is especially important for non-LLM tools where the work is already done (SMS already sent, PDF already generated) — there’s no point blocking on the tracking call.