TypeScript SDK

@margint-ai/sdk — per-customer AI cost tracking for Node.js and edge runtimes.

The @margint-ai/sdk package runs in Node 18+, Bun, Deno, and edge runtimes that expose fetch.

npm install @margint-ai/sdk

60-second start

import { Margint } from '@margint-ai/sdk'

const m = new Margint({ apiKey: process.env.MARGINT_API_KEY! })

m.track({
  customerId: 'cust_abc',
  feature: 'chat',
  provider: 'openai',
  model: 'gpt-4o',
  inputTokens: 820,
  outputTokens: 310
})

// before your process exits
await m.shutdown()

Events batch in memory and flush every 5 s.

Three integration patterns

1. wrap() — zero-touch

Wrap your LLM client once and every call is tracked automatically.

import OpenAI from 'openai'
import { Margint } from '@margint-ai/sdk'

const m = new Margint({ apiKey: process.env.MARGINT_API_KEY! })
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 SDK response shapes (response.usage, response.model). For other providers, use track().

2. track() — manual

For custom HTTP, multiple customers per request, 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,
  metadata: { requestId: req.id }
})

3. guardedCall() — budget enforcement

Blocks the call if the customer is over budget. Budget checks are cached for 60 s.

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
}

guardedCall checks the budget but does not auto-track the result — combine with wrap() or track() if you want both.

Framework quickstarts

Next.js (App Router)

Instantiate as a module-level singleton so HMR doesn't leak flush 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 client = margint.wrap(new OpenAI(), { customerId: userId, feature: 'chat' })
  const res = await client.chat.completions.create({ model: 'gpt-4o', messages })
  return Response.json(res)
}

On serverless, call await margint.flush() at the end of long-lived handlers for immediate visibility. 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' })
  const result = await client.chat.completions.create({ model: 'gpt-4o', messages: req.body.messages })
  res.json(result)
})

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
})

Troubleshooting

Events not appearing?

  • Verify apiKey matches a key in Settings → API Keys.
  • Call await m.flush() — the default 5 s timer may not fire in short scripts.
  • Check egress for app.margint.dev.

Cost shows as $0?

  • Model isn't in the bundled pricing database. Pass costMicrodollars manually on track(), or email us to add the model.

wrap() doesn't track my calls?

  • The response must expose .usage (with prompt_tokens/input_tokens) and .model. If not, use track().