---
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`

## Request

- Base URL: `https://api.ionworks.com`
- URL: `https://api.ionworks.com/projects/{project_id}/lab/status`
- Auth: none declared

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, uuid, 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

## Changes

- **2026-09-30** `ea3d377fec1a`: 1 breaking
  - for the `path` request parameter `project_id`, the format changed from no format to `uuid`

[Operation history](https://skmtc.dev/ionworks/apis/ionworks-api/changes/projects/:project_id/lab/status/get.md)

---

[API](https://skmtc.dev/ionworks/apis/ionworks-api.md) - [All operations](https://skmtc.dev/ionworks/apis/ionworks-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ionworks/apis/ionworks-api/revisions/abebbbc5f60a?raw)
