> ## 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.

# How Routing Works

> BoxRouter skips any route that can't do what you asked, orders the rest by your preferences, and tries the next only if creating the sandbox fails.

Every sandbox runs on one route: a provider plus the account that pays for it, either your own
provider key or Credits. BoxRouter skips any route that can't do what you asked, puts the rest in
order, and tries them one at a time until one creates the sandbox.

Sandboxes on different providers share one [Volume](https://boxrouter.ai/docs/volumes.md).

![Files on a Local Disk: your code runs a sandbox on Provider A, and its files sit on Provider A's local disk. Files in a Volume: your code calls BoxRouter, which runs the sandbox on Provider A or Provider B, and both mount the same Volume.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/shared-volume-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=07051fe0beaa6ac48a34be1fd576078f)

![Files on a Local Disk: your code runs a sandbox on Provider A, and its files sit on Provider A's local disk. Files in a Volume: your code calls BoxRouter, which runs the sandbox on Provider A or Provider B, and both mount the same Volume.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/shared-volume-mobile-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=97ddf0094c44e8f49e0b06870ff4d103)

A local disk ends with its sandbox; a Volume outlives it.

This page covers:

* [Which routes qualify](#requirements-filter-routes) and [what happens when none does](#when-no-route-is-left)
* [How the remaining routes are ranked](#preferences-rank-what-is-left)
* [What happens when creation fails](#fallback-when-creating-a-sandbox-fails)
* [Where the decision is recorded](#read-the-route-result)

![Illustrative routes are checked against the whole request. Ineligible routes are excluded; preferences order eligible routes before creation. No eligible route means nothing is created.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/routing-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=bacc42db681173440bae0853b568bace)

![Illustrative routes are checked against the whole request. Ineligible routes are excluded; preferences order eligible routes before creation. No eligible route means nothing is created.](https://mintcdn.com/nv-4965711d/RMVlH8Z1jdQT60zr/images/diagrams/routing-mobile-light.svg?fit=max\&auto=format\&n=RMVlH8Z1jdQT60zr\&q=85\&s=f90089f51cea71030084e50ff98985ca)

Requirements filter routes; preferences order those that remain. The routes shown are examples.

## Requirements Filter Routes

A route stays only if it meets everything in your create request:

| Part of the Request | The Route Must |
| - | - |
| `requirements` | Provide each optional capability you named. |
| `resources` | Offer the size and region you asked for. |
| `lifetime_seconds` | Allow a lifetime that long. |
| `image` | Support the image you named. |
| `privacy` | Meet the privacy setting you asked for. |
| `route` | Be the route or account you pinned. |

Workspace restrictions apply too: excluded providers, privacy settings, spend limits and which provider accounts are connected.

BoxRouter never loosens a requirement to make a route fit. A cheaper or faster route that misses one is
out: a sandbox that silently lacks a capability fails later, inside your workload.

## When No Route Is Left

The create request fails with `409 no_eligible_route` before any provider is called, so nothing needs
cleanup. The error lists each excluded route and the change that would make it eligible.

[Match](https://boxrouter.ai/docs/reference/match.md) runs the same filter and creates nothing. The examples on this page use a
client from the [quickstart](https://boxrouter.ai/docs/quickstart.md), and the CLI examples also use `jq`. Output is trimmed to the
fields discussed. Your provider's ID replaces `<PROVIDER_ID>`, and its sizes can differ:

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

with BoxRouter() as client:
    result = client.sandboxes.match(resources={"cpu_min": 512})
    print(json.dumps(result.to_dict(), 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: { cpu_min: 512 } });
  console.log(JSON.stringify(result, null, 2));
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter match --resources '{"cpu_min": 512}'
```

Output

```json theme={"theme":"css-variables"}
{
"eligible": [],
"excluded": [{"provider": "<PROVIDER_ID>", "failed": [{"reason": "resource_mismatch",
"asked": {"cpu_min": 512}, "offered": [{"cpu": [1, 8], "memory_mib": [128, 8192]}]}]}],
"nearest": [{"change": {"resources.cpu_min": 8}, "then_eligible": ["<PROVIDER_ID>"]}]
}
```

Creating a sandbox with the same request fails with these lists under `details`. Its `fix` comes from
the nearest change: `Set resources.cpu_min to 8; then <PROVIDER_ID> fits.` A workspace with no
provider key and no Credits gets the same error, as does a request that names a provider you have not
connected.

## Preferences Rank What Is Left

BoxRouter tries the routes that fit in three groups, by who pays: your Prioritized keys, then
Credits, then your Fallback keys. Within a group, the workspace's routing mode ranks the providers. Custom is the exception: it follows
your own provider order, then each provider's funding order. [Routing settings](https://boxrouter.ai/docs/routing-settings.md)
covers the modes and how a key lands in a group.

A request's `preferences` adjust that ranking:

| Preference | Effect |
| - | - |
| `only` | Allows only these providers. Every other route is excluded. |
| `ignore` | Excludes these providers, even when `only` or `order` names them. |
| `order` | Tries these providers first, in this order. Other eligible providers follow. |
| `sort` | Ranks providers by one of the sorts below. Explicit provider order takes precedence; funding order still applies. |
| `allow_fallbacks` | `false` means one attempt only: if the first route fails, no other is tried. |

A request can narrow what the workspace allows but never widen it:

* A request can never bring back a provider the workspace excludes.
* If the workspace sets `allow_fallbacks` to `false`, a request cannot turn it back on.
* With **Prevent Overrides** on, a request containing `order`, `sort` or `allow_fallbacks` is refused
  with `400 routing_overrides_forbidden`, even if the value matches the workspace's.

`sort` takes one of two values:

| Sort | Meaning |
| - | - |
| Price (`price`) | Lowest hourly price first. |
| Time (`time`) | Shortest run time measured on reference workloads first. |

`sort: "price"` compares the hourly list price of the size each route would grant. With no size there
is nothing to compare, so name `resources` when price matters. It does not compare what your provider
invoices you.

`match` shows where each eligible route ranks: its `position`, a `reason` such as
`lowest_price` or `explicit_order`, and the price compared. With one eligible route there is nothing
to compare:

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

with BoxRouter() as client:
    result = client.sandboxes.match(preferences={"sort": "price"}).to_dict()
    for route in result["eligible"]:
        print(json.dumps(route["ranking"], indent=2))
```

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

const client = new BoxRouter();
try {
  const result = await client.sandboxes.match({ preferences: { sort: 'price' } });
  for (const route of result.eligible) {
    console.log(JSON.stringify(route.ranking, null, 2));
  }
} finally {
  await client.close();
}
```

```bash CLI theme={"theme":"css-variables"}
boxrouter match --preferences '{"sort": "price"}' | jq '.eligible[].ranking'
```

Output

```json theme={"theme":"css-variables"}
{
"position": 1,
"reason": "only_eligible_route",
"explanation": "Nothing else to rank it against in its group.",
"price": {
"usd_per_hour": null,
"kind": "unknown",
"basis": "No price: only one route is eligible, so nothing is compared"
}
}
```

| Reason | Meaning |
| - | - |
| Lowest price (`lowest_price`) | Lowest hourly price of the routes ranked together. |
| Weighted price (`weighted_price`) | Automatic mode leads with a draw weighted toward lower prices, so the leader can change between creates; the routes behind it follow by price. |
| Lowest time (`lowest_time`) | Shortest run time measured on reference workloads first. |
| Time unmeasured (`time_unmeasured`) | No current run time was measured for this provider on reference workloads, so it follows the measured routes, ordered by ready time, then price. |
| Predicted completion (`predicted_completion`) | Soonest predicted completion among the routes ranked together. |
| Predicted ready time (`predicted_ready_time`) | Ordered by predicted time to ready from this workspace's history. |
| Explicit order (`explicit_order`) | Placed by the provider order you set in Routing or in the request. |
| After named providers (`after_named_providers`) | Behind the providers you named in your order; the others keep BoxRouter's ranking. |
| Provider key order (`provider_key_order`) | Another key of a provider already placed; one provider's keys are tried in the order you set in Routing. |
| Only eligible route (`only_eligible_route`) | Nothing else to rank it against in its group. |
| Health deprioritized (`health_deprioritized`) | Recent failures or degraded health put it behind healthier routes. |
| Price unknown (`price_unknown`) | No price quote at this size, so routes with a quote are tried first. |
| No signals (`no_signals`) | No price or time signals exist yet, so it keeps BoxRouter's default order. |

`route.selection.ranked_by` records the ordering rule.

| `ranked_by` | Meaning |
| - | - |
| Price (`price`) | Lowest hourly price first. |
| Time (`time`) | Shortest run time measured on reference workloads first. |
| Providers (`providers`) | The hard list order. |
| Order (`order`) | A soft provider preference. |

## Fallback When Creating a Sandbox Fails

If creating a sandbox on a route fails without producing one, BoxRouter tries the next route in the
ranking. The next route can be another account of the same provider. BoxRouter also moves on after an
unclear failure, such as a timeout.

The provider may finish creating the abandoned sandbox later, and it may incur charges until it is
destroyed. BoxRouter looks for it by operation label and requests cleanup, but cleanup can be
delayed or unconfirmed. If you need help tracing an attempt, contact support with the
[diagnostic identifiers](https://boxrouter.ai/docs/errors.md#keep-the-diagnostic-identifiers).

The next route always comes from the same group as the first, so the same party pays. A request that
started on one of your Prioritized keys never moves to Credits or a Fallback key.

BoxRouter stops trying when:

* the request pins one route or account with `route.id` or `route.account_id`,
* `allow_fallbacks` is `false`,
* an attempt limit is reached, or
* no eligible route is left in the group.

If every attempt fails, the error is the last attempt's, and the earlier ones are in
`details.attempts`.

## Read the Route Result

A sandbox stays on the route it was created on, and `route.selection` records the decision. It holds
two lists:

* `attempts` are the routes where creating the sandbox failed before the one that worked.
* `preparation_skips` are the routes BoxRouter skipped before calling any provider. Each also has a
  `reason_code`.

An entry in either list names `route_id`, `account_id` (`null` for Credits) and a `cause`:

| `cause` | Meaning |
| - | - |
| `key_rejected` | The provider refused the saved key. Replace it under **Provider Keys**. |
| `plan_unbuildable` | The route fit your request, but the sandbox could not be prepared for it, for example because that provider cannot prepare the image. |
| `privacy_denied` | The workspace's privacy settings refused the route. |
| `not_ready` | The provider session was not ready in time. |
| `not_dispatched` | BoxRouter never sent the request to the provider, for a reason none of the other causes describes. |
| `provider_create_failed` | BoxRouter asked the provider, and creating the sandbox failed. |

Treat a `cause` you do not recognize as a failed attempt, because new ones can be added.

## Next Steps

> [card] [Set Workspace Routing](https://boxrouter.ai/docs/routing-settings.md)
>
> Set the workspace mode and funding order.

> [card] [Capabilities and Isolation](https://boxrouter.ai/docs/provider-support.md)
>
> See what you must request and which routes fit.

> [card] [Recover from Errors](https://boxrouter.ai/docs/errors.md)
>
> Read a no\_eligible\_route refusal.


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