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

# Set Workspace Routing

> Choose how your workspace orders providers, which providers it never uses, and whether a key or Credits pays first.

Your workspace routing settings choose how [routing](https://boxrouter.ai/docs/routing.md) orders providers, which providers it
never uses, and whether a key or Credits pays first. They apply to every sandbox you create. A
request's `preferences` can change the order for that one sandbox, unless `prevent_overrides` is
on, and a provider in `excluded_providers` is never used.

Edit the settings under **Routing** in the dashboard or with the clients below.

**Before You Begin:** A workspace admin key in `BOXROUTER_API_KEY`, a client installed as in the [quickstart](https://boxrouter.ai/docs/quickstart.md), and `jq` for the CLI examples.

## Set the Routing Mode

The mode decides how BoxRouter ranks providers:

| Mode | Meaning |
| - | - |
| Auto (`automatic`) | A random draw weighted toward lower hourly price. |
| Cheapest (`cheapest`) | Lowest hourly price first. |
| Fastest (`fastest`) | Shortest run time measured on reference workloads first. |
| Custom (`custom`) | Your exact provider order, top to bottom. |

Auto draws at random, weighted toward lower hourly list price, so the leader can change from one sandbox to the next. Cheapest and Fastest compare the requested size. Custom uses your `provider_order`.

For one request, `preferences.sort` replaces the ranking criterion used by the mode. An explicit provider `order` takes precedence; the funding order still applies.

With `prevent_overrides` on, a request that sets `preferences.sort` fails with `400 routing_overrides_forbidden`. `details.fields` lists the offending preferences. Even a value equal to the default is refused. `only` and `ignore` are never blocked.

The Routing section has five fields:

| Field | Effect |
| - | - |
| `mode` | `automatic`, `cheapest`, `fastest` or `custom`. |
| `provider_order` | Every provider, first choice first. Only `custom` uses this order. |
| `excluded_providers` | Providers never used, even when a request names them. |
| `allow_fallbacks` | If creating a sandbox fails, try later routes in the same funding group. |
| `prevent_overrides` | Refuse requests whose `preferences` contain `order`, `sort` or `allow_fallbacks`. |

This switches the mode to Cheapest and keeps the other fields' stored values:

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    updated = client.routing.update(mode="cheapest")
    print(updated.mode)
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const updated = await client.routing.update({ mode: 'cheapest' });
  console.log(updated.mode);
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter routing update-settings \
  --body '{"section":"routing","routing":{"mode":"cheapest"}}' | jq '{mode}'
```

The CLI prints JSON. Python and TypeScript print `cheapest`.

Output

```json theme={"theme":"css-variables"}
{
"mode": "cheapest"
}
```

Routing and each provider's funding order are separate sections, each with its own ETag under
`section_etags`. An update sends only the fields you name; the rest keep their stored values. The
reply holds the full settings, with the section's new ETag.

`If-Match` is optional. Passing the section's ETag as `if_match`, `ifMatch` or `--if-match` makes a
stale write fail with `412 routing_settings_stale` instead of overwriting a change made after the
read.

## Provider Keys and Credits Order

For each provider, you decide who pays: your own keys or Credits. The provider's funding order is a
list of your connected keys and, where Credits is offered, one Credits row. Keys above the row are
Prioritized and keys below it are Fallback.

* A new key joins the end of the Prioritized keys.
* Deleting a key removes its row.
* To reorder, update the provider's funding section, which has its own ETag in
  `section_etags.provider_funding`.

BoxRouter combines the provider ranking and the funding orders as follows. Outside Custom mode:

1. Group routes across all providers: Prioritized keys first, then Credits, then Fallback keys.
2. Within each group, order providers by the routing mode.
3. Keep each provider's keys in their list order.

In Custom mode:

1. Pick the provider using `provider_order`.
2. Try that provider's funding order from top to bottom.

If creating a sandbox fails, BoxRouter tries only later routes in the same group, so a failure never
changes who pays. [Spend limits](https://boxrouter.ai/docs/spend-limits.md) cap what the Credits row can spend.

Put every key above the Credits row to pay with your own accounts before Credits. This example does
that for one provider, keeping the keys' order and moving the Credits row last. Enter a provider
ID, which is a key of `provider_funding` in your settings, when prompted:

```python Python theme={"theme":"css-variables"}
import json
from boxrouter import BoxRouter

with BoxRouter() as client:
    provider = input("Provider ID: ")
    order = client.routing.settings().provider_funding[provider].funding_order
    keys_first = sorted(order, key=lambda item: hasattr(item, "supply"))  # Credits row last
    updated = client.routing.update_funding(provider, funding_order=keys_first)
    order = [item.to_dict() for item in updated.provider_funding[provider].funding_order]
    print(json.dumps(order, indent=2))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { createInterface } from 'node:readline/promises';
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const rl = createInterface({ input: process.stdin, output: process.stdout });
  const provider = await rl.question('Provider ID: ');
  rl.close();
  const funding = (await client.routing.settings()).provider_funding[provider];
  if (!funding) throw new Error(`No funding section for ${provider}`);
  const keys = funding.funding_order.filter(item => 'account_id' in item);
  const managed = funding.funding_order.filter(item => 'supply' in item);
  const updated = await client.routing.updateFunding(provider, {
    funding_order: [...keys, ...managed],
  });
  console.log(JSON.stringify(updated.provider_funding[provider]?.funding_order, null, 2));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  printf 'Provider ID: '
  read -r provider
  body=$(boxrouter routing settings | jq -ce --arg p "$provider" '
    select(.provider_funding[$p].funding_order | type == "array") |
    {
      section: "provider_funding",
      provider: $p,
      funding: {
        funding_order: (
          [.provider_funding[$p].funding_order[] | select(has("account_id"))] +
          [.provider_funding[$p].funding_order[] | select(has("supply"))]
        )
      }
    }
  ')
  boxrouter routing update-settings --body "$body" |
    jq --arg p "$provider" '.provider_funding[$p].funding_order'
)
```

Output

```json theme={"theme":"css-variables"}
[
{
"account_id": "<KEY_ACCOUNT_ID>"
},
{
"supply": "managed"
}
]
```

The output lists the provider's new order: your keys first, then the Credits row where the provider
offers one.

The list uses these entries:

* Key: `{"account_id": "<KEY_ACCOUNT_ID>"}`.
* Credits: `{"supply": "managed"}`.

To stop using the Credits row without moving it, set `credits` to `false`: in Python
`client.routing.update_funding(provider, credits=False)`, in TypeScript
`client.routing.updateFunding(provider, { credits: false })`.

A provider that does not offer Credits has no row, and asking for one fails with
`Credits are not offered for this provider`. Each account ID must be one of your keys for that
provider, listed once.

## Regions

Set the region per request in `resources.region`: a country such as `US`, an area such as `EU`,
or one provider's region written `<PROVIDER_ID>:<REGION>`.

An area matches a route only when the provider lists the countries its region covers, so a region
listed only as "Europe" never matches `EU`. Ask for it by name. [Match](https://boxrouter.ai/docs/reference/match.md) shows
the answer without creating anything. This command prints the first exclusion. If there are none,
it prints null fields. Reasons and offered values depend on your routes:

```python Python theme={"theme":"css-variables"}
import json
from boxrouter import BoxRouter

with BoxRouter() as client:
    result = client.sandboxes.match(resources={"region": "EU"})
    route = result.excluded[0] if result.excluded else None
    failed = route.failed[0].to_dict() if route and route.failed else {}
    summary = {
        "reason": failed.get("reason"),
        "asked": failed.get("asked"),
        "offered": failed.get("offered"),
    }
    print(json.dumps(summary, indent=2))
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

const client = new BoxRouter();
try {
  const result = await client.sandboxes.match({ resources: { region: 'EU' } });
  const failed = result.excluded[0]?.failed[0];
  const { reason = null, asked = null, offered = null } = failed ?? {};
  console.log(JSON.stringify({ reason, asked, offered }, null, 2));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter match --resources '{"region": "EU"}' | jq '
  .excluded[0].failed[0] // {} |
  {reason: (.reason // null), asked: (.asked // null), offered: (.offered // null)}
'
```

For example, this exclusion uses `<PROVIDER_ID>` in place of a provider ID:

Output

```json theme={"theme":"css-variables"}
{
"reason": "resource_mismatch",
"asked": "EU",
"offered": ["<PROVIDER_ID>:us"]
}
```

Use `match` to check a sandbox request. `routing preview` reports only Actions runner
availability.

## Next Steps

> [card] [How Routing Works](https://boxrouter.ai/docs/routing.md)
>
> How requirements filter and preferences rank.

> [card] [Connect a Provider Key](https://boxrouter.ai/docs/provider-keys.md)
>
> Connect your own provider account.

> [card] [Cap Your Spend](https://boxrouter.ai/docs/spend-limits.md)
>
> Cap what Credits can spend.


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