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_keymatches 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 ontrack(), or email us to add it.
wrap() doesn't track my calls?
- The response must expose
.usage(withprompt_tokens/input_tokens) and.model. If not, usetrack().