Spend limits

Budgets and spend limits can be attached to multiple levels to enforce your spending policies and rules. Limits can be added at the organization, API keys, users, apps and teams. Each limit has a daily, weekly and monthly window and is defined in USD. Limits and teams can be managed directly from the dashboard or via the API.

Amounts are exact USD in every SDK, never a float.

Setup

The examples below use api and limits from here. Every client reads STREAM_API_KEY and STREAM_API_SECRET from the environment, and spend limits are server side only.

use vision_agents::types::{
    CreateSessionRequest, LimitInterval, LimitSubject,
};
use vision_agents::{
    Client, ClientOptions, Limit, LimitFilter, Reset,
    TeamLimit, Tools, Usd,
};

let api = Client::from_env()?;
let limits = api.spend_limits();

Organization limits

// The organization's ceiling
let ceiling = Limit::new(
    LimitSubject::Organization,
    LimitInterval::Monthly,
    Usd::dollars(20_000),
);
limits.create(&ceiling).await?;

API key limits

// Default: every key of app 1234 may spend $50 a day
let fallback = Limit::new(
    LimitSubject::ApiKey,
    LimitInterval::Daily,
    Usd::dollars(50),
)
.app_id("1234");
limits.create(&fallback).await?;

// One key gets its own limit, replacing the default
let ci_key = Limit::new(
    LimitSubject::ApiKey,
    LimitInterval::Daily,
    Usd::dollars(10),
)
.app_id("1234")
.api_key("mz7kq2xa9b")
.note("CI key");
limits.create(&ci_key).await?;

// Later: raise it. Find the row first if you lack its id
let found = limits
    .list(&LimitFilter {
        subject: Some(LimitSubject::ApiKey),
        api_key: Some("mz7kq2xa9b".into()),
        interval: Some(LimitInterval::Daily),
        ..Default::default()
    })
    .await?;
limits.set_amount(found[0].id, Usd::dollars(25)).await?;

// Delete it: the key falls back to the $50 default
limits.delete(found[0].id).await?;

User limits

// Default: every user of app 1234 may spend $20 a day
let fallback = Limit::new(
    LimitSubject::User,
    LimitInterval::Daily,
    Usd::dollars(20),
)
.app_id("1234");
limits.create(&fallback).await?;

// A custom limit for one user, replacing the default
let user_42 = Limit::new(
    LimitSubject::User,
    LimitInterval::Daily,
    Usd::dollars(50),
)
.app_id("1234")
.user_id("user-42");
limits.create(&user_42).await?;

Team limits

// Finance across the organization: one row per interval,
// each created or updated
let teams = api.teams();
for (interval, amount) in [
    (LimitInterval::Monthly, Usd::dollars(8_000)),
    (LimitInterval::Weekly, Usd::dollars(2_500)),
    (LimitInterval::Daily, Usd::dollars(600)),
] {
    let limit = TeamLimit::new("finance", interval, amount);
    teams.set_limit(&limit).await?;
}

To associate an agent with a team, add the team: finance tag to its agents.yaml file. This is the same for every SDK.

agents.yaml
name: my-agent
mode: text
llm: llm-fast

# associate this agent to the finance team
tags:
  team: finance

Associate sessions to a team

You can attribute a session's usage to a team with the team field when creating it. It is server side only: a team signed into the user's token always wins, then the session's team, then the agent's tag.

let request = CreateSessionRequest {
    team: Some("finance".into()),
    ..Default::default()
};
let session = api
    .agent("my-agent")
    .sessions
    .create(request, Tools::new())
    .await?;

Router usage

If you use the router directly, without an agent or session, you can still pass the team to the client constructor as an option. Everything that client does counts under the team.

let finance = Client::new(ClientOptions {
    team: Some("finance".into()),
    ..Default::default()
})?;
let router = finance.router("dentist_ai");

Reset limits

You can use the API to reset usage for users and teams.

// Reset the weekly window for one user.
// Leave intervals out to reset every window
let user = Reset::new(
    LimitSubject::User,
    "refunded a broken session",
)
.app_id("1234")
.user_id("user-42")
.intervals([LimitInterval::Weekly]);
limits.reset(&user).await?;

// Only today's window, for team finance
let team = Reset::new(LimitSubject::Team, "a batch job ran twice")
    .team("finance")
    .intervals([LimitInterval::Daily]);
limits.reset(&team).await?;