# MarginFront: Complete Reference > Usage-based billing for AI agent companies. Track what your agents do, what they cost to run, and bill your customers accordingly. - Website: https://marginfront.com - Dashboard: https://app.marginfront.com - npm: https://www.npmjs.com/package/@marginfront/sdk - Docs: https://docs.marginfront.com - MCP Reference: https://marginfront.com/llms-mcp.txt - Sitemap (full page index): https://docs.marginfront.com/sitemap.xml ## What MarginFront Does MarginFront sits between your AI agent and your customers. When your agent does work (generates a report, drafts an email, answers a support ticket), you send MarginFront an event. MarginFront tracks the volume, calculates your cost to deliver (using a built-in pricing table for 300+ LLM models), and handles billing. ## Auto-Provisioning (Read This First) The MarginFront API auto-provisions records on first sight. When a usage event is fired with a `customerExternalId`, `agentCode`, or `signalName` that has not been seen before: - The customer is created with `name` defaulting to the `customerExternalId` value. - The agent is created with `name` defaulting to the `agentCode` value. - The signal is created with `shortName` defaulting to the `signalName` value. This means a developer integrating MarginFront does not need to walk a "create customer, create agent, create signal" sequence in advance. One `POST /v1/usage/record` call (or one `mf.usage.record(...)` call) is enough to ingest the event AND create the three records. The dashboard reflects the new records immediately. Display names can be renamed later in the dashboard or via the corresponding API endpoints. What does NOT auto-provision: pricing plans, pricing strategies, subscriptions, invoices, team members. Those are explicit and require dedicated API calls or dashboard actions. ## Catalog Discovery (Recommended Before First Event) MarginFront's `service_pricing` catalog has 1100+ entries across LLM and non-LLM services. To avoid the `NEEDS_COST_BACKFILL` cycle, list the catalog and use a canonical name your code can send as `model` + `modelProvider`. Three ways to query: - REST: `GET /v1/services?provider=&serviceType=&search=` - SDK: `await client.services.list({ provider: 'google' })` - MCP tool: `list_catalog_services` (see llms-mcp.txt) Categories covered: LLM (~990), Embeddings (~70), Speech-to-Text (~47), Image Generation, Text-to-Speech, Reranking, Web Search, Geocoding, Compute (Cloud Run), Web Scraping, Email, Maps, Document Processing, Data Enrichment, SMS, Voice, Code Execution, Vector Database. The exact list lives at `/v1/services` and at `/api-reference/supported-services`. If a service is in the catalog, fire events with `model: ''` and `modelProvider: ''` (both lowercase, both from the catalog row). Cost auto-resolves on ingest. If a service is not in the catalog, two options: 1. Map an existing entry via `POST /v1/events/map-model` (only redirects to existing entries; cannot create new rates). 2. Email `team@marginfront.com` with the service name and the provider's pricing page; the catalog gets updated. In both cases, events still land. Cost stays null until the catalog catches up; `map_model` backfills retroactively once the mapping is in place. ## Integration Paths Three integration paths, all hitting the same backend: - SDK (Node.js / TypeScript): typed function calls (`mf.usage.record(...)`) from a customer's product code. Handles batching, retries, fire-and-forget. This is the default integration path. - REST API: raw HTTP from any language. Same auto-provision behavior, same response shape. Use when the customer's stack is not Node. - MCP: an operator interface for human-to-LLM ops on a MarginFront account (backfills, batch fixes, queries, admin actions). MCP is NOT the integration path for instrumenting product code; it runs through an LLM and is built for conversational use. See llms-mcp.txt for the full MCP tool catalog. ## Core Concepts - AGENT: A product or service that does work. Has an `agentCode`. Auto-provisioned on first event with a new `agentCode`. - CUSTOMER: The end-user or company being billed. Has an `externalId` matching the user ID in the developer's own system. Auto-provisioned on first event with a new `customerExternalId`. - SIGNAL: The billing unit being tracked. This is what customers see on invoices. Good signals: "messages", "reports-generated", "pages-processed". Bad signals: "gpt-4o-call" (that is an internal cost detail, not a deliverable). Auto-provisioned on first event with a new `signalName`. - EVENT: A record that agent X did Y quantity of signal Z for customer W. Events carry model/provider info so MarginFront can calculate cost, plus optional quantity and metadata. - SERVICE COSTS: Calculated automatically from the model + provider + tokens sent in each event. Customers never see this number. The developer sees it on the margin dashboard. - PRICING PLAN: Turns service cost into a price. A container for one or more pricing strategies. Created explicitly via `POST /v1/pricing-plans`. Does NOT auto-provision. - PRICING STRATEGY: The actual math rule inside a plan. Has a charge type (when to bill) and a pricing model (how to calculate). Does NOT auto-provision. - SUBSCRIPTION: Ties a customer to an agent via a pricing plan. Defines billing cycle. Created explicitly via `POST /v1/subscriptions`. Does NOT auto-provision. - INVOICE: Auto-generated from subscriptions. Shows what is charged to the customer. - API KEY: Starts with `mf_sk_` (secret, server-side only). Identifies the organization. No org ID is needed in requests. ## Pricing Strategy Types Charge types (WHEN the customer gets billed): - usage: every time an event comes in. Priced per unit of a signal. - recurring: once per billing cycle (flat monthly/yearly fee). - seat_based: once per billing cycle, priced by seatsCount on the subscription. - onetime: once, on the subscription start date. Pricing models (HOW the number is calculated, for usage strategies): - flat: quantity times rate. - graduated: tiered with progressive rates. Each unit pays the rate for its tier. - volume: tiered with one rate for all units. All units pay the rate of the tier the TOTAL quantity landed in. - credit_pool: flat fee for a pool of units, per-unit overage after the pool is used up. Per-window-aggregate billing: usage strategies bill on the TOTAL quantity for the window, not per event. Three events with quantity=500 bill identically to one event with quantity=1500. ## The Four Labels of Revenue MarginFront reports four distinct dollar amounts for "revenue," each answering a different question. They are NOT the same number. - Agent-Earned: pure activity. Events times pricing strategy rates. No fees, no proration. - Revenue: Agent-Earned plus prorated recurring, seat, and onetime fees. "What the customer owed for this window." - Billed: sum of invoices dated in the window with status issued or overdue. Draft and paid don't count here. - Collected: sum of invoices dated in the window with status paid. Cash landed. Each label has its own margin: margin = label minus cost. ## Choosing a Signal Name (The Billing-Unit Rule) Before writing any integration code, the developer must pick a signal name that matches how they want to bill. This is the single most important setup decision. Four rules cover it: 1. Fire ONE event per business outcome. One report finishing = 1 event. One call ending = 1 event. Do NOT loop one event per page, per minute, or per token. The quantity field exists so you don't have to. 2. The signal name IS the billing unit. Bill per page, name the signal "pages". Bill per report, name it "reports". Bill per minute, name it "minutes". Whatever unit appears on the customer's invoice is the signal name. 3. quantity is the count of that billing unit for this one event. A 50-page report fired as signal "pages" has quantity=50. Fired as signal "reports", quantity=1. Same underlying LLM call, same token cost, different invoice. 4. Cost and revenue are decoupled. MarginFront calculates cost automatically from model+provider+tokens. Revenue = quantity times pricing-plan rate. The gap is margin. Example: the SAME Claude call that produced a 50-page market research report can be billed three different ways. - signalName="reports_generated", quantity=1, rate=$50/report produces invoice line: "1 report x $50.00 = $50.00" - signalName="report_pages", quantity=50, rate=$2/page produces invoice line: "50 pages x $2.00 = $100.00" - signalName="tokens_used", quantity=15000, rate=$0.01 per 1K produces invoice line: "15,000 tokens x $0.01 = $0.15" Same Claude call. Same underlying cost. Three different invoices. The choice of signal name is the contract between "the agent did work" and "the customer gets billed this much for that work." Warning: the single easiest thing to get wrong is firing one event per page. If the report is 50 pages, fire ONE event with quantity=50, not 50 events with quantity=1. Looping would multiply the invoice, flood analytics, and burn API calls. Same applies to minutes of audio, images in a batch, messages in a thread. Bad signal names (internal implementation details the customer shouldn't see): "gpt-4o-call", "api-requests", "llm_call", "tokens". Good signal names (what the customer is actually paying for): "messages", "reports", "pages", "minutes", "emails-sent", "reports-generated". ## Setup Sequence For cost tracking only (the minimum, recommended starting point): 1. Get a secret API key (`mf_sk_*`) from the dashboard's Developer Zone. 2. Fire one usage event via SDK or REST API with `customerExternalId`, `agentCode`, `signalName`, `model`, and `modelProvider`. The customer, agent, and signal are auto-provisioned on this call. 3. Confirm the event landed by reading it back via the dashboard or `GET /v1/analytics/usage`. That is it. No "create customer / create agent / create signal" sequence. The first event creates the records. For revenue tracking and invoicing, add these explicit steps after cost tracking is working: 4. Create a pricing plan via `POST /v1/pricing-plans`. 5. Add pricing strategies inside the plan via `POST /v1/pricing-plans/:planId/pricing-strategies`. 6. Link the plan to the agent via `POST /v1/pricing-plans/:planId/agents`. 7. Create a subscription tying the customer to the plan via `POST /v1/subscriptions`. Without steps 4-7, MarginFront tracks cost but cannot calculate revenue or generate invoices. Add them whenever the developer is ready to bill. ## SDK Integration (Node.js / TypeScript) ### Install ```bash npm install @marginfront/sdk ``` ### Initialize ```typescript import { MarginFrontClient } from '@marginfront/sdk'; const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY); ``` ### Example 1: LLM Event (the 90% case) The agent calls an LLM and the developer wants to track cost. Send the model name, provider, and token counts. If `customerExternalId`, `agentCode`, or `signalName` are new strings, the records auto-provision on this call. ```typescript // After the OpenAI call completes... const response = await openai.chat.completions.create({ ... }); await mf.usage.record({ customerExternalId: '', // required. Whatever ID your system uses for this user. agentCode: '', // required. A stable name for this agent (e.g. "report_writer"). signalName: '', // required. What the customer pays for (e.g. "reports-generated"). model: 'gpt-4o', // required. Which model was used. modelProvider: 'openai', // required. Who runs the model. Lowercase. inputTokens: response.usage.prompt_tokens, // recommended for LLM cost calc outputTokens: response.usage.completion_tokens, // recommended for LLM cost calc }); ``` ### Example 2: Non-LLM Discrete Event (one task = one event) Your agent sends an SMS, makes an API call, or does one unit of work that isn't an LLM call. ```typescript await mf.usage.record({ customerExternalId: 'acme-001', agentCode: 'cs-bot', signalName: 'sms-sent', model: 'sms-send', // descriptive name for non-LLM services modelProvider: 'twilio', quantity: 1, // defaults to 1 if omitted, but explicit is clearer }); ``` ### Example 3: Variable-Quantity Event (output varies in size) Your agent generates a report, but reports vary in size. A 3-page report costs you less than a 15-page report, and you might bill per page. ```typescript await mf.usage.record({ customerExternalId: 'acme-001', agentCode: 'research-bot', signalName: 'report-pages', model: 'gpt-4o', modelProvider: 'openai', inputTokens: 12500, outputTokens: 8200, quantity: 15, // 15 pages. This drives per-unit billing. }); ``` ### Example 4: Metadata (extra context, not used for billing) Attach debugging info, A/B test variants, or any context you want searchable later. MarginFront stores metadata but does NOT use it for cost calculation or billing. ```typescript await mf.usage.record({ customerExternalId: 'acme-001', agentCode: 'cs-bot', signalName: 'messages', model: 'gpt-4o', modelProvider: 'openai', inputTokens: 523, outputTokens: 117, metadata: { promptTemplate: 'v3-concise', abVariant: 'treatment-b', conversationId: 'conv_abc123', customerTier: 'pro', }, }); ``` ### Example 5: Multi-Service Event (one outcome, multiple services) Real agents often combine several services to produce one outcome. A cold-outreach report uses a search API, an LLM, and an email send. Record it as ONE event with a `services[]` array. The customer is billed once per signal at your pricing plan rate; MarginFront tracks per-service cost under the hood. ```typescript await mf.usage.record({ customerExternalId: 'acme-001', agentCode: 'outreach-bot', signalName: 'prospect_reports', quantity: 1, // one report (signal-level count) services: [ { model: 'exa-search', modelProvider: 'exa', quantity: 12 }, { model: 'claude-opus-4-20250514', modelProvider: 'anthropic', inputTokens: 4200, outputTokens: 1800 }, { model: 'sendgrid-email', modelProvider: 'sendgrid', quantity: 3 }, ], }); ``` Two shapes, mutually exclusive: send `model` + `modelProvider` at the top level (single-service), OR send `services[]` (multi-service). Never both, never neither. Top-level `quantity` is signal-level (how many reports/outreaches/etc); per-service volume lives inside each `services[]` entry. ### Batch Events (1-100 per call) ```typescript await mf.usage.recordBatch({ records: [ { customerExternalId: 'acme-001', agentCode: 'cs-bot', signalName: 'messages', model: 'gpt-4o', modelProvider: 'openai', inputTokens: 500, outputTokens: 100 }, { customerExternalId: 'acme-001', agentCode: 'cs-bot', signalName: 'sms-sent', model: 'sms-send', modelProvider: 'twilio', quantity: 1 }, ], }); ``` ### Fire-and-Forget Mode (default: ON) The SDK defaults to fire-and-forget. Events send in the background. Your agent never waits for MarginFront. If MarginFront is unreachable, events queue in a local retry buffer (up to 1,000 events, 5 retries each with exponential backoff). Your agent keeps running no matter what. Turn it off if you want to handle errors yourself: ```typescript const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY, { fireAndForget: false, // now usage.record() throws on errors }); ``` ### Canonical Analytics (SDK) The SDK exposes eight canonical analytics methods on `mf.analytics`. Each returns typed, reconciled numbers from the same library the dashboard uses. ```typescript // Revenue (full formula: Agent-Earned plus fees) const rev = await mf.analytics.revenue({ startDate: '2026-04-01', endDate: '2026-04-30' }); // Cost breakdown (total plus byAgent/byCustomer/bySignal/byDay/byPlan/byModel) const cost = await mf.analytics.costBreakdown({ startDate: '2026-04-01', endDate: '2026-04-30' }); // Invoice totals (billed + collected) const inv = await mf.analytics.invoiceTotals({ startDate: '2026-04-01', endDate: '2026-04-30' }); // MRR variants const mrr = await mf.analytics.mrr(); // canonical: last complete calendar month billed const rr = await mf.analytics.runRateMrr(); // trajectory from last 30 days const cm = await mf.analytics.committedMrr(); // contractual floor // Agent-Earned (pure activity, pre-invoice) const earned = await mf.analytics.agentEarned({ startDate: '2026-04-01', endDate: '2026-04-30' }); // Legacy usage analytics (still supported) const usage = await mf.analytics.usage({ startDate: '2026-04-01', endDate: '2026-04-30', groupBy: 'day' }); ``` ### Service Catalog Discovery (SDK 0.12.0+) Browse the global service_pricing catalog from the SDK to discover canonical model + provider names BEFORE firing events. Same data that powers the dashboard /catalog page and the MCP list_catalog_services tool. ```typescript // List with filters const { data, pagination } = await mf.services.list({ provider: 'google', // lowercase; e.g. 'openai', 'anthropic', 'twilio' serviceType: 'Compute', // e.g. 'LLM', 'Geocoding', 'Web Search' isApi: true, // true = non-LLM API only; false = LLM only; omit for both search: 'cloud-run', // case-insensitive ilike across canonicalName + displayName page: 1, limit: 50, }); data.forEach(svc => { console.log(svc.canonicalName, svc.provider, svc.serviceType, svc.inputCost, svc.costUnit); }); // Single entry by UUID const entry = await mf.services.get('df3c1acd-fdc4-4f6c-9d84-99697ffb76a2'); console.log(`Send '${entry.canonicalName}' as model with provider '${entry.provider}'`); ``` Returns the same shape as GET /v1/services. Read-only; the catalog is sync-script-managed. ### CLI (Testing) ```bash npm install -g @marginfront/sdk mf verify --api-key mf_sk_your_key mf track-event --customer-id acme-001 --agent-code cs-bot --signal messages --model gpt-4o --provider openai --input-tokens 500 --output-tokens 100 ``` ## REST API Base URL: https://api.marginfront.com/v1 Auth: x-api-key header with your secret key (NOT Authorization: Bearer) ### Track Usage Event ```bash curl -X POST https://api.marginfront.com/v1/usage/record \ -H "x-api-key: mf_sk_your_key" \ -H "Content-Type: application/json" \ -d '{ "records": [{ "customerExternalId": "acme-001", "agentCode": "cs-bot", "signalName": "messages", "model": "gpt-4o", "modelProvider": "openai", "inputTokens": 523, "outputTokens": 117 }] }' ``` ### Full Endpoint List Usage Events: POST /v1/usage/record: track one or more usage events (batch: wrap in records[] array) Customers: POST /v1/customers: create customer GET /v1/customers: list customers (paginated, searchable) GET /v1/customers/:id: get customer by ID PATCH /v1/customers/:id: update customer DELETE /v1/customers/:id: delete customer Agents: POST /v1/agents: create agent GET /v1/agents: list agents GET /v1/agents/:id: get agent by ID PATCH /v1/agents/:id: update agent DELETE /v1/agents/:id: delete agent Signals: POST /v1/signals: create signal POST /v1/signals/bulk: create multiple signals at once GET /v1/signals: list signals GET /v1/signals/:id: get signal by ID PATCH /v1/signals/:id: update signal DELETE /v1/signals/:id: delete signal Services Catalog (read-only; the catalog of model + provider combos MarginFront recognizes for cost): GET /v1/services: list catalog entries (paginated; filterable by provider, serviceType, isApi, search) GET /v1/services/:id: get a single catalog entry by UUID Pricing Plans: POST /v1/pricing-plans: create pricing plan GET /v1/pricing-plans: list pricing plans GET /v1/pricing-plans/:id: get pricing plan by ID PATCH /v1/pricing-plans/:id: update pricing plan DELETE /v1/pricing-plans/:id: delete pricing plan POST /v1/pricing-plans/:id/copy: duplicate a pricing plan POST /v1/pricing-plans/:id/agents: link plan to agent DELETE /v1/pricing-plans/:id/agents/:agentId: unlink plan from agent Pricing Strategies (nested under pricing plans): POST /v1/pricing-plans/:planId/pricing-strategies: bulk create strategies (body is array) GET /v1/pricing-plans/:planId/pricing-strategies: list strategies for a plan GET /v1/pricing-plans/:planId/pricing-strategies/:id: get strategy by ID PATCH /v1/pricing-plans/:planId/pricing-strategies/:id: update strategy PATCH /v1/pricing-plans/:planId/pricing-strategies: bulk update + create DELETE /v1/pricing-plans/:planId/pricing-strategies/:id: soft delete strategy Charge types: usage, recurring, onetime, seat_based Pricing models: flat, graduated, volume, credit_pool Credit pool: flat fee for a pool of units, then per-unit overage tiers[0] = { lower: 0, upper: poolSize, rate: poolPrice } (flat fee) tiers[1] = { lower: poolSize, upper: null, rate: overageRate } (per-unit) Subscriptions: POST /v1/subscriptions: create subscription GET /v1/subscriptions: list subscriptions GET /v1/subscriptions/:id: get subscription by ID PATCH /v1/subscriptions/:id: update subscription DELETE /v1/subscriptions/:id: delete subscription Invoices: GET /v1/invoices: list invoices (filterable by customer, status) GET /v1/invoices/:id: get invoice with line items Analytics (canonical, reconciled with the dashboard): GET /v1/analytics/usage: usage analytics (time-series, groupBy customer/agent/signal/day) GET /v1/analytics/revenue: canonical revenue metrics (Agent-Earned + fees) GET /v1/analytics/cost: canonical cost metrics + breakdowns by agent/customer/signal/day/plan/model GET /v1/analytics/mrr: MRR with variant param (canonical, runRate, committed, all) GET /v1/analytics/agent-earned: pure activity earnings (no fees, pre-invoice) GET /v1/analytics/invoice-totals: Billed + Collected for a window GET /v1/analytics/profitability: cost + revenue + margin grouped as requested Portal Sessions: POST /v1/portal-sessions: create a portal session for a customer GET /v1/portal-sessions: list portal sessions GET /v1/portal-sessions/:id: get portal session by ID DELETE /v1/portal-sessions/:id: delete portal session Model Mapping: POST /v1/events/map-model: map an unknown model to an existing catalog entry and backfill costs (does not create new catalog rates; for services not in the catalog, the event still lands but cost stays null until the catalog is updated) Verification: GET /v1/verify: verify API key and see organization info ## MCP Integration MarginFront has an MCP server for AI assistants (Claude, ChatGPT, Gemini, Cursor, etc.). MCP is the operator interface for human-to-LLM ops on a MarginFront account: backfilling historical events from CSVs, running batch fixes, querying revenue and cost in plain English, mapping unrecognized models in bulk, managing pricing plans and subscriptions conversationally. MCP is NOT how a developer instruments their product. For per-event tracking from production code, use the SDK or REST API above. The SDK is typed, fast, and runs inline with product logic; MCP runs through an LLM and is built for ad-hoc, conversational use. Setup (Claude Code): ```json { "mcpServers": { "marginfront": { "command": "npx", "args": ["-y", "@marginfront/mcp"], "env": { "MF_API_SECRET_KEY": "mf_sk_your_key_here" } } } } ``` See https://marginfront.com/llms-mcp.txt for the complete MCP tool catalog (25 tools). ## Field Reference ### Required for every event: - customerExternalId (string): the developer's ID for this user in their own system. If MarginFront has not seen this ID, the customer record is auto-provisioned. - agentCode (string): a stable identifier for the agent. If MarginFront has not seen this code, the agent record is auto-provisioned. - signalName (string): the billing unit being tracked (e.g. "messages", "reports"). If MarginFront has not seen this name, the signal record is auto-provisioned. - model (string): model identifier (e.g. "gpt-4o", "claude-sonnet-4", "twilio-sms"). - modelProvider (string): provider name, lowercase (e.g. "openai", "anthropic", "twilio"). ### Add for LLM events (required for cost tracking): - inputTokens (integer, ≥ 0): prompt / input tokens. Must be a whole integer. - outputTokens (integer, ≥ 0): completion / output tokens. Must be a whole integer. ### Add for non-LLM or variable-quantity events: - quantity (integer, ≥ 0): numeric count. Must be a whole integer. For fractional units (e.g. 14.137 seconds of compute, 3.45 minutes of audio), round to a whole integer before sending. Defaults to 1 if omitted AFTER validation fires. For non-LLM events, quantity is required on the incoming record; sending nothing triggers MISSING_VOLUME_DATA. ### Optional: - usageDate (string, ISO 8601): when the event happened. Defaults to now. - metadata (object): free-form key-value pairs. Stored but not used for billing or cost calculation. ## Important: Silent Failure Behavior The API returns HTTP 200 even when individual events fail validation. You MUST check the response body: ```json { "processed": 2, "successful": 1, "failed": 1, "results": { "success": [...], "failed": [{ "error": "Signal not found: typo-name" }] } } ``` If `failed > 0`, that event did NOT record. Read `results.failed[].error` for the reason. Common causes: signal name mismatch (including extra whitespace), customer external ID not found, unrecognized model (event still stores with cost = null). ## Important: Event Ingestion Statuses Every event lands in one of three states on the server. None of them drop the event. - PROCESSED: event recorded, cost calculated. Everything worked. - NEEDS_COST_BACKFILL: event recorded, model+provider wasn't recognized. Cost is null until you map the model. Future events with the same model auto-resolve. - MISSING_VOLUME_DATA: event recorded, but couldn't calculate cost because volume fields were missing. LLM events need inputTokens AND outputTokens. Non-LLM events need quantity. Fix by replaying the event with the missing field filled in. Explicit zero values (inputTokens: 0, quantity: 0) are valid and record as PROCESSED with cost = 0. ## Important: Never Break the Core Product MarginFront is billing infrastructure. If it is unavailable or returns an error, the host application must continue functioning. Always wrap MarginFront calls in error handling. On failure: log a warning and continue. The SDK does this automatically when fireAndForget is true (the default). ```typescript // GOOD. Agent keeps running no matter what. try { await mf.usage.record({ ... }); } catch (err) { console.warn('MarginFront tracking failed, continuing:', err.message); } // BETTER. Use fireAndForget (default) and don't even think about it. await mf.usage.record({ ... }); // never throws, never blocks ```