SDK Reference
Complete reference for the noburn Python and Node.js SDKs.
Python SDK
Installation
pip install noburnRequires Python 3.9+.
NoburnGuard
The main class. Create one instance per project and reuse it across requests.
from noburn import NoburnGuard
guard = NoburnGuard(
api_key="sk-nb-xxxxxxxxxxxxxxxx",
project_id="your-project-id",
budget_cap_usd=10.00,
)Constructor options
| Parameter | Type | Required | Description |
|---|---|---|---|
api_key | str | ✓ | Your project SDK key from the dashboard |
project_id | str | ✓ | Project UUID from the dashboard |
budget_cap_usd | float | — | Monthly spend cap in USD. Calls are blocked once exceeded |
base_url | str | — | Override API endpoint (default: https://noburn.dev) |
timeout | float | — | Request timeout in seconds (default: 2.0) |
on_error | str | — | Behavior when the noburn API is unreachable: "allow" (default — fail open, never breaks your app) or "block" (fail closed — blocks with reason "noburn_unreachable") |
guard.check()
Checks whether a planned LLM call should be allowed. Call this before every LLM API call.
result = guard.check(
model="gpt-4o",
estimated_tokens_in=1500,
estimated_tokens_out=500,
end_user_id="user_abc123", # optional — for per-user tracking
context={"plan": "free"}, # optional — matched against policy rules
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
model | str | ✓ | Model name as it appears in your LLM provider's API |
estimated_tokens_in | int | ✓ | Estimated prompt token count |
estimated_tokens_out | int | ✓ | Estimated completion token count |
end_user_id | str | — | Identifier for the end user. Enforces that user's per-user budget cap |
run_id | str | — | Run identifier from start_run(). Enforces that run's per-run budget cap |
cost_usd | float | — | Cost override for unrecognized models. For recognized models, noburn prices the call server-side from token counts |
context | dict | — | Arbitrary key-value pairs matched against server-side policy rules (e.g. {"plan": "free"}) |
Return value: CheckResult
| Field | Type | Description |
|---|---|---|
blocked | bool | True if the call should be blocked |
block_reason | str | None | Why the call was blocked: "budget_exceeded", "user_budget_exceeded", "run_budget_exceeded", "policy_rule", "plan_limit_exceeded", or "noburn_unreachable" |
forced_model_tier | str | None | "small" or "big" when a policy rule forces a cheaper/stronger model; otherwise None |
spend_usd | float | Current monthly project spend at time of check |
budget_cap_usd | float | None | The active project budget cap |
remaining_usd | float | None | Project budget remaining. None if no cap |
user_spend_usd / user_cap_usd / user_remaining_usd | float | None | Per-user budget state (when end_user_id is passed) |
run_spend_usd / run_cap_usd / run_remaining_usd | float | None | Per-run budget state (when run_id is passed) |
Pricing authority
The SDK includes a local pricing table for offline fallback estimates only. On every check() and record(), noburn recomputes cost server-side from token counts. Call guard.fetch_pricing() (Python) or guard.fetchPricing() (Node) to refresh the local table from GET /api/v1/pricing (global rates + your project overrides). For unrecognized models, pass cost_usd explicitly or rely on the server's conservative token floor.
guard.record()
Records the actual outcome of an LLM call. Always call this after a successful call.
guard.record(
model="gpt-4o",
tokens_in=1423,
tokens_out=487,
cost_usd=0.00848,
was_blocked=False,
end_user_id="user_abc123",
latency_ms=1240,
)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
model | str | ✓ | Model name |
tokens_in | int | ✓ | Actual prompt token count |
tokens_out | int | ✓ | Actual completion token count |
cost_usd | float | ✓ | Actual cost in USD |
was_blocked | bool | ✓ | Whether the call was blocked |
end_user_id | str | — | Same identifier passed to check() |
run_id | str | — | Same run identifier passed to check() |
latency_ms | int | — | End-to-end latency in milliseconds |
block_reason | str | — | Reason if was_blocked=True |
timestamp | str | — | ISO 8601 timestamp (default: now) |
record() is fire-and-forget — it returns immediately and never raises. The event is delivered on a background worker with retry. Call guard.flush() before your process exits to drain any queued events.
Per-user budgets
Pass end_user_id to track spend per user. You can set per-user caps in the dashboard under End-User Budgets — once a user crosses their cap, their calls are blocked with reason "user_budget_exceeded", independently of the project budget.
# Each user has independent spend tracking
result = guard.check(
model="claude-3-5-sonnet-20241022",
estimated_tokens_in=800,
estimated_tokens_out=200,
end_user_id=f"user_{request.user.id}",
)Error handling
check() never raises for budget decisions. By default it fails open: if the noburn API is unreachable, the call is allowed (blocked=False). Set on_error="block" to fail closed instead — unreachable means blocked=True with reason "noburn_unreachable". The only exception check() can raise is NoburnAuthError (invalid SDK key).
from noburn import NoburnGuard, NoburnAuthError
guard = NoburnGuard(api_key="sk-nb-…", project_id="…", on_error="block")
try:
result = guard.check(model="gpt-4o", estimated_tokens_in=1000, estimated_tokens_out=300)
except NoburnAuthError:
# Invalid SDK key — fix your credentials
raise
if result.blocked:
handle_blocked(result.block_reason)start_run() is stricter: it raises on network/auth failure (NoburnTimeoutError on timeout, NoburnAuthError on a bad key), since a run must be registered before you proceed.
Agent runs
A run bounds a single agent invocation with its own budget cap, separate from the project and per-user caps. Start a run, thread its run_id through check()/record(), then end it.
run = guard.start_run(budget_cap_usd=0.50) # StartRunResult(run_id, budget_cap_usd, started_at)
result = guard.check(
model="gpt-4o",
estimated_tokens_in=1000,
estimated_tokens_out=300,
run_id=run.run_id,
)
# ... make the LLM call ...
guard.record(model="gpt-4o", tokens_in=980, tokens_out=290,
cost_usd=0.005, was_blocked=False, run_id=run.run_id)
summary = guard.end_run(run.run_id) # EndRunResult(status="completed" | "budget_exceeded", spend_usd, ...)| Method | Description |
|---|---|
start_run(budget_cap_usd=None, end_user_id=None, metadata=None) | Registers a run. Raises on network/auth error. |
end_run(run_id) | Finalizes a run; returns EndRunResult or None on a network error. |
Node.js SDK
Installation
npm install @noburn/sdkRequires Node.js 18+. Works with TypeScript out of the box.
NoburnGuard
import { NoburnGuard } from '@noburn/sdk';
const guard = new NoburnGuard({
apiKey: process.env.NOBURN_API_KEY!,
projectId: process.env.NOBURN_PROJECT_ID!,
budgetCapUsd: 10.00,
});Constructor options
| Option | Type | Required | Description |
|---|---|---|---|
apiKey | string | ✓ | Your project SDK key |
projectId | string | ✓ | Project UUID |
budgetCapUsd | number | — | Monthly spend cap in USD |
baseUrl | string | — | Override API endpoint |
timeoutMs | number | — | Request timeout in ms (default: 2000) |
onError | 'allow' | 'block' | — | Behavior when the API is unreachable: 'allow' (default — fail open) or 'block' (fail closed — blockReason: 'noburn_unreachable') |
guard.check()
const result = await guard.check({
model: 'gpt-4o',
estimatedTokensIn: 1500,
estimatedTokensOut: 500,
endUserId: 'user_abc123',
context: { plan: 'free' }, // optional — matched against policy rules
});
if (result.blocked) {
return new Response('Budget exceeded', { status: 402 });
}Options
| Option | Type | Required | Description |
|---|---|---|---|
model | string | ✓ | Model name |
estimatedTokensIn | number | ✓ | Estimated prompt tokens |
estimatedTokensOut | number | ✓ | Estimated completion tokens |
endUserId | string | — | Identifier that enforces the user's per-user budget cap |
runId | string | — | Run id from startRun(); enforces that run's per-run budget cap |
costUsd | number | — | Cost override for unrecognized models (recognized models are priced server-side from token counts) |
context | Record<string, string> | — | Key-value pairs matched against server-side policy rules (e.g. { plan: 'free' }) |
Return: CheckResult
interface CheckResult {
blocked: boolean;
// 'budget_exceeded' | 'user_budget_exceeded' | 'run_budget_exceeded'
// | 'policy_rule' | 'plan_limit_exceeded' | 'noburn_unreachable' | null
blockReason: string | null;
forcedModelTier: 'small' | 'big' | null; // set by a force_small/force_big rule
spendUsd: number;
budgetCapUsd: number | null;
remainingUsd: number | null;
// Present when endUserId / runId are supplied:
userSpendUsd?: number;
userCapUsd?: number | null;
userRemainingUsd?: number | null;
runSpendUsd?: number;
runCapUsd?: number | null;
runRemainingUsd?: number | null;
}guard.record()
await guard.record({
model: 'gpt-4o',
tokensIn: response.usage.prompt_tokens,
tokensOut: response.usage.completion_tokens,
costUsd: 0.00848,
wasBlocked: false,
endUserId: 'user_abc123',
latencyMs: 1240,
});Agent runs
Bound a single agent invocation with its own cap, threading runId through check()/record():
const run = await guard.startRun({ budgetCapUsd: 0.5 });
const check = await guard.check({
model: 'gpt-4o',
estimatedTokensIn: 1000,
estimatedTokensOut: 300,
runId: run.runId,
});
// ... make the LLM call ...
await guard.record({ model: 'gpt-4o', tokensIn: 980, tokensOut: 290, costUsd: 0.005, wasBlocked: false, runId: run.runId });
const summary = await guard.endRun(run.runId); // { status: 'completed' | 'budget_exceeded', spendUsd, ... }record() is fire-and-forget with background retry; call await guard.flush() before exit to drain queued events. startRun() throws on network/auth error (NoburnTimeoutError on timeout).
Framework integrations
Next.js middleware
// middleware.ts
import { NoburnGuard } from '@noburn/sdk';
const guard = new NoburnGuard({
apiKey: process.env.NOBURN_API_KEY!,
projectId: process.env.NOBURN_PROJECT_ID!,
budgetCapUsd: 50,
});
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/ai')) {
const userId = request.headers.get('x-user-id') ?? undefined;
const check = await guard.check({
model: 'gpt-4o',
estimatedTokensIn: 1000,
estimatedTokensOut: 500,
endUserId: userId,
});
if (check.blocked) {
return Response.json(
{ error: 'Budget exceeded', code: 'BUDGET_EXCEEDED' },
{ status: 402 }
);
}
}
}Express
import express from 'express';
import { NoburnGuard } from '@noburn/sdk';
const guard = new NoburnGuard({ apiKey: '...', projectId: '...' });
const app = express();
app.use('/api/ai', async (req, res, next) => {
const check = await guard.check({
model: req.body.model ?? 'gpt-4o',
estimatedTokensIn: 1000,
estimatedTokensOut: 500,
endUserId: req.user?.id,
});
if (check.blocked) {
return res.status(402).json({ error: check.blockReason });
}
next();
});