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

# Channel service

> Take a channel out of service with a reason, and read the incident history that records every outage span

A channel that is broken, being serviced, or otherwise unusable can be marked
**out of service** so it stops appearing as available capacity in the
[Lab view](/operate/lab-view). Every transition in and out of that state is
recorded as an **incident**, giving you a per-channel downtime history.

## Marking a channel out of service

Toggle `out_of_commission` to remove a channel from the pool without deleting
it or breaking historical measurements that reference it. Every change to
`out_of_commission` also opens or closes a row in the channel's
[incident history](#channel-incident-history), so you can look back and see
when — and why — a channel was down.

Alongside `out_of_commission`, pass three optional fields to describe the
outage:

| Field               | Applies when          | Description                                                                                                                   |
| ------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `incident_category` | Taking out of service | Cause of the outage. One of `hardware_failure`, `maintenance`, `calibration`, `decommissioned`, `other`. Defaults to `other`. |
| `incident_notes`    | Taking out of service | Free-text detail about what happened.                                                                                         |
| `resolution_notes`  | Returning to service  | What was done to bring the channel back.                                                                                      |

These three fields **only annotate a transition** in `out_of_commission` — they
are ignored on a PATCH that doesn't change the flag. Re-sending the value a
channel already has is a no-op, not a new outage.

```python theme={null}
# Take a channel out of service and record why.
client.channel.update(
    channel_id,
    {
        "out_of_commission": True,
        "incident_category": "hardware_failure",
        "incident_notes": "Cell holder cracked; awaiting replacement part",
    },
)

# Later, bring it back and record the fix.
client.channel.update(
    channel_id,
    {
        "out_of_commission": False,
        "resolution_notes": "Replaced cell holder and recalibrated",
    },
)
```

The Lab view shows the channel as `out_of_commission` regardless of any
in-flight measurement.

<Note>
  `out_of_commission` cannot be set at channel creation — the create endpoint
  rejects it with a `BAD_REQUEST`. Create the channel first, then PATCH it to
  take it out of service so the outage is recorded in the incident history.
</Note>

Trying to mark a channel out of commission while a
[time-series measurement](/data/measurements) is still open on it returns a
`CONFLICT` (HTTP 409) — close the measurement first (patch its `end_time`).

Use `incident_category` of `decommissioned` for a permanent retirement rather
than a repair-in-progress: the incident stays open indefinitely and is excluded
from repair-time statistics.

## Channel incident history

Every transition of `out_of_commission` is recorded as an **incident** — one
row per outage, opened when the channel goes down and closed when it comes
back. Use the history to audit downtime, attribute outages to a cause, or
compute per-channel mean time to repair.

At most one incident can be open per channel at a time (the current outage).
Sending `out_of_commission: true` for a channel that's already down is a no-op
— it does not fragment the existing outage into a new row.

### Incident fields

| Field              | Description                                                                                                                                                        |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`               | Unique identifier for the incident.                                                                                                                                |
| `channel_id`       | The channel that went out of service.                                                                                                                              |
| `category`         | Cause of the outage: `hardware_failure`, `maintenance`, `calibration`, `decommissioned`, or `other`.                                                               |
| `notes`            | Free-text detail captured when the channel went down (from `incident_notes`).                                                                                      |
| `started_at`       | When the channel went out of service.                                                                                                                              |
| `started_by`       | User who took the channel out of service (may be `null` for system actions).                                                                                       |
| `resolved_at`      | When the channel returned to service. `null` means the outage is still open.                                                                                       |
| `resolved_by`      | User who returned the channel to service.                                                                                                                          |
| `resolution_notes` | What was done to fix it.                                                                                                                                           |
| `is_estimated`     | `true` for backfilled rows whose `started_at` is derived from the channel's last-modified timestamp and is only an upper bound. Excluded from repair-time metrics. |

### Reading incident history

List a channel's incidents through the HTTP API, most recently started first:

```bash theme={null}
GET /channels/{channel_id}/incidents?limit=20&offset=0
```

```python theme={null}
# There is no dedicated incidents sub-client yet; call the endpoint directly.
# `client.get` takes the full path only, so put query parameters in the string.
history = client.get(f"/channels/{channel_id}/incidents?limit=20")

for incident in history["items"]:
    span = incident["resolved_at"] or "still open"
    print(f"{incident['started_at']} → {span}  ({incident['category']})")

print(f"{history['count']} of {history['total']} incidents shown")
```

The response is a paginated envelope (`items`, `count`, `total`). Pass `limit`
(1–100, default 20) and `offset` to page through longer histories.

<Note>
  Channels that already existed before incident history was introduced have a
  single **backfilled** row with `is_estimated: true`. Its `started_at` is the
  channel's last-modified time and is only an upper bound on when the outage
  actually began — treat those rows as "we know it was down by this time" and
  skip them when computing repair-time statistics.
</Note>

## Servicing a whole cycler

Calibration and preventive maintenance are performed on the *instrument*, so a
40-channel cycler going in for its annual calibration is one event — not 40
unrelated channel outages. Use the cycler service methods rather than marking
each channel out of service by hand.

Event types are `calibration`, `preventive_maintenance`, `firmware`, `repair`,
and `other`.

### Taking a cycler out of service

`start_service` opens one event and an incident on **every** channel of the
cycler, so the whole instrument reads as down:

```python theme={null}
event = client.cycler.start_service(
    cycler_id,
    event_type="calibration",
    scheduled_for="2026-09-01T09:00:00Z",
    notes="Annual calibration",
)
```

`scheduled_for` is required — a cycler must not sit out of service with no date
attached. Channels already out of service for their own faults keep their more
specific incident and stay out when the service completes.

### Recording service as done

```python theme={null}
client.cycler.complete_service(
    cycler_id,
    event_type="calibration",
    calibration_interval_days=365,
)
```

`complete_service` takes two paths depending on whether the cycler is currently
out of service:

| Situation                                         | What happens                                                                                                                                      |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| An open event exists (you called `start_service`) | The event is completed and every channel incident it opened is closed in one write, so the cycler cannot end up half returned to service.         |
| No open event                                     | A single already-completed event is recorded — the "I calibrated this, log it" case. You do not need to open an event purely so it can be closed. |

For a `calibration` event this also advances the cycler's `last_calibrated_at`,
and therefore `calibration_due_at`. That is the only supported way the
calibration clock moves.

### Reading service history

```python theme={null}
history = client.cycler.list_service_events(cycler_id, limit=20)

for event in history.items:
    status = "in progress" if event.performed_at is None else "done"
    print(event.event_type, status)
```

Events come back newest first. A cycler has at most one open event at a time —
the one whose `performed_at` is `None` is the current service.

<Note>
  A cycler that is in service reports as unavailable, so scheduling a
  measurement on it fails when you schedule rather than at the bench.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Equipment" icon="server" href="/operate/equipment">
    Sites, cyclers, channels, and how measurements link to them.
  </Card>

  <Card title="Lab view" icon="grid-2" href="/operate/lab-view">
    Live channel occupancy for the project.
  </Card>
</CardGroup>
