@margint-ai/sdk
v0.1.0
Published
Per-customer AI cost tracking and budget enforcement for AI-native SaaS. Track LLM spend per customer, enforce budgets, see margin in real time.
Maintainers
Readme
@margint-ai/sdk
Per-customer AI margin in three lines of code.
Tag every LLM call with a customer ID and Margint shows you which customers actually make you money — after AI costs. No proxy, no latency hit. Privacy-first: tokens, model, cost. Never your prompts.
npm install @margint-ai/sdkimport { Margint } from '@margint-ai/sdk'
import OpenAI from 'openai'
const m = new Margint({ apiKey: process.env.MARGINT_API_KEY! })
const openai = m.wrap(new OpenAI(), { customerId: user.id, feature: 'chat' })
await openai.chat.completions.create({ model: 'gpt-4o', messages })
// → tracked. Open the dashboard to see margin per customer.Get a key at app.margint.dev. Free up to 100k events / month, GitHub OAuth, no credit card.
Three integration patterns
Pick whichever fits. Mix them.
1. wrap() — zero-touch
Wrap your LLM client once; every call is tracked.
const openai = m.wrap(new OpenAI(), { customerId: 'cust_abc', feature: 'chat' })
const res = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'hi' }],
})Works with OpenAI- and Anthropic-shaped responses (response.usage, response.model). For other providers, use track().
2. track() — manual
For custom HTTP, multi-customer requests, or providers without a native SDK.
const res = await myLlmCall()
m.track({
customerId: req.user.id,
feature: 'summarize',
provider: 'anthropic',
model: 'claude-sonnet-4-20250514',
inputTokens: res.usage.input_tokens,
outputTokens: res.usage.output_tokens,
})Cost is computed locally from the bundled pricing table — no network hop.
3. guardedCall() — kill switch
Block the call before it bills you when a customer is over their monthly budget.
import { BudgetExceededError } from '@margint-ai/sdk'
try {
const res = await m.guardedCall(
{ customerId: 'cust_abc', feature: 'agent' },
() => openai.chat.completions.create({ model: 'gpt-4o', messages }),
)
} catch (err) {
if (err instanceof BudgetExceededError) {
return reply.status(402).send({ error: 'Budget exceeded', breaches: err.breaches })
}
throw err
}Budget checks are cached for 60 s, so guardedCall stays fast in hot paths. It does not auto-track — combine with wrap() or track() if you want both.
Framework quickstarts
Next.js (App Router)
Module-level singleton so HMR doesn't leak intervals.
// lib/margint.ts
import { Margint } from '@margint-ai/sdk'
declare global {
var __margint: Margint | undefined
}
export const margint =
globalThis.__margint ??
(globalThis.__margint = new Margint({ apiKey: process.env.MARGINT_API_KEY! }))// app/api/chat/route.ts
import { margint } from '@/lib/margint'
import OpenAI from 'openai'
export async function POST(req: Request) {
const { userId, messages } = await req.json()
const openai = margint.wrap(new OpenAI(), { customerId: userId, feature: 'chat' })
const res = await openai.chat.completions.create({ model: 'gpt-4o', messages })
return Response.json(res)
}On serverless, call await margint.flush() at the end of long-lived handlers if you want events visible immediately. For short requests the 5 s batch timer is fine.
Nuxt 3 / 4
// server/utils/margint.ts
import { Margint } from '@margint-ai/sdk'
let instance: Margint | undefined
export function useMargint() {
return (instance ??= new Margint({ apiKey: process.env.MARGINT_API_KEY! }))
}// server/api/chat.post.ts
import OpenAI from 'openai'
import { useMargint } from '../utils/margint'
export default defineEventHandler(async (event) => {
const { userId, messages } = await readBody(event)
const client = useMargint().wrap(new OpenAI(), { customerId: userId, feature: 'chat' })
return client.chat.completions.create({ model: 'gpt-4o', messages })
})Express
import express from 'express'
import OpenAI from 'openai'
import { Margint } from '@margint-ai/sdk'
const app = express()
const margint = new Margint({ apiKey: process.env.MARGINT_API_KEY! })
app.post('/chat', async (req, res) => {
const client = margint.wrap(new OpenAI(), { customerId: req.user.id, feature: 'chat' })
res.json(await client.chat.completions.create({ model: 'gpt-4o', messages: req.body.messages }))
})
process.on('SIGTERM', async () => {
await margint.shutdown()
process.exit(0)
})Configuration
new Margint({
apiKey: string, // required
endpoint?: string, // default: https://app.margint.dev/api/ingest/events
budgetEndpoint?: string, // default: https://app.margint.dev/api/budgets/check
flushIntervalMs?: number, // default: 5000
maxBatchSize?: number, // default: 50 — flushes early when reached
budgetCacheTtlMs?: number, // default: 60000
})Self-hosting? Override endpoint and budgetEndpoint.
Troubleshooting
Events not appearing?
- Verify
apiKeymatches a key in Settings → API Keys. - Call
await m.flush()— the 5 s timer may not have fired in short scripts. - Check egress for
app.margint.dev.
Cost shows as $0?
- Model isn't in the bundled pricing table. Pass
costMicrodollarsdirectly ontrack(), or email[email protected]to add it.
wrap() not tracking?
- Response must expose
.usage(withprompt_tokens/input_tokens) and.model. Otherwise calltrack()directly.
MIT licensed. © Margint. Questions: [email protected].
