# The warm buffer and allocate

Starting a game server takes tens of seconds. A **warm buffer** is a number of units the platform keeps started and idle, so that when a match is ready, one call hands it a server that is already running.

## Switch the policy to `code` mode

The scaling policy lives at `.../units/policy`. Changing it needs a person's token: a service principal may control units but never the policy.

**curl**

```bash
curl -sS -X PATCH "$ZAROZ_API/projects/$PROJECT/service-instances/$SERVICE/units/policy" \
  -H "$AUTH" -H "Content-Type: application/json" -d '{
  "mode": "code",
  "max_units": 12,
  "warm_units": 3,
  "default_on_exit": "destroy",
  "idle_action": "stop",
  "idle_after_seconds": 600,
  "rollout": "replace_idle"
}'
```

**TypeScript**

```ts
const policy = await call("PATCH", `${UNITS}/policy`, {
  mode: "code",
  max_units: 12,
  warm_units: 3,
  default_on_exit: "destroy",
  idle_action: "stop",
  idle_after_seconds: 600,
  rollout: "replace_idle",
});
console.log(policy.status, policy.body.mode);
```

**Python**

```python
status, policy = call("PATCH", f"{UNITS}/policy", {
    "mode": "code",
    "max_units": 12,
    "warm_units": 3,
    "default_on_exit": "destroy",
    "idle_action": "stop",
    "idle_after_seconds": 600,
    "rollout": "replace_idle",
})
print(status, policy["mode"])
```


| Field | Meaning | Default and range |
|---|---|---|
| `mode` | `fixed`, `autoscaled` or `code` | `fixed` at one unit |
| `max_units` | the most units **with a pod** at once: warm, allocated, created by key and running | up to the product maximum and your organization limit |
| `warm_units` | ready, unallocated units to keep on top of everything else | 0 to `max_units` |
| `default_on_exit` | `restart`, `stop` or `destroy` | `restart` |
| `idle_action`, `idle_after_seconds` | stop or destroy a unit that signalled `idle` this long. Never applied to unallocated warm units | none; 60 to 86,400 |
| `destroy_stopped_after_days` | destroy stopped units after this long | 90; 1 to 365 |
| `ready_timeout_seconds` | a unit not Ready this long after its pod started is `Failed` | 300; 10 to 3,600 |
| `readiness` | `auto` or `signal` (Ready only after the unit signals `ready`) | `auto` |
| `rollout` | `rolling`, `replace_idle` or `next_start` | `replace_idle` for `code` |
| `spend_cap_cents` | the most the extra units may cost in a month before growth is refused | the worst case |

Preview the cost before committing, with the same body, on `POST .../units/policy/quote`:

**curl**

```bash
curl -sS -X POST "$UNITS/policy/quote" -H "$AUTH" -H "Content-Type: application/json" -d '{"mode": "code", "max_units": 12, "warm_units": 3}'
```

**TypeScript**

```ts
const quote = await call("POST", `${UNITS}/policy/quote`, { mode: "code", max_units: 12, warm_units: 3 });
console.log(quote.body);
```

**Python**

```python
status, quote = call("POST", f"{UNITS}/policy/quote", {"mode": "code", "max_units": 12, "warm_units": 3})
print(quote)
```


A warm unit is billed like any running extra unit: it holds CPU and memory. Every start bills at least 5 minutes. Stopped units are free.

## Allocate

`POST .../units/allocate` takes **one ready, accepting, unallocated, draining-free warm unit**, preferring the current template and then the longest ready. It sets the unit's `labels` and `user_data` when you give them, and the buffer refills in about a second.

**curl**

```bash
curl -sS -X POST "$UNITS/allocate?wait=delivered" -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: match-7f3a" -d '{
  "labels": {"mode": "2v2"},
  "user_data": {"roster": ["ana", "luis", "marta", "pau"], "map": "dunes"},
  "if_none": "fail"
}'
# 200 {"created": false, "replayed": false, "unit": {"key": "w-k3m9q2xa", "allocation": {...}, ...}}
# 409 {"code": "no-warm-unit", "warm_ready": 0, "warm_starting": 2, "retry_after_seconds": 5, ...}
```

**TypeScript**

```ts
const res = await call(
  "POST",
  `${UNITS}/allocate?wait=delivered`,
  {
    labels: { mode: "2v2" },
    user_data: { roster: ["ana", "luis", "marta", "pau"], map: "dunes" },
    if_none: "fail",
  },
  { "Idempotency-Key": "match-7f3a" },
);
if (res.status === 409 && res.body.code === "no-warm-unit") {
  // none ready: wait res.body.retry_after_seconds and retry, or use if_none: "create"
} else {
  console.log(res.body.unit.key, res.body.unit.private_name, res.body.unit.public_endpoints);
}
```

**Python**

```python
status, res = call(
    "POST",
    f"{UNITS}/allocate?wait=delivered",
    {
        "labels": {"mode": "2v2"},
        "user_data": {"roster": ["ana", "luis", "marta", "pau"], "map": "dunes"},
        "if_none": "fail",
    },
    headers={"Idempotency-Key": "match-7f3a"},
)
if status == 409 and res["code"] == "no-warm-unit":
    pass  # none ready: sleep res["retry_after_seconds"] and retry, or use if_none="create"
else:
    print(res["unit"]["key"], res["unit"]["private_name"], res["unit"]["public_endpoints"])
```


- **Atomic.** Two callers never get the same unit.
- **`Idempotency-Key`** (optional, up to 64 printable characters): retrying with the same key returns the same unit while it lives (`200`, `replayed: true`). Without it, every call allocates a new unit. Always send one from a matchmaker.
- **`?wait=delivered`** holds the response until the unit's agent has written the allocation files (`/zaroz/allocation.json`, `/zaroz/user-data.json`), up to 10 seconds. Without it, `unit.allocation_delivered` arrives as an [event](/guides/units-api/events).
- **`if_none`**: `fail` (default) answers `409 no-warm-unit` with `warm_ready`, `warm_starting` and `retry_after_seconds`. `create` cold-starts a new unit already allocated to you and answers `202` with the unit still starting.
- **An allocation is never undone.** The unit ends by its own exit, a stop or a destroy.
- If the region cannot be reached, the allocation is reverted and you get `503 region-unreachable`. Running units keep serving.

The unit in the response has the `private_name` (`<key>.<workload-slug>`) and the `public_endpoints` to send players to.

## Rollouts when you ship a new image

`replace_idle` replaces stale units that are not allocated and not busy, at most a quarter at a time, new unit first. Allocated or busy units are replaced when they end. `next_start` leaves running units alone and applies the change the next time a unit starts. See [rollout rules](/units-code/rollouts).
