---
title: "Get the lab-view occupancy status for a project"
method: GET
path: "/projects/{project_id}/lab/status"
tags: ["Lab"]
---

# Get the lab-view occupancy status for a project

`GET /projects/{project_id}/lab/status`

Return the project's sites, cyclers, and channels with occupancy state.

Each channel carries a derived ``free`` / ``occupied`` / ``stale`` state
(there is no live telemetry; state comes from linked measurements'
``end_time`` and update recency). Includes per-cycler and project-level
occupancy counts. Each occupied/stale channel's measurement carries a
``watched`` flag for the requesting user, so the "My Channels" filter needs
no extra round-trip.

## Path parameters

- `project_id` string, required

## Response `200`

Successful Response

- LabStatus — The full Lab-view tree for a project: sites -> cyclers -> channels. Returned by ``GET /projects/{project_id}/lab/status`` in a single call so the wall can render without N per-channel round-trips.
  - `sites` LabSite[] — Sites with this project's cyclers
    - `id` string, required — Site ID
    - `name` string, required — Site name
    - `cyclers` LabCycler[] — Cyclers at this site owned by the project
      - `id` string, required — Cycler ID
      - `name` string, required — Cycler name
      - `manufacturer` string, nullable — Cycler manufacturer (make)
      - `model` string, nullable — Cycler model (type)
      - `serial_number` string, nullable — Manufacturer serial number
      - `last_calibrated_at` string, date-time, nullable — When the cycler was last calibrated
      - `calibration_interval_days` integer, nullable — Configured calibration cadence, carried so the record-calibration dialog can prefill it without a second fetch.
      - `calibration_due_at` string, date-time, nullable — When calibration next falls due. Null when the cycler is not on a calibration schedule or has never been calibrated.
      - `channel_count` integer, required — Total number of channels
      - `occupied` integer, required — Channels in the occupied state
      - `stale` integer, required — Channels in the stale state
      - `free` integer, required — Channels in the free state
      - `out_of_commission` integer — Channels that are out of commission
      - `channels` LabChannel[] — Channels on this cycler
        - `id` string, required — Channel ID
        - `name` string, required — Channel name
        - `state` 'free' | 'occupied' | 'stale' | 'out_of_commission', required — Derived occupancy state of a channel in the Lab view. There is no live telemetry; state is derived from the channel's linked measurements (a measurement references its channel via ``channel_id``): - ``free`` — no linked measurement, or every linked measurement has an ``end_time`` set (all tests finished). The channel is available. - ``occupied`` — a linked measurement has ``end_time`` null and was updated within the staleness window (data is fresh); a cell is on test now. - ``stale`` — a linked measurement has ``end_time`` null but has not been updated within the staleness window. Likely a stopped or silently failed test, or one someone forgot to mark complete; needs a human to resolve (mark it complete to free the channel). - ``out_of_commission`` — the channel is deliberately out of service (broken / maintenance). This overrides any derived state above.
        - `measurement` LabMeasurementSummary — Slim view of the measurement occupying a channel, for the Lab wall. Carries only what a channel tile needs; the full measurement is fetched separately on the channel detail page.
          - `id` string, required — Measurement ID
          - `name` string, required — Measurement name
          - `start_time` string, date-time, nullable — When the test started, if known
          - `estimated_end_time` string, date-time, nullable — Forecast finish time, if an estimate has been computed
          - `updated_at` string, date-time, required — When the measurement was last updated (drives recency)
          - `cell_instance_id` string, required — ID of the cell instance on test
          - `cell_instance_name` string, nullable — Name of the cell instance on test
          - `cell_specification_name` string, nullable — Name of the cell instance's cell specification
          - `protocol_name` string, nullable — Protocol name from the measurement's protocol dict
          - `watched` boolean — Whether the requesting user is watching this measurement. Drives the Lab 'My Channels' filter (a watch on the live measurement reads as watching its channel).
          - `requested_by_email` string, nullable — Email of the planned-measurement requester, when this run is linked to a plan.
          - `program_name` string, nullable — Catalog program name when the measurement (or its linked plan) has a program.
        - `notes` string, nullable — Free-text notes on the channel
        - `out_of_commission` boolean — Whether the channel is out of service
        - `max_amps` number, nullable — Maximum rated current (A)
        - `min_volts` number, nullable — Minimum rated voltage (V)
        - `max_volts` number, nullable — Maximum rated voltage (V)
  - `occupied` integer — Total occupied channels across the project
  - `stale` integer — Total stale channels across the project
  - `free` integer — Total free channels across the project
  - `out_of_commission` integer — Total out-of-commission channels across the project

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/ionworks/apis/fastapi.md) · [All operations](https://skmtc.dev/ionworks/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/ionworks/fastapi/revisions/7337a3cbdaf2/schema)
