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

# Switch, roll back and delete

> Choose which families and models answer calls, take a family back to an older model, and delete what you no longer need.

A switch decides whether a family or a model answers calls. Switch one on to call it, and off to
free its place for another model. Roll a family back when a newer model does worse for you: the
family returns to an older model, and every newer one is discarded. Delete a family or a model when
you no longer need it. A delete can't be undone.

```python Python theme={null}
from sqwishai import OneHot

client = OneHot()
client.models.switch("support-router@3", True)
```

```typescript TypeScript theme={null}
import { OneHot } from "sqwishai";

const client = new OneHot();
await client.models.switch("support-router@3", { on: true });
```

```bash cURL theme={null}
curl --fail-with-body https://api.sqwish.ai/v1/models/support-router@3/switch \
  -H "Authorization: Bearer $SQWISH_API_KEY" -H 'Content-Type: application/json' \
  -d '{"on": true}'
```

[Families](https://docs.sqwish.ai/guides/families.md) explains how a family grows and moves.

## Switch a family or a model on

A family starts switched on. A model answers calls to its number only once you switch it on, as
in the request above.

* `POST /v1/families/support-router/switch` switches the family.
* Switching a model on loads it and checks it first.
* A family switches on as described in [how a family moves](https://docs.sqwish.ai/guides/families.md#how-a-family-moves).
* Send `{"on": false}` to switch either off. Calls stop at once, and its keep warm stops too.

To switch the family off:

```python Python theme={null}
client.families.switch("support-router", False)
```

```typescript TypeScript theme={null}
await client.families.switch("support-router", { on: false });
```

```bash cURL theme={null}
curl --fail-with-body https://api.sqwish.ai/v1/families/support-router/switch \
  -H "Authorization: Bearer $SQWISH_API_KEY" -H 'Content-Type: application/json' \
  -d '{"on": false}'
```

`GET /v1/families` and `GET /v1/families/support-router` show each family's switch as
`switched_on`, and the model it serves. They also show each model's own switch as `on_at`. That is
when the switch went on, or `null` while it is off.

### Places

Each model that answers calls takes one place, however many switches make it answer. Your tier
sets how many places you have. `answering` in `effective_limits` on `/v1/account` gives the number
([tiers](https://docs.sqwish.ai/guides/limits.md#tiers)).

`places` on `/v1/account` counts the places you use. It has `answering`, and `kept_warm` for the
models kept warm. In a shared organisation, its [projects](https://docs.sqwish.ai/guides/projects.md) share its places.

A switch that would take one more place gets `409 live_limit`. Its details list what would answer
in your project once its `switch_off` is applied, the one used longest ago first. Each has
`used_at`: the latest call, evaluation or switch-on of the switches it answers through.
`elsewhere` counts the places the organisation's other projects use.

To switch some off in the same step, send them as `switch_off`. It is a list of families and
models, such as `["support-router@2"]`. Lowering a limit below what answers switches nothing off.

### Checks and errors

To switch only what you last read, add a check. If what you read has changed, the switch is
refused:

* To a family's switch, send `served`, the ID of the model the family serves as you saw it. A
  change gets `409 served_changed`.
* To a model's switch, send `adapter`, the model's ID. A change gets `409 model_changed`.

The same codes answer in two cases, whether or not you sent a check:

* A newer model joined while the switch was loading a latest model the family didn't serve.
* A model, or a family serving one, was switched off before the switch took hold. The switch had
  read it as on.

| Error | Why |
| - | - |
| `422 invalid_reference` | A model's switch was given its ID. It takes the number only |
| `410 model_deleted`, `410 model_discarded` | The model was deleted, or a rollback discarded it |
| `503 deployment_not_ready` | The model failed its check on loading, so it stays off |
| `409 live_limit` | No place is free ([places](#places)) |
| `409 served_changed`, `409 model_changed` | What the switch read has changed. Look at it, then switch it on again |

### Unused switches

A switch nobody uses doesn't turn itself off. A family or a model keeps its place until you switch
it off. So switch off what you no longer call, to free a place.

| Field | What it holds |
| - | - |
| `used_at` | When it was last used. That is its latest production call with an API key that names it as `model`, to `/v1/decide`, `/v1/systemone` or `/v1/hooks/claude-code`. It can also be its latest evaluation that names it, Compare included, or when it was switched on |
| `switches_off_at` | Always `null`: nothing switches off for going unused |
| `off_reason`, `off_at` | `owner` and the time, for a switch you turned off |

* A name that appears only in `fallback` doesn't count as a use. Nor do tries in the console
  playground.
* The family's switch and a model's switch are counted apart. Calls to `support-router` move the
  family's `used_at`, not the `used_at` of `support-router@3`.
* Switching on again needs a place, like any switch-on.

## Roll a family back

```python Python theme={null}
client.families.rollback("support-router", "support-router@2",
                         ["support-router@3", "support-router@4"])
```

```typescript TypeScript theme={null}
await client.families.rollback("support-router",
  { to: "support-router@2", discard: ["support-router@3", "support-router@4"] });
```

```bash cURL theme={null}
curl --fail-with-body https://api.sqwish.ai/v1/families/support-router/rollback \
  -H "Authorization: Bearer $SQWISH_API_KEY" -H 'Content-Type: application/json' \
  -d '{"to": "support-router@2", "discard": ["support-router@3", "support-router@4"]}'
```

A rollback takes a family back to one of its older models. It discards every newer one for good.

* **`to`** names the model by its number, such as `support-router@2`.
* **`discard`** lists the models newer than `to` that aren't deleted or discarded, exactly as you
  confirmed them.
* **`served`** is optional. It is the ID of the model the family serves, as you saw it.

A discarded model answers nothing. Its own switch and keep warm go off, and its hour of keep warm
is billed to the minute. A call to it gets `410 model_discarded`, with no fallback answer. Its
number is never given again. The family lists it with status `discarded` and `discarded_at`.

A fine-tune of the family that is still training is cancelled, and its hold released. An
evaluation of a discarded model that is still running is cancelled too. The rows it scored stay
charged.

A family that is switched on loads `to` and checks it first, the same way a move does. Then it
serves `to`, and keep warm follows. If `to` fails to load, nothing changes.

A rollback takes a place only when a move would. It is never refused at your limits. With no place
free, it still discards the newer models. The same holds for a kept-warm family with no warm place.
The family keeps serving its model while its move to `to` waits, like any move.

A rollback onto a full size goes ahead, unless it would leave the size no room for models that
aren't kept warm.

A family that is switched off serves `to` once it is switched on, as its latest model. The dataset
is unchanged. The family's next fine-tune trains from `to`, and still needs new rows.

| Error | Why |
| - | - |
| `422 invalid_reference` | `to` is an ID, a name, an alias or another family's model |
| `410 model_deleted`, `410 model_discarded` | `to` was deleted, or discarded by a rollback |
| `409 rollback_changed` | `discard` isn't exactly what the rollback would discard, perhaps because a model joined meanwhile. Nothing changes. The details list what it would discard and the work it would cancel |
| `409 nothing_to_roll_back` | `to` is the latest model. If every model `discard` names was discarded already, you get the family as it is instead. That happens when the same rollback is sent again after a lost answer |
| `409 served_changed` | The family serves another model than the `served` you sent |
| `503 warm_capacity` | The family is kept warm, and the rollback would leave the size no room for models that aren't kept warm. Nothing changes |

## Delete a family

```python Python theme={null}
family = client.families.get("support-router")
client.families.delete("support-router", dataset_id=family["dataset_id"],
                       latest=family["latest"]["adapter"] if family["latest"] else None)
```

```typescript TypeScript theme={null}
const family = await client.families.get("support-router");
await client.families.delete("support-router",
  { dataset_id: family.dataset_id, latest: family.latest?.adapter ?? null });
```

```bash cURL theme={null}
curl --fail-with-body https://api.sqwish.ai/v1/families/support-router \
  -H "Authorization: Bearer $SQWISH_API_KEY" -H 'Content-Type: application/json' \
  -X DELETE -d '{"dataset_id": "ds_...", "latest": "ft-..."}'
```

Send `dataset_id`, the dataset the family grew on, as you saw it. Send `latest`, the ID of the
family's latest model as you saw it, or `null` for none. Then a delete never takes a model you
didn't see.

What happens depends on whether a model ever joined the family. A model that failed its gate never
joined.

| The family | After the delete |
| - | - |
| No model ever joined | It's gone, and its name is free. A new family given the name starts with no jobs of its own. The data the deleted family's jobs used counts as new for it. Deleting it again gets `404 not_found` |
| A model joined | It goes with every model of its line, including those a rollback discarded. You can't bring them back. Its name is never given again: a new family with that name gets `409 family_exists`. Deleting it again gives the same answer and changes nothing |

After a family that a model joined is deleted:

* Calls to the family get `410 family_deleted`. So does reading, switching, keeping warm, rolling
  back or continuing it.
* Calls to its models get `410 model_deleted`, with no fallback answer.
* `GET /v1/families` leaves it out.
* Its models no longer count against your storage allowance.

Either way its dataset stays. So do its jobs in the fine-tuning jobs list, with their charges and
`family_deleted: true`. The answer is `{"id": "support-router", "object": "family", "deleted": true}`.

| Error | Why |
| - | - |
| `409 family_answers` | The family serves a model, or one of its models' own switches is on. Switch them off first. The details list what answers |
| `409 family_training` | One of its fine-tunes is queued or running. Cancel it first |
| `409 version_in_use` | An evaluation is scoring one of its models. Delete the family once the evaluation ends |
| `409 family_changed` | Its dataset isn't the `dataset_id` you sent, as for a family deleted and named again on another dataset. Or it has another latest model than the `latest` you sent |

### Delete the dataset instead

When you delete a family's dataset instead, the family keeps its models, and they answer as
before. The family can't continue. Its `continue` state is `dataset_deleted`, and continuing it
gets `410 dataset_deleted`. Start a new family from its latest model, such as `support-router@4`.

A dataset can't be deleted while a fine-tune, an evaluation or a prompt-tuning job reads it.
Deleting it then gets `409 in_use`.

## Delete a model

```python Python theme={null}
client.models.delete("support-router@2")
```

```typescript TypeScript theme={null}
await client.models.delete("support-router@2");
```

```bash cURL theme={null}
curl --fail-with-body https://api.sqwish.ai/v1/models/support-router@2 \
  -H "Authorization: Bearer $SQWISH_API_KEY" -X DELETE
```

Delete a model by its number. Add `?adapter=ft-...`, the ID you saw for that number. Then the call
changes nothing if the number names another model.

Deleting a model removes it for good, with its weights, its training specification and its
held-out records.

* A call to it gets `410 model_deleted`, with no fallback answer. So does an evaluation or
  fine-tune that names it.
* A decision already under way on it finishes and is charged. A fine-tune that started from it
  before also finishes.
* It no longer counts against your storage allowance.
* Deleting again changes nothing, so a retry is safe.

The family's line keeps the model's place, with only these fields:

| Field | Holds |
| - | - |
| `version`, `ref` | Its number |
| `adapter` | Its ID |
| `parent` | The model it trained from |
| `status`, `deleted_at` | `deleted`, and when |

| Error | Why |
| - | - |
| `422 invalid_reference` | The model was named by its ID, by a family's name, or with anything after `@` but a number. Each could point at another model by the time a retry arrives |
| `409 model_changed` | The number names another model than the `adapter` you sent |
| `409 latest_model` | It is its family's latest model. [Roll the family back](#roll-a-family-back) instead, which discards it |
| `409 model_answers` | Its own switch is on, or its family serves it while switched on. Switch its own switch off if it is on. A model its family serves can be deleted once the family has moved to its latest model, or is switched off |
| `409 version_in_use` | An evaluation is still scoring it. Delete it once that ends |

A switched-off family whose model you delete serves its latest model once you switch it on.


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