JamJet
Cloud

Budgets

Per-project cost ceilings. Pre-call estimation throws before billing; post-call records truth.

Budgets

budget(maxCostUsd) sets a cost ceiling for your project. JamJet enforces it in two phases: a pre-call estimation that throws before the LLM is even contacted if the projected spend would exceed the ceiling, and a post-call recording that updates the running total with the actual cost from response.usage.

Setting a budget

Pass a positive USD number to budget(). Call it once at startup, after init().

import { init, budget, wrap } from '@jamjet/cloud'
import OpenAI from 'openai'

init({ apiKey: process.env.JAMJET_API_KEY!, project: 'my-app' })

budget(50)   // $50 ceiling for this project

const openai = wrap(new OpenAI())
import jamjet.cloud as jamjet
from openai import OpenAI
import os

jamjet.configure(api_key=os.environ['JAMJET_API_KEY'], project='my-app')

jamjet.budget(max_cost_usd=50)   # $50 ceiling for this project

client = jamjet.wrap(OpenAI())

The budget applies to all wrapped calls in the process. It is per-process, not per-API-key — different processes with the same key each have independent ceilings.

Pre-call estimation

Before sending a request to the LLM, JamJet estimates the cost of the call from the prompt token count using a model-keyed price table. If the estimated total spend (cumulative across the process lifetime) would exceed the ceiling, JamjetBudgetExceeded is thrown immediately — the LLM is never called and no charges are incurred.

import { JamjetBudgetExceeded } from '@jamjet/cloud'

try {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [{ role: 'user', content: 'Long prompt...' }],
  })
} catch (err) {
  if (err instanceof JamjetBudgetExceeded) {
    console.error(
      `Budget exceeded. Spent: $${err.spent.toFixed(4)}, Limit: $${err.limit.toFixed(2)}`
    )
  }
}
from jamjet.cloud.errors import JamjetBudgetExceeded

try:
    response = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': 'Long prompt...'}],
    )
except JamjetBudgetExceeded as err:
    print(f'Budget exceeded. Spent: ${err.spent:.4f}, Limit: ${err.limit:.2f}')

JamjetBudgetExceeded fields

FieldTypeDescription
spentnumberCumulative USD spend recorded so far in this process
limitnumberThe ceiling set via budget()

Both values are in USD. spent reflects actual post-call costs from previous calls plus the pre-call estimate for the call that triggered the exception.

Post-call recording

When a call completes successfully, JamJet reads response.usage.input_tokens and response.usage.output_tokens from the response, computes the actual cost using the same price table, and updates the running total. The pre-call estimate is replaced by the actual cost in the span.

This means the running total is always grounded in real billing data from the API, not just estimates.

Changing the ceiling

Call budget(N) again at any point to replace the current ceiling. The running total is not reset — only the ceiling changes. To effectively reset, set a new ceiling equal to the current limit plus however much additional spend you want to allow.

// Initial ceiling
budget(50)

// ... after approval from a human operator ...

// Raise ceiling to $100
budget(100)
# Initial ceiling
jamjet.budget(max_cost_usd=50)

# ... after approval from a human operator ...

# Raise ceiling to $100
jamjet.budget(max_cost_usd=100)

Model recognition limitation

JamJet uses an internal price table keyed by model name (e.g. gpt-4o, claude-3-5-sonnet-20241022). If the model name in a request is not in the table — for example, a newly released model or a custom fine-tune — the estimated cost is treated as $0.

A $0 estimate means the budget gate effectively allows the call, even if actual billing turns out to be significant. The post-call recording still updates the running total with the real cost, so subsequent calls will be gate-checked accurately.

To verify that your model is recognized, check the dashboard span for a non-zero cost_usd value on a completed call. If it shows $0.0000 for a model that should be billable, open the project settings and file a model-table update request.

Combined example

import OpenAI from 'openai'
import { init, budget, wrap, JamjetBudgetExceeded } from '@jamjet/cloud'

init({ apiKey: process.env.JAMJET_API_KEY!, project: 'research-pipeline' })
budget(5)   // $5 hard ceiling — useful for development

const openai = wrap(new OpenAI())

async function runResearch(prompt: string): Promise<string | null> {
  try {
    const res = await openai.chat.completions.create({
      model: 'gpt-4o',
      messages: [{ role: 'user', content: prompt }],
    })
    return res.choices[0].message.content
  } catch (err) {
    if (err instanceof JamjetBudgetExceeded) {
      console.warn(`Research halted — budget exhausted ($${err.spent.toFixed(4)} / $${err.limit})`)
      return null
    }
    throw err
  }
}
from openai import OpenAI
import jamjet.cloud as jamjet
from jamjet.cloud.errors import JamjetBudgetExceeded
import os

jamjet.configure(api_key=os.environ['JAMJET_API_KEY'], project='research-pipeline')
jamjet.budget(max_cost_usd=5)   # $5 hard ceiling — useful for development

client = jamjet.wrap(OpenAI())

def run_research(prompt: str) -> str | None:
    try:
        res = client.chat.completions.create(
            model='gpt-4o',
            messages=[{'role': 'user', 'content': prompt}],
        )
        return res.choices[0].message.content
    except JamjetBudgetExceeded as err:
        print(f'Research halted — budget exhausted (${err.spent:.4f} / ${err.limit})')
        return None

Next steps

On this page