> ## Documentation Index
> Fetch the complete documentation index at: https://runcrate.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Spend guardrails

> Cap how long an instance runs, stop it when it sits idle, and set a monthly budget per environment.

An agent that launches GPUs can leave them running after the job is done. The simplest guard is to run the work as a [job](/docs/jobs/overview): its machine stops itself, and its billing, when the command ends. For instances, guardrails put a ceiling on it: every instance can stop itself after a set time or when it goes idle, and every environment can have a monthly budget that nothing in it can spend past. You get an email and, if you add one, a [webhook](/docs/webhooks/overview) event before and when any of them acts.

| Guardrail | Scope | Range | Who sets it |
| - | - | - | - |
| [Max runtime](#max-runtime) | One instance | 5 minutes to 7 days | Whoever launches the instance |
| [Idle stop](#idle-stop) | One instance | 5 minutes to 24 hours | Whoever launches the instance |
| [Monthly budget](#monthly-budgets) | One environment | Any amount in USD | A workspace owner or admin, in the dashboard |

> [!TIP]
>
> Ask your agent: "Launch an H100 for this fine-tune, stop it after 4 hours, and stop it sooner if it sits idle for 30 minutes." It calls `create_instance` with `max_runtime_minutes: 240` and `idle_shutdown_minutes: 30`.

## Max runtime

Max runtime terminates an instance a fixed time after it starts. The clock starts when the instance first reaches `running`, so boot time does not count against it.

* Ten minutes before the limit, owners and admins get an email and your webhooks get an `instance.runtime_ending` event with the exact stop time.
* At the limit the instance is **terminated, not paused**. Its local disk is deleted, as with a manual terminate. Keep anything you need on a [storage volume](/docs/storage/overview) or push it somewhere before the limit.
* The range is 5 minutes to 7 days (10,080 minutes). Leave it unset for no limit.
* Once the instance is running, `auto_stop_at` on the instance tells you when it will stop: its start time plus the max runtime.

Set it when you launch:

```text Dashboard theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
Instances > Launch an instance > Auto stop > Stop after > 4 hours
```

```bash CLI theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
runcrate instances create --name training-run --gpu H100 --max-runtime 4h
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
const instance = await rc.instances.create({
  name: 'training-run',
  sshKeyId: 'key_abc123',
  gpuType: 'H100',
  maxRuntimeMinutes: 240,
});
```

```python Python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
instance = client.instances.create(
    name="training-run",
    ssh_key_id="key_abc123",
    gpu_type="H100",
    max_runtime_minutes=240,
)
```

```bash API theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
curl https://www.runcrate.ai/api/v1/instances \
  -H "Authorization: Bearer rc_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "training-run", "gpu_type": "h100-sxm", "max_runtime_minutes": 240}'
```

```json MCP theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{
  "tool": "create_instance",
  "arguments": { "name": "training-run", "gpu": "H100", "max_runtime_minutes": 240 }
}
```

The CLI takes durations with a unit (`30m`, `90m`, `1h30m`, `4h`, `168h`), in whole minutes. The API, SDKs and MCP take minutes as an integer. A value outside the range is rejected with HTTP 422 (`validation_error`) before anything launches; the SDKs raise `UnprocessableEntityError`.

## Idle stop

Idle stop terminates an instance once it stops doing work: GPU utilization stays under 5% **and** nobody is connected (SSH, the web terminal or the code editor), for the number of minutes you choose. It needs a GPU, so it is not offered for CPU-only instances.

* The range is 5 minutes to 24 hours (1,440 minutes). Leave it unset to keep the instance running while idle.
* An open SSH, web terminal or code editor session counts as activity, so an instance you are working on is not stopped while you are connected.
* The idle clock starts once the instance has finished setting up.
* Like max runtime, idle stop terminates the instance and deletes its local disk.
* It applies to instances launched with the setting. Instances launched before idle stop existed are not watched.

```bash CLI theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
runcrate instances create --name notebook --gpu A100 --idle-stop 30m
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
await rc.instances.create({ name: 'notebook', sshKeyId: 'key_abc123', gpuType: 'A100', idleShutdownMinutes: 30 });
```

```python Python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
client.instances.create(name="notebook", ssh_key_id="key_abc123", gpu_type="A100", idle_shutdown_minutes=30)
```

```json API theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{ "name": "notebook", "gpu_type": "a100-sxm", "idle_shutdown_minutes": 30 }
```

Both guardrails can be set on the same instance; whichever comes first stops it. Read them back on any instance:

| Field | Type | Meaning |
| - | - | - |
| `max_runtime_minutes` | integer or `null` | The max runtime set at launch. `null` means no limit. |
| `idle_shutdown_minutes` | integer or `null` | The idle stop set at launch. `null` means off. |
| `auto_stop_at` | ISO 8601 timestamp or `null` | When max runtime will stop the instance. `null` until it is running, or when there is no limit. |
| `stop_reason` | string or `null` | Why the instance stopped. See [stop reasons](#stop-reasons). |

`runcrate instances info <name>` prints the same values as **Max runtime**, **Idle stop**, **Auto stop at** and **Stop reason**.

## Changing auto stop on a running instance

Extend max runtime before a job that will outlast it, or add a stop to an instance you are about to leave working. Anyone with access to the instance can change it, including an API key or an agent.

* Max runtime still counts from when the instance started running, so give the **total** minutes, not extra minutes. A change never stops the instance sooner than 2 minutes from the moment you make it, and `auto_stop_at` shows the new stop time.
* Extending the limit sends a fresh `instance.runtime_ending` alert 10 minutes before the new stop time.
* `null` (CLI `off`) turns a limit off. A field you leave out keeps its value.
* An idle stop change reaches the instance within about 5 minutes, and restarts its idle clock. Some older instances can't turn idle stop on (409, "Idle stop needs a newer launch"), and neither can CPU-only instances; turning it off always works.
* Only a starting or running instance can change (409 otherwise).

```text Dashboard theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
Instances > the instance's menu > Change auto stop
```

```bash CLI theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
runcrate instances auto-stop training-run --max-runtime 8h --idle-stop off
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
await rc.instances.update(instance.id, { maxRuntimeMinutes: 480, idleShutdownMinutes: null });
```

```python Python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
client.instances.update(instance.id, max_runtime_minutes=480, idle_shutdown_minutes=None)
```

```bash API theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
curl -X PATCH https://www.runcrate.ai/api/v1/instances/training-run \
  -H "Authorization: Bearer rc_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_runtime_minutes": 480, "idle_shutdown_minutes": null}'
```

```json MCP theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{
  "tool": "set_instance_auto_stop",
  "arguments": { "id": "training-run", "max_runtime_minutes": 480 }
}
```

## Monthly budgets

A monthly budget caps what one [environment](/docs/account/workspaces) can spend in a calendar month. Give each project or agent its own environment and budget. An API key scoped to that environment, and an agent connected through MCP or the CLI (which works only in the environment picked when it signed in), cannot spend past the budget.

* **Who sets it:** a workspace owner or admin, in [Settings, Environments](https://runcrate.ai/dashboard/settings/environments).
* **What counts:** GPU instances and inference billed to that environment, including inference made with that environment's API keys.
* **The month:** the UTC calendar month. Month to date resets to \$0 at 00:00 UTC on the 1st.
* **At 80%:** owners and admins get an email and your webhooks get a `budget.threshold_reached` event.
* **At 100%:**
  * New launches in the environment fail with HTTP 402 and error code `BUDGET_REACHED`.
  * Running instances in the environment are terminated, with `stop_reason` set to `budget`.
  * Inference calls with the environment's API keys fail with HTTP 402: error type `insufficient_quota`, code `budget_exceeded`.
  * Owners and admins get an email and your webhooks get a `budget.reached` event.

While an environment is over its budget, its running instances cannot be moved to another environment to escape it (409, `BUDGET_REACHED`).

Everything resumes when the next month starts or when an owner or admin raises the budget.

> [!NOTE]
>
> Budgets are set in the dashboard only, on purpose: an agent holding an API key cannot raise its own ceiling. A `PATCH /api/v1/environments/{id}` carrying `monthly_budget_usd` returns 403, code `forbidden`, with the message "Budgets are set in the dashboard". The CLI, SDKs and MCP server can read a budget but cannot change it. An agent signed in through MCP or the CLI cannot raise or remove a budget either, and cannot switch to another environment: naming one returns 404.

### Reading a budget

`GET /api/v1/environments/{id}` returns the budget alongside the environment:

```json theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{
  "data": {
    "id": "e81b4f6a-2c3d-4a9e-b710-5f8c2d1e9a04",
    "name": "agents",
    "is_default": false,
    "monthly_budget_usd": 500,
    "month_to_date_usd": 412.37,
    "budget_remaining_usd": 87.63
  }
}
```

| Field | Type | Meaning |
| - | - | - |
| `monthly_budget_usd` | number or `null` | The cap for the month. `null` means no cap. |
| `month_to_date_usd` | number | Spend in this environment so far this UTC month: GPU instances plus inference. |
| `budget_remaining_usd` | number or `null` | What is left before the cap. `null` when there is no cap. |

Amounts are in USD, rounded to the cent.

From an agent, the MCP tool `get_environment_budget` returns the same three values for the environment in scope, or for the one named by `environment_id`. From the SDKs, `client.environments.get(id)` in Python and `rc.environments.get(id)` in TypeScript.

A launch refused by a budget returns:

```json theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{
  "error": {
    "code": "BUDGET_REACHED",
    "message": "This environment has reached its monthly budget."
  }
}
```

Both SDKs raise `InsufficientCreditsError` for any 402. Check its `code`: `BUDGET_REACHED` means the environment's budget, anything else means the workspace balance.

## Alerts

Every guardrail alert goes to two places:

* **Email** to every active owner and admin of the workspace.
* **Webhooks**: your own HTTPS endpoint, a Slack channel or a Discord channel, added by an owner or admin in [Settings, Webhooks](https://runcrate.ai/dashboard/settings/webhooks). See [webhooks](/docs/webhooks/overview) to set one up.

The guardrail events are `budget.threshold_reached` (an environment at 80% of its budget), `budget.reached` (at 100%), `instance.runtime_ending` (10 minutes before max runtime) and `instance.auto_stopped` (an instance terminated by max runtime, idle stop, a budget or a low balance). Their fields and example payloads are in [webhook events](/docs/webhooks/events). The email goes out whether or not a webhook delivery succeeds.

## Stop reasons

Every instance that has stopped carries a `stop_reason`. It is `null` while the instance is running.

| `stop_reason` | Meaning |
| - | - |
| `user` | Someone in your workspace terminated it, from the dashboard, CLI, SDK, API or MCP. |
| `max_runtime` | It reached its max runtime. |
| `idle` | Idle stop: GPU utilization stayed under 5% with nobody connected for the set time. |
| `budget` | Its environment reached its monthly budget. |
| `low_balance` | The workspace balance ran out. [Auto-recharge](/docs/billing/auto-recharge) prevents this. |
| `failed` | The instance failed and could not be recovered. |
| `system` | Runcrate stopped it for an operational reason, such as a fault on its host. |

## Related

> **[Managing instances](/docs/instances/managing)**
>
> Watch, inspect and terminate running instances.

> **[CLI: instances](/docs/cli/instances)**
>
> The `--max-runtime` and `--idle-stop` flags.

> **[MCP tools](/docs/mcp/tools)**
>
> `create_instance` guardrail parameters and `get_environment_budget`.

> **[API reference](/docs/api-reference)**
>
> Instance and environment fields.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.