# Create by key, start, stop and destroy

A unit made by your code has a **key**: its name, chosen by you, that never changes. A hub that opens a private world per player uses `plot-<player>`. A job runner uses `job-<id>`. The key is also the unit's private network name: `<key>.<workload-slug>`.

These routes need the policy in [`code` mode](/guides/units-api/warm-buffer-and-allocate#switch-the-policy-to-code-mode).

## Create a unit by key

`POST .../units` is **idempotent on the key**, which makes it safe to call on every "player opens a plot".

**curl**

```bash
curl -sS -X POST "$UNITS" -H "$AUTH" -H "Content-Type: application/json" -d '{
  "key": "plot-ana",
  "labels": {"kind": "plot", "owner": "ana"},
  "user_data": {"world": "s3://my-bucket/plots/ana"},
  "start": true,
  "on_exit": "stop"
}'
# 201 {"created": true,  "unit": {...}}   a new unit
# 200 {"created": false, "unit": {...}}   the live unit with that key already existed
```

**TypeScript**

```ts
import { call, UNITS } from "./zaroz";

const { status, body } = await call("POST", UNITS, {
  key: "plot-ana",
  labels: { kind: "plot", owner: "ana" },
  user_data: { world: "s3://my-bucket/plots/ana" },
  start: true,
  on_exit: "stop",
});
console.log(status, body.created, body.unit.key, body.unit.state);
```

**Python**

```python
from zaroz import call, UNITS

status, body = call("POST", UNITS, {
    "key": "plot-ana",
    "labels": {"kind": "plot", "owner": "ana"},
    "user_data": {"world": "s3://my-bucket/plots/ana"},
    "start": True,
    "on_exit": "stop",
})
print(status, body["created"], body["unit"]["key"], body["unit"]["state"])
```


| Situation | Answer |
|---|---|
| No live unit has this key | `201 {created: true}` and the unit starts (unless `"start": false`) |
| A live unit has this key and is running, input equal or omitted | `200 {created: false}` with the current unit |
| It is **stopped** and `start` is true | It starts. `labels` and `user_data`, when you send them, replace the stored ones. `200 {created: false}` |
| It is running and you send different `labels` or `user_data` | `409 unit-input-differs`. A running unit's input changes only by allocate or [`PATCH` labels](/guides/units-api/labels-and-user-data) |

Fields: `key` (required), `labels`, `user_data`, `start` (default `true`), `on_exit` (`restart`, `stop` or `destroy`; default the policy's `default_on_exit`).

`"start": false` creates the unit **stopped**: it holds no slot and costs nothing until you start it.

`on_exit` is what happens when the unit's process exits: `restart` keeps the pod and restarts the process in place, `stop` and `destroy` end the unit (and release the slot). Use `destroy` for a match or a job, `stop` for a plot you will come back to.

## Read a unit

**curl**

```bash
curl -sS "$UNITS/plot-ana" -H "$AUTH"
```

**TypeScript**

```ts
const { body: unit } = await call("GET", `${UNITS}/plot-ana`);
console.log(unit.state, unit.labels, unit.user_data, unit.allocation);
```

**Python**

```python
_, unit = call("GET", f"{UNITS}/plot-ana")
print(unit["state"], unit["labels"], unit["user_data"], unit["allocation"])
```


The response carries `state`, `desired_state`, `labels`, `user_data`, `origin` (`policy`, `code` or `warm`), `allocation` (null until allocated), `private_name`, `public_endpoints`, `billing_class` (`included` or `extra`), the current pod and the restart count.

## Stop, start and destroy

**curl**

```bash
curl -sS -X POST "$UNITS/plot-ana/stop" -H "$AUTH"      # drain, then stop: costs nothing while stopped
curl -sS -X POST "$UNITS/plot-ana/start" -H "$AUTH" -H "Content-Type: application/json" -d '{}'
curl -sS -X DELETE "$UNITS/plot-ana" -H "$AUTH"         # drain, then destroy: final
```

**TypeScript**

```ts
await call("POST", `${UNITS}/plot-ana/stop`);
await call("POST", `${UNITS}/plot-ana/start`, {});          // {} starts it as it is
await call("DELETE", `${UNITS}/plot-ana`);                   // final; the key may be reused afterwards
```

**Python**

```python
call("POST", f"{UNITS}/plot-ana/stop")
call("POST", f"{UNITS}/plot-ana/start", {})   # {} starts it as it is
call("DELETE", f"{UNITS}/plot-ana")           # final; the key may be reused afterwards
```


- **stop** drains the unit (it receives a draining notice and stops accepting new work), then stops it. Key, labels and `user_data` are kept, the slot is released and **nothing is billed while it is stopped**. Stopping a stopped unit is a no-op `200`.
- **start** wakes a stopped unit. Starting a running unit is a no-op `200`. The body may carry `labels` and `user_data`, each replacing the stored value whole. Send `{}` to start it as it is.
- **destroy** drains and then removes the unit. It is final: afterwards the key answers `404` and may be reused. Success is `204`.

**curl**

```bash
curl -sS -X POST "$UNITS/plot-ana/start" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"labels": {"kind": "plot", "owner": "ana", "tier": "vip"}, "user_data": {"world": "s3://my-bucket/plots/ana-v2"}}'
```

**TypeScript**

```ts
await call("POST", `${UNITS}/plot-ana/start`, {
  labels: { kind: "plot", owner: "ana", tier: "vip" },
  user_data: { world: "s3://my-bucket/plots/ana-v2" },   // replaces the stored value whole
});
```

**Python**

```python
call("POST", f"{UNITS}/plot-ana/start", {
    "labels": {"kind": "plot", "owner": "ana", "tier": "vip"},
    "user_data": {"world": "s3://my-bucket/plots/ana-v2"},  # replaces the stored value whole
})
```


Every call that starts a pod (create with `start`, `start`, a refill, allocate with `if_none: create`) is checked against your limits and spend cap: see [Errors](/guides/units-api/errors) for `container-unit-limit`, `org-unit-limit`, `start-rate` and `spend-cap`. A refusal is never a partial success, and running units are never touched by a limit.

## Stopped units pile up

A Workload may hold 1,000 stopped units (`stopped-unit-limit`). Stopped units that stay stopped for longer than the policy's `destroy_stopped_after_days` (default 90) are destroyed, and a `unit.destroyed` event with reason `expired` tells you.
