Python SDK

margint — per-customer AI cost tracking for Python 3.10+.

The margint package runs on Python 3.10+ and works with any LLM provider.

pip install margint

60-second start

import os
from margint import Margint

m = Margint(api_key=os.environ["MARGINT_API_KEY"])

m.track(
    customer_id="cust_abc",
    feature="chat",
    provider="openai",
    model="gpt-4o",
    input_tokens=820,
    output_tokens=310,
)

m.shutdown()  # flushes before exit

The flush worker runs on a background thread. track() returns immediately.

Three integration patterns

1. wrap() — zero-touch

import openai
from margint import Margint

m = Margint(api_key=os.environ["MARGINT_API_KEY"])
client = m.wrap(openai.OpenAI(), customer_id="cust_abc", feature="chat")

response = client.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

response = my_llm_call()

m.track(
    customer_id=request.user.id,
    feature="summarize",
    provider="anthropic",
    model="claude-sonnet-4-20250514",
    input_tokens=response.usage.input_tokens,
    output_tokens=response.usage.output_tokens,
    metadata={"request_id": request.id},
)

3. guarded_call() — budget enforcement

from margint import BudgetExceededError

try:
    response = m.guarded_call(
        customer_id="cust_abc",
        feature="agent",
        fn=lambda: client.chat.completions.create(model="gpt-4o", messages=msgs),
    )
except BudgetExceededError as e:
    return {"error": "Budget exceeded", "breaches": [b.__dict__ for b in e.breaches]}, 402

guarded_call checks the budget but does not auto-track. Pair with wrap() or track().

Framework quickstarts

FastAPI

app/margint_client.py
import os
from margint import Margint

margint = Margint(api_key=os.environ["MARGINT_API_KEY"])
app/main.py
import openai
from fastapi import FastAPI
from contextlib import asynccontextmanager
from .margint_client import margint

@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    margint.shutdown()

app = FastAPI(lifespan=lifespan)

@app.post("/chat")
def chat(user_id: str, messages: list[dict]):
    client = margint.wrap(openai.OpenAI(), customer_id=user_id, feature="chat")
    return client.chat.completions.create(model="gpt-4o", messages=messages)

Django

myproject/apps.py
import os
from django.apps import AppConfig
from margint import Margint

margint: Margint | None = None

class CoreConfig(AppConfig):
    name = "core"

    def ready(self):
        global margint
        margint = Margint(api_key=os.environ["MARGINT_API_KEY"])
core/views.py
import openai
from django.http import JsonResponse
from .apps import margint

def chat(request):
    client = margint.wrap(openai.OpenAI(), customer_id=request.user.id, feature="chat")
    response = client.chat.completions.create(model="gpt-4o", messages=request.POST["messages"])
    return JsonResponse(response.model_dump())

atexit handles flushing on process exit automatically.

Flask

import os
import openai
from flask import Flask, request, jsonify
from margint import Margint

app = Flask(__name__)
margint = Margint(api_key=os.environ["MARGINT_API_KEY"])

@app.post("/chat")
def chat():
    client = margint.wrap(openai.OpenAI(), customer_id=request.json["user_id"], feature="chat")
    response = client.chat.completions.create(model="gpt-4o", messages=request.json["messages"])
    return jsonify(response.model_dump())

Configuration

Margint(
    api_key: str,
    endpoint: Optional[str] = None,          # default: https://app.margint.dev/api/ingest/events
    budget_endpoint: Optional[str] = None,   # default: https://app.margint.dev/api/budgets/check
    flush_interval_seconds: float = 5.0,
    max_batch_size: int = 50,
    budget_cache_ttl_seconds: float = 60.0,
)

Async

v0.1 is synchronous only. track() is non-blocking — the flush worker runs on a background thread, so async frameworks aren't stalled.

A native AsyncMargint is on the roadmap. Email hi@margint.dev if it matters for your stack.

Troubleshooting

Events not appearing?

  • Verify api_key matches a key in Settings → API Keys.
  • Call m.flush() — the 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 cost_microdollars=... manually on track(), or email us to add it.

wrap() doesn't track my calls?

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