temperDocs
Menu

Concepts

Usage and limits

What Temper meters for each environment, how to read it, and how monthly limits suspend environments that go over.

Temper meters what each environment uses, hour by hour. You can read usage for any time range, per environment and in total, and set monthly limits so that a runaway agent or an unexpectedly busy end user cannot use more than you planned for.

Metrics

MetricWhat it measures
active_hoursTime the environment's VM was running. Time while it is paused, suspended or stopped does not count.
cpu_hoursCPU time used by the VM.
memory_gib_hoursMemory held by the VM over time, in GiB-hours.
storage_gib_hoursData disk space used over time, in GiB-hours.
egress_gibNetwork traffic sent out of the environment, in GiB.
browser_hoursTime spent in browser leases that use a profile (from lease start to end). Clean leases do not count.

Hosts report cumulative counters and the control plane records the increase in each hour. Time while a host is out of contact is not counted.

Reading usage

Get usage returns the totals for a time range and one entry per environment.

ParameterMeaning
fromStart of the range (inclusive). Defaults to the start of the current month, 00:00 UTC.
toEnd of the range (exclusive). Defaults to now.
environment_idOnly this environment. Returns not_found if it is not yours.

from must be earlier than to, otherwise the request fails with bad_request. The response has from, to, total (all six metrics summed) and data: one entry per environment with environment_id, end_user_id and the six metrics. Environments with no usage in the range are left out of data.

import { Temper } from "@temper-hq/sdk";

const temper = new Temper();

const usage = await temper.metering.usage(); // this month (UTC) so far
console.log("total egress GiB:", usage.total.egress_gib);
for (const e of usage.data) {
  console.log(e.end_user_id, e.active_hours, e.browser_hours);
}

// One environment, a fixed range.
const sept = await temper.metering.usage({
  from: "2026-09-01T00:00:00Z",
  to: "2026-10-01T00:00:00Z",
  environment_id: envId,
});
from datetime import datetime, timezone

from temper_hq import Temper

temper = Temper()

usage = temper.metering.usage()  # this month (UTC) so far
print("total egress GiB:", usage.total.egress_gib)
for e in usage.data:
    print(e.end_user_id, e.usage.active_hours, e.usage.browser_hours)

# One environment, a fixed range.
sept = temper.metering.usage(
    from_=datetime(2026, 9, 1, tzinfo=timezone.utc),
    to=datetime(2026, 10, 1, tzinfo=timezone.utc),
    environment_id=env_id,
)
usage, err := client.Metering.Usage(ctx, temper.UsageParams{}) // this month (UTC) so far
if err != nil {
	log.Fatal(err)
}
fmt.Println("total egress GiB:", usage.Total.EgressGiB)
for _, e := range usage.Data {
	fmt.Println(e.EndUserID, e.ActiveHours, e.BrowserHours)
}

// One environment, a fixed range.
sept, err := client.Metering.Usage(ctx, temper.UsageParams{
	From:          time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC),
	To:            time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC),
	EnvironmentID: envID,
})

Monthly limits

A limit caps one metric for the current calendar month (UTC). There are two kinds:

  • Developer-level: no environment_id. The cap applies to the sum across all your environments.
  • Per environment: with an environment_id. The cap applies to that environment alone.

Setting a limit for a metric that already has one (at the same level) replaces it. monthly_limit must be zero or more; an unknown metric or a negative limit returns bad_request. Listing limits returns every limit, developer-level ones first, each with used for the current month.

// At most 50 GiB of egress per month across all environments.
await temper.metering.setLimit({ metric: "egress_gib", monthly_limit: 50 });

// At most 20 browser hours per month for one environment.
await temper.metering.setLimit({ metric: "browser_hours", monthly_limit: 20, environment_id: envId });

for (const l of await temper.metering.listLimits()) {
  console.log(l.environment_id ?? "all environments", l.metric, `${l.used} / ${l.monthly_limit}`);
}
# At most 50 GiB of egress per month across all environments.
temper.metering.set_limit(metric="egress_gib", monthly_limit=50)

# At most 20 browser hours per month for one environment.
temper.metering.set_limit(metric="browser_hours", monthly_limit=20, environment_id=env_id)

for lim in temper.metering.list_limits():
    print(lim.environment_id or "all environments", lim.metric, f"{lim.used} / {lim.monthly_limit}")
// At most 50 GiB of egress per month across all environments.
if _, err := client.Metering.SetLimit(ctx, temper.SetLimitParams{Metric: temper.MetricEgressGiB, MonthlyLimit: 50}); err != nil {
	log.Fatal(err)
}

// At most 20 browser hours per month for one environment.
if _, err := client.Metering.SetLimit(ctx, temper.SetLimitParams{
	Metric: temper.MetricBrowserHours, MonthlyLimit: 20, EnvironmentID: envID,
}); err != nil {
	log.Fatal(err)
}

limits, err := client.Metering.ListLimits(ctx)
if err != nil {
	log.Fatal(err)
}
for _, l := range limits {
	fmt.Println(l.EnvironmentID, l.Metric, l.Used, l.MonthlyLimit)
}

When a limit is exceeded

A limit counts as exceeded when usage for the month is greater than or equal to the limit, so a limit of 0 allows nothing. Limits are checked every minute, and again right away whenever you set or delete one. When a limit is exceeded:

  1. The environment is suspended. If a developer-level limit is exceeded, every environment that is not destroyed is suspended.
  2. The environment's over_limit field is set to a human-readable reason, such as environment egress_gib 1.20 >= 1. It is null when the environment is within its limits.
  3. A limit_exceeded environment event is recorded and an environment.limit_exceeded webhook is sent.
  4. Resume, wake and exec on the environment fail with 409 limit_exceeded.

This lasts until you raise the limit, delete it, or a new month starts. At that point over_limit goes back to null, but the environment stays suspended until you resume it. Suspending keeps the data disk; see environments.

import { Temper, isTemperError } from "@temper-hq/sdk";

try {
  await temper.environments.resume(envId);
} catch (err) {
  if (isTemperError(err, "limit_exceeded")) {
    const env = await temper.environments.get(envId);
    console.log("over limit:", env.over_limit);
  } else {
    throw err;
  }
}
from temper_hq import TemperError

try:
    temper.environments.resume(env_id)
except TemperError as err:
    if err.code != "limit_exceeded":
        raise
    print("over limit:", temper.environments.get(env_id).over_limit)
if _, err := client.Environments.Resume(ctx, envID); err != nil {
	if !temper.IsCode(err, "limit_exceeded") {
		log.Fatal(err)
	}
	env, err := client.Environments.Get(ctx, envID)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("over limit:", *env.OverLimit)
}

Deleting a limit

Delete a limit by metric, plus environment_id for a per-environment limit (leave it out to delete the developer-level one). Deleting a limit that does not exist returns not_found. Limits are rechecked right away, so environments that were suspended only because of that limit can be resumed.

await temper.metering.deleteLimit({ metric: "egress_gib" }); // developer-level
await temper.metering.deleteLimit({ metric: "browser_hours", environment_id: envId });
temper.metering.delete_limit(metric="egress_gib")  # developer-level
temper.metering.delete_limit(metric="browser_hours", environment_id=env_id)
if err := client.Metering.DeleteLimit(ctx, temper.MetricEgressGiB, ""); err != nil { // developer-level
	log.Fatal(err)
}
if err := client.Metering.DeleteLimit(ctx, temper.MetricBrowserHours, envID); err != nil {
	log.Fatal(err)
}