API Reference (Unit /unit_api)
Conventions
- All Pioreactors have the
/unit_apiendpoints exposed. - Async endpoints return
202 Acceptedwith atask_idandresult_url_path. - Poll
GET /unit_api/task_results/{task_id}untilstatusissucceededorfailed. - File download endpoints return binary bodies; use the response content-type to handle them.
- Path parameters are shown inline in the endpoint URL.
- Request/response examples are the canonical shapes; omit optional fields you do not need.
- Errors have the following schema:
{
"error": "Human-readable error message",
"cause": "Human-readable cause (defaults to error if not set)",
"remediation": "Suggested fix or next step",
"status": 400
}
Pioreactor Unit API
Generated from core/pioreactor/web/unit_api.py.
This file is generated. Edit the API source or generator instead of editing this file by hand.
Endpoint count: 93
Endpoint Index
Get All Active Calibrations
Get All Active Calibrations endpoint.
Endpoint
GET /unit_api/active_calibrations
Response
Success
Status: 200 OK
Example body:
{}
Remove Active Status Calibration
Remove Active Status Calibration endpoint.
Endpoint
DELETE /unit_api/active_calibrations/{device}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Set Active Calibration
Set Active Calibration endpoint.
Endpoint
PATCH /unit_api/active_calibrations/{device}/{calibration_name}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
| calibration_name | string | Yes | Calibration name. |
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Get All Active Estimators
Get All Active Estimators endpoint.
Endpoint
GET /unit_api/active_estimators
Response
Success
Status: 200 OK
Example body:
{}
Remove Active Status Estimator
Remove Active Status Estimator endpoint.
Endpoint
DELETE /unit_api/active_estimators/{device}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Set Active Estimator
Set Active Estimator endpoint.
Endpoint
PATCH /unit_api/active_estimators/{device}/{estimator_name}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
| estimator_name | string | Yes | Estimator name. |
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Get Automation Descriptors
Return this unit's automation UI descriptors for one automation family.
Endpoint
GET /unit_api/automations/descriptors/{automation_type}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| automation_type | string | Yes | Automation type, for example dosing, temperature, or led. |
Response
Success
Status: 200 OK
Example body:
[
{
"display_name": "Only record temperature",
"automation_name": "only_record_temperature",
"description": "Apply no heating, only record the temperature.",
"source": null,
"fields": []
},
{
"display_name": "Thermostat",
"automation_name": "thermostat",
"description": "Vary the amount of applied heating to keep the culture near a target temperature, using a control-loop.",
"source": null,
"fields": [
{
"key": "target_temperature",
"default": 30,
"label": "Target temperature",
"disabled": false,
"required": true,
"unit": "\u2103",
"type": "numeric",
"options": null
}
]
}
]
Get Bioreactor Values
Get Bioreactor Values endpoint.
Endpoint
GET /unit_api/bioreactor/experiments/{experiment}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Response
Success
Status: 200 OK
Example body:
{
"values": {
"current_volume_ml": 14.0,
"efflux_tube_volume_ml": 14.0,
"alt_media_fraction": 0.0,
"cumulative_media_added_ml": 0.0,
"cumulative_alt_media_added_ml": 0.0,
"cumulative_waste_removed_ml": 0.0
}
}
Update Bioreactor Values
Update Bioreactor Values endpoint.
Endpoint
PATCH /unit_api/bioreactor/experiments/{experiment}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| values | object | Yes | values. |
{
"values": {
"current_volume_ml": 12.5
}
}
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Get Calibration Protocols
Get Calibration Protocols endpoint.
Endpoint
GET /unit_api/calibration_protocols
Response
Success
Status: 200 OK
Example body:
[
{
"id": "alt_media_pump_duration_based",
"target_device": "alt_media_pump",
"protocol_name": "duration_based",
"priority": 99,
"title": "Duration-based pump calibration",
"description": "Build a duration-to-volume curve for the alt media pump using a simple multi-step flow.",
"requirements": [
"DC Peristaltic pump assigned to alt media pump",
"Pioreactor vial",
"Container of clean water"
]
},
{
"id": "camera_manual_focus",
"target_device": "camera",
"protocol_name": "manual_focus",
"priority": 99,
"title": "Manual camera focus",
"description": "Capture still images while manually adjusting the camera's focus.",
"requirements": [
"Camera connected",
"Detailed focus target near the intended vial position",
"Physical access to the camera focus control"
]
},
{
"id": "media_pump_duration_based",
"target_device": "media_pump",
"protocol_name": "duration_based",
"priority": 99,
"title": "Duration-based pump calibration",
"description": "Build a duration-to-volume curve for the media pump using a simple multi-step flow.",
"requirements": [
"DC Peristaltic pump assigned to media pump",
"Pioreactor vial",
"Container of clean water"
]
}
]
Get All Calibrations
Get All Calibrations endpoint.
Endpoint
GET /unit_api/calibrations
Response
Success
Status: 200 OK
Example body:
{
"alt_media_pump": [
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "alt_media-2023-05-02",
"calibrated_on_pioreactor_unit": "testing_unit",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
1.0270833333333333,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [
0.5,
0.5,
0.5
],
"y": [
0.5,
0.5,
0.5
]
},
"hz": 200.0,
"dc": 90.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "alt_media_pump-2026-01-28",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-01-28T01:45:28.286000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
6.224877783813145,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [
0.26666666666666666,
0.26666666666666666,
0.26666666666666666
],
"y": [
1.0,
1.0,
1.0
]
},
"hz": 250.0,
"dc": 100.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "debug",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
1.0,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [],
"y": []
},
"hz": 100.0,
"dc": 60.0,
"voltage": -1.0,
"is_active": false,
"pioreactor_unit": "localhost"
}
],
"media_pump": [
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "alt_media-2023-05-02",
"calibrated_on_pioreactor_unit": "testing_unit",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
1.0270833333333333,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [
0.5,
0.5,
0.5
],
"y": [
0.5,
0.5,
0.5
]
},
"hz": 200.0,
"dc": 90.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "debug",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
1.0,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [],
"y": []
},
"hz": 100.0,
"dc": 60.0,
"voltage": -1.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "fast_dosing_test_calibration_10ml_s",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
10.0,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [],
"y": []
},
"hz": 100.0,
"dc": 60.0,
"voltage": -1.0,
"is_active": false,
"pioreactor_unit": "localhost"
}
],
"od135": [
{
"calibration_type": "od600",
"calibration_name": "linear_135",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-01-02T02:07:01.506000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
5.0,
0.0
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
0.0,
1.0
],
"y": [
0.0,
5.0
]
},
"ir_led_intensity": 90.0,
"angle": "135",
"pd_channel": "4",
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "od600",
"calibration_name": "od-cal-2026-01-02",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-01-02T03:14:38.840000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
0.0006722234149721264,
-0.0007658042929294755,
0.11977764295022217
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
1.0,
2.0,
0.0
],
"y": [
0.1196840620722648,
0.12093492802425171,
0.11977764295022218
]
},
"ir_led_intensity": 80.0,
"angle": "135",
"pd_channel": "4",
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "od600",
"calibration_name": "od-cal-2026-01-11",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-01-11T02:16:58.880000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
0.05997775639991009,
0.059977756399910084
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
1.0,
1.0
],
"y": [
0.1200894308809028,
0.11982159471873759
]
},
"ir_led_intensity": 80.0,
"angle": "135",
"pd_channel": "4",
"is_active": false,
"pioreactor_unit": "localhost"
}
],
"od45": [
{
"calibration_type": "od600",
"calibration_name": "linear_45",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-02-12T01:35:42.992000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
4.0,
0.0
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
0.0,
1.0
],
"y": [
0.0,
4.0
]
},
"ir_led_intensity": 90.0,
"angle": "45",
"pd_channel": "3",
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "od600",
"calibration_name": "tmp45",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-02-24T14:56:02.229000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
4.0,
0.0
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
0.0,
1.0
],
"y": [
0.0,
4.0
]
},
"ir_led_intensity": 90.0,
"angle": "45",
"pd_channel": "3",
"is_active": false,
"pioreactor_unit": "localhost"
}
],
"od90": [
{
"calibration_type": "od600",
"calibration_name": "custom-od600",
"calibrated_on_pioreactor_unit": "pio1",
"created_at": "2026-01-18T20:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
4.701085432962409e-06,
-0.0002849862354595922,
0.0023565718622962583
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
0.0025,
0.007924,
0.01991
],
"y": [
0.0045,
0.018,
0.19
]
},
"ir_led_intensity": 70.0,
"angle": "90",
"pd_channel": "2",
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "od600",
"calibration_name": "linear",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-01-02T01:54:55.994000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
2.0,
0.0
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
0.0,
2.0
],
"y": [
0.0,
1.0
]
},
"ir_led_intensity": 90.0,
"angle": "90",
"pd_channel": "2",
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "od600",
"calibration_name": "linear_90",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-01-02T02:07:01.506000Z",
"curve_data_": {
"type": "poly",
"coefficients": [
2.0,
0.0
]
},
"x": "OD600",
"y": "Voltage",
"recorded_data": {
"x": [
0.0,
2.0
],
"y": [
0.0,
4.0
]
},
"ir_led_intensity": 90.0,
"angle": "90",
"pd_channel": "2",
"is_active": false,
"pioreactor_unit": "localhost"
}
],
"stirring": [
{
"calibration_type": "simple_stirring",
"calibration_name": "stirring-calibration-2026-02-10_02-02",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-02-10T02:02:24.897000Z",
"curve_data_": {
"type": "spline",
"knots": [
19.8,
39.9
],
"coefficients": [
[
"<truncated>",
"<truncated>",
"<truncated>"
]
]
},
"x": "DC %",
"y": "RPM",
"recorded_data": {
"x": [
39.9,
34.875,
29.85
],
"y": [
1500.0,
1500.0,
1500.0
]
},
"pwm_hz": 200.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_stirring",
"calibration_name": "stirring-calibration-2026-02-20_02-19",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-02-20T02:19:35.790000Z",
"curve_data_": {
"type": "spline",
"knots": [
9.9,
12.413,
17.438
],
"coefficients": [
[
"<truncated>",
"<truncated>",
"<truncated>"
],
[
"<truncated>",
"<truncated>",
"<truncated>"
],
[
"<truncated>",
"<truncated>",
"<truncated>"
]
]
},
"x": "DC %",
"y": "RPM",
"recorded_data": {
"x": [
19.95,
17.438,
14.925
],
"y": [
500.0,
500.0,
500.0
]
},
"pwm_hz": 200.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_stirring",
"calibration_name": "stirring-calibration-2026-02-20_02-21",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2026-02-20T02:21:41.341000Z",
"curve_data_": {
"type": "spline",
"knots": [
9.9,
12.413,
17.438
],
"coefficients": [
[
"<truncated>",
"<truncated>",
"<truncated>"
],
[
"<truncated>",
"<truncated>",
"<truncated>"
],
[
"<truncated>",
"<truncated>",
"<truncated>"
]
]
},
"x": "DC %",
"y": "RPM",
"recorded_data": {
"x": [
19.95,
17.438,
14.925
],
"y": [
500.0,
500.0,
500.0
]
},
"pwm_hz": 200.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
}
],
"waste_pump": [
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "alt_media-2023-05-02",
"calibrated_on_pioreactor_unit": "testing_unit",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
1.0270833333333333,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [
0.5,
0.5,
0.5
],
"y": [
0.5,
0.5,
0.5
]
},
"hz": 200.0,
"dc": 90.0,
"voltage": 0.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "debug",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
1.0,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [],
"y": []
},
"hz": 100.0,
"dc": 60.0,
"voltage": -1.0,
"is_active": false,
"pioreactor_unit": "localhost"
},
{
"calibration_type": "simple_peristaltic_pump",
"calibration_name": "fast_dosing_test_calibration_10ml_s",
"calibrated_on_pioreactor_unit": "localhost",
"created_at": "2010-01-01T00:00:00Z",
"curve_data_": {
"type": "poly",
"coefficients": [
10.0,
0.0
]
},
"x": "Duration",
"y": "Volume",
"recorded_data": {
"x": [],
"y": []
},
"hz": 100.0,
"dc": 60.0,
"voltage": -1.0,
"is_active": false,
"pioreactor_unit": "localhost"
}
]
}
Get Calibrations By Device
Get Calibrations By Device endpoint.
Endpoint
GET /unit_api/calibrations/{device}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
Response
Success
Status: 200 OK
Example body:
[
{
"calibration_name": "example_calibration",
"is_active": true
}
]
Create Calibration
Create a new calibration for the specified device.
Endpoint
POST /unit_api/calibrations/{device}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| calibration_data | string | Yes | calibration data. |
| set_as_active | boolean | No | set as active. |
{
"calibration_data": "calibration_type: simple_peristaltic_pump\ncalibration_name: example_calibration\n",
"set_as_active": true
}
Response
Success
Status: 201 Created
No success response body.
Delete Calibration
Delete a specific calibration for a given device.
Endpoint
DELETE /unit_api/calibrations/{device}/{calibration_name}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
| calibration_name | string | Yes | Calibration name. |
Response
Success
Status: 200 OK
No success response body.
Get Calibration
Get Calibration endpoint.
Endpoint
GET /unit_api/calibrations/{device}/{calibration_name}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
| calibration_name | string | Yes | Calibration name. |
Response
Success
Status: 200 OK
Example body:
{
"calibration_name": "example_calibration",
"is_active": true
}
Get Camera Status For Experiment
Get Camera Status For Experiment endpoint.
Endpoint
GET /unit_api/camera/experiments/{experiment}/status
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Response
Success
Status: 200 OK
Example body:
{
"unit": "localhost",
"detection_status": "detected",
"runtime_available": true,
"capture_command": null,
"mock": true,
"latest_still": {
"experiment": "demo",
"captured_at": "2026-08-18T15:54:17.433333Z",
"image_id": "20260818T155417.433333Z-a5d9d6ba",
"capture_reason": "scheduled"
},
"auto_capture_enabled": true,
"snapshot_interval_minutes": 5
}
Delete Camera Stills For Experiment Route
Delete Camera Stills For Experiment Route endpoint.
Endpoint
DELETE /unit_api/camera/experiments/{experiment}/stills
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Response
Success
Status: 200 OK
No example body inferred.
List Camera Stills For Experiment
List Camera Stills For Experiment endpoint.
Endpoint
GET /unit_api/camera/experiments/{experiment}/stills
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Response
Success
Status: 200 OK
Example body:
{
"unit": "localhost",
"experiment": "demo",
"stills": [
{
"experiment": "demo",
"captured_at": "2026-08-18T15:54:17.433333Z",
"image_id": "20260818T155417.433333Z-a5d9d6ba",
"capture_reason": "scheduled"
}
]
}
Capture Camera Still For Experiment
Capture Camera Still For Experiment endpoint.
Endpoint
POST /unit_api/camera/experiments/{experiment}/stills
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Response
Success
Status: 202 Accepted
Example body:
{
"unit": "pio01",
"task_id": "abcd1234",
"result_url_path": "/unit_api/task_results/abcd1234",
"status": "accepted"
}
Get Zipped Camera Stills For Experiment
Get Zipped Camera Stills For Experiment endpoint.
Endpoint
GET /unit_api/camera/experiments/{experiment}/stills.zip
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
Response
Success
Status: 200 OK
Response body is binary file data.
Delete Camera Still For Experiment
Delete Camera Still For Experiment endpoint.
Endpoint
DELETE /unit_api/camera/experiments/{experiment}/stills/{image_id}.jpg
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
| image_id | string | Yes | image id. |
Response
Success
Status: 200 OK
No example body inferred.
Get Camera Still For Experiment
Get Camera Still For Experiment endpoint.
Endpoint
GET /unit_api/camera/experiments/{experiment}/stills/{image_id}.jpg
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
| image_id | string | Yes | image id. |
Response
Success
Status: 200 OK
No example body inferred.
Rename Camera Still For Experiment
Rename a stored photo without changing its experiment, capture time, or capture reason. The name becomes its image identifier and JPEG filename.
Endpoint
PATCH /unit_api/camera/experiments/{experiment}/stills/{image_id}.jpg
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| experiment | string | Yes | Experiment identifier. |
| image_id | string | Yes | Current image identifier, without the .jpg extension. |
JSON Body
{
"new_image_id": "culture-before-dosing"
}
new_image_id is required. Use only ASCII letters, digits, dots, dashes, and underscores, without spaces or a .jpg extension. The name must not already belong to another stored image on that Pioreactor, including images from other experiments.
Response
Status: 200 OK. Returns the updated image metadata:
{
"experiment": "my experiment",
"captured_at": "2026-09-09T12:00:00Z",
"image_id": "culture-before-dosing",
"capture_reason": "manual"
}
Use the new identifier for subsequent image requests. Invalid names return 400 Bad Request, missing images return 404 Not Found, and name conflicts return 409 Conflict.
Get Camera Focus Preview
Get Camera Focus Preview endpoint.
Endpoint
GET /unit_api/camera/focus_sessions/{session_id}/preview.jpg
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| session_id | string | Yes | Calibration session identifier. |
Response
Success
Status: 200 OK
No example body inferred.
Update Camera Settings
Update Camera Settings endpoint.
Endpoint
PATCH /unit_api/camera/settings
Request
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| auto_capture_enabled | boolean | Yes | auto capture enabled. |
{
"auto_capture_enabled": true
}
Response
Success
Status: 200 OK
No example body inferred.
Get Capabilities
Get Capabilities endpoint.
Endpoint
GET /unit_api/capabilities
Response
Success
Status: 200 OK
Example body:
[
{
"job_name": "add_alt_media",
"help": "Add alt-media from unit",
"arguments": [],
"options": [
{
"name": "ml",
"long_flag": "ml",
"help": "",
"required": false,
"multiple": false,
"default": null,
"type": "float"
},
{
"name": "duration",
"long_flag": "duration",
"help": "",
"required": false,
"multiple": false,
"default": null,
"type": "float"
},
{
"name": "continuously",
"long_flag": "continuously",
"help": "add until the maximum safe volume is reached.",
"required": false,
"multiple": false,
"default": false,
"type": "boolean"
}
],
"published_settings": {},
"cli_example": "pio run add_alt_media [OPTIONS]"
},
{
"job_name": "add_media",
"help": "Add media to unit",
"arguments": [],
"options": [
{
"name": "ml",
"long_flag": "ml",
"help": "",
"required": false,
"multiple": false,
"default": null,
"type": "float"
},
{
"name": "duration",
"long_flag": "duration",
"help": "",
"required": false,
"multiple": false,
"default": null,
"type": "float"
},
{
"name": "continuously",
"long_flag": "continuously",
"help": "add until the maximum safe volume is reached.",
"required": false,
"multiple": false,
"default": false,
"type": "boolean"
}
],
"published_settings": {},
"cli_example": "pio run add_media [OPTIONS]"
},
{
"job_name": "backup_database",
"help": "(leader only) Backup db to workers.",
"arguments": [],
"options": [
{
"name": "output",
"long_flag": "output",
"help": "",
"required": false,
"multiple": false,
"default": "/home/pioreactor/.pioreactor/storage/pioreactor.sqlite.backup",
"type": "text"
},
{
"name": "force",
"long_flag": "force",
"help": "force backing up",
"required": false,
"multiple": false,
"default": false,
"type": "boolean"
},
{
"name": "backup_to_workers",
"long_flag": "backup-to-workers",
"help": "back up db to N workers",
"required": false,
"multiple": false,
"default": null,
"type": "integer"
}
],
"published_settings": {},
"cli_example": "pio run backup_database [OPTIONS]"
}
]
Get Merged Config
Get Merged Config endpoint.
Endpoint
GET /unit_api/config/merged
Response
Success
Status: 200 OK
Example body:
{
"PWM": {
"1": "stirring",
"2": "waste",
"3": "media",
"4": "bubblert",
"5": "heating"
},
"leds": {
"A": "IR",
"B": "white_light",
"C": "",
"D": ""
},
"bioreactor": {
"efflux_tube_volume_ml": "14",
"initial_volume_ml": "14",
"initial_alt_media_fraction": "0.0",
"initial_cumulative_media_added_ml": "0",
"initial_cumulative_alt_media_added_ml": "0",
"initial_cumulative_waste_removed_ml": "0"
},
"stirring.config": {
"initial_target_rpm": "500",
"initial_duty_cycle": "15",
"pwm_hz": "200",
"use_rpm": "True",
"duration_between_updates_seconds": "23",
"post_delay_duration": "0.25",
"pre_delay_duration": "0.25",
"enable_dodging_od": "true",
"target_rpm_during_od_reading": "0",
"target_rpm_outside_od_reading": "500"
},
"dosing_automation.turbidostat": {
"biomass_signal": "auto"
},
"stirring.pid": {
"Kp": "0.007",
"Ki": "0.0",
"Kd": "0.0"
},
"od_config.photodiode_channel": {
"1": "REF",
"2": "90"
},
"od_reading.config": {
"samples_per_second": "0.2",
"turn_off_leds_during_reading": "1",
"pd_reference_ema": "0.4",
"ir_led_intensity": "80",
"duration_between_led_off_and_od_reading": "0.1",
"smoothing_penalizer": "6.0",
"use_dark_offsets": "1"
},
"camera": {
"snapshot_interval_minutes": "5",
"camera_index": "0",
"ir_led_intensity": "90",
"enabled": "1",
"keep_camera_active": "0"
},
"storage": {
"database": "/Users/camerondavidson-pilon/code/pioreactor/.pioreactor/storage/pioreactor.sqlite",
"temporary_cache": "/Users/camerondavidson-pilon/code/pioreactor/.pioreactor/storage/local_intermittent_pioreactor_metadata.sqlite",
"persistent_cache": "/Users/camerondavidson-pilon/code/pioreactor/.pioreactor/storage/local_persistent_pioreactor_metadata.sqlite",
"number_of_backup_replicates_to_workers": "0"
},
"logging": {
"log_file": "./pioreactor.log",
"ui_log_file": "./pioreactor.log",
"ui_log_level": "DEBUG",
"console_log_level": "DEBUG"
},
"cluster.topology": {
"leader_hostname": "localhost",
"leader_address": "localhost"
},
"ui.overview.settings": {
"filtered_od_lookback_minutes": "240",
"raw_od_lookback_minutes": "240",
"log_display_count": "65",
"time_display_mode": "hours"
},
"ui": {
"port": "4999",
"proto": "http"
},
"ui.overview.charts": {
"implied_growth_rate": "1",
"implied_daily_growth_rate": "0",
"fraction_of_volume_that_is_alternative_media": "1",
"normalized_optical_density": "1",
"raw_optical_density": "1",
"temperature": "1",
"optical_density": "1"
},
"ui.overview.cards": {
"dosings": "1",
"event_logs": "1",
"profiles": "1"
},
"dosing_automation.pid_morbidostat": {
"Kp": "5",
"Ki": "0",
"Kd": "0"
},
"temperature_automation.thermostat": {
"Kp": ".01",
"Ki": ".01",
"Kd": ".01"
},
"mqtt": {
"username": "pioreactor",
"password": "raspberry",
"broker_address": "localhost",
"broker_ws_port": "9001",
"broker_port": "1883",
"ws_protocol": "ws",
"use_tls": "0"
},
"dosing_automation.config": {
"pause_between_subdoses_seconds": "0.5",
"waste_removal_multiplier": "2.0",
"max_volume_to_warn": "17.0",
"max_volume_to_stop": "18.0",
"max_subdose": "1.0",
"experimental_pump_malfunction_tolerance": "0.2",
"experimental_detect_pump_malfunction": "False"
}
}
Get Specific Config
Get Specific Config endpoint.
Endpoint
GET /unit_api/config/specific
Response
Success
Status: 200 OK
Response body is plain text.
Update Specific Config
Replace this unit's unit-specific config text.
Endpoint
PATCH /unit_api/config/specific
Request
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | code. |
{
"code": "[section]\nkey=value\n"
}
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Update Specific Config
Replace this unit's unit-specific config text.
Endpoint
POST /unit_api/config/specific
Request
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| code | string | Yes | code. |
{
"code": "[section]\nkey=value\n"
}
Response
Success
Status: 200 OK
Example body:
{
"status": "success"
}
Get All Estimators
Get All Estimators endpoint.
Endpoint
GET /unit_api/estimators
Response
Success
Status: 200 OK
Example body:
{
"od_fused": [
{
"estimator_type": "od_fused_estimator",
"estimator_name": "od-fused-estimator-2026-01-23",
"calibrated_on_pioreactor_unit": "xr1",
"created_at": "2026-01-23T14:43:04.207000Z",
"ir_led_intensity": 80.0,
"angles": [
"45",
"90",
"135"
],
"mu_splines": {
"45": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>"
]
},
"90": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>"
]
},
"135": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>"
]
}
},
"sigma_splines_log": {
"45": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>"
]
},
"90": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>"
]
},
"135": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>"
]
}
},
"min_logc": 0.0,
"max_logc": 0.7781512503836436,
"sigma_floor": 0.05,
"recorded_data": {
"x": [
0.0,
0.47712125471966244,
0.6989700043360189
],
"y": [
-6.778473545527855,
-6.787264739975302,
-6.787050588455531
]
},
"low_conc_scales": {},
"y": "log(Voltage)",
"is_active": false,
"pioreactor_unit": "localhost",
"device": "od_fused"
},
{
"estimator_type": "od_fused_estimator",
"estimator_name": "od-fused-estimator-2026-01-27",
"calibrated_on_pioreactor_unit": "xr1",
"created_at": "2026-01-27T15:59:11.334000Z",
"ir_led_intensity": 80.0,
"angles": [
"45",
"90",
"135"
],
"mu_splines": {
"45": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
},
"90": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
},
"135": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
}
},
"sigma_splines_log": {
"45": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>"
]
},
"90": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>"
]
},
"135": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>"
]
}
},
"min_logc": -2.0,
"max_logc": 0.5440680443502757,
"sigma_floor": 0.04,
"recorded_data": {
"by_angle": {
"45": {
"x": "<truncated>",
"y": "<truncated>"
},
"90": {
"x": "<truncated>",
"y": "<truncated>"
},
"135": {
"x": "<truncated>",
"y": "<truncated>"
}
}
},
"low_conc_scales": {},
"y": "log(Voltage)",
"is_active": false,
"pioreactor_unit": "localhost",
"device": "od_fused"
},
{
"estimator_type": "od_fused_estimator",
"estimator_name": "od-fused-estimator-2026-01-28",
"calibrated_on_pioreactor_unit": "xr4",
"created_at": "2026-01-28T16:04:53.392000Z",
"ir_led_intensity": 80.0,
"angles": [
"45",
"90",
"135"
],
"mu_splines": {
"45": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
},
"90": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
},
"135": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
}
},
"sigma_splines_log": {
"45": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
},
"90": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
},
"135": {
"type": "akima",
"knots": [
"<truncated>",
"<truncated>",
"<truncated>"
],
"coefficients": [
"<truncated>",
"<truncated>",
"<truncated>"
]
}
},
"min_logc": -4.0,
"max_logc": 1.6989700043360187,
"sigma_floor": 0.04,
"recorded_data": {
"by_angle": {
"45": {
"x": "<truncated>",
"y": "<truncated>"
},
"90": {
"x": "<truncated>",
"y": "<truncated>"
},
"135": {
"x": "<truncated>",
"y": "<truncated>"
}
}
},
"low_conc_scales": {},
"y": "log(Voltage)",
"is_active": false,
"pioreactor_unit": "localhost",
"device": "od_fused"
}
],
"temperature_fir": [
{
"estimator_type": "fir_temperature_estimator",
"estimator_name": "fir_temperature_estimator_linear_mlx_ambient_pcb_v1",
"calibrated_on_pioreactor_unit": "seed",
"created_at": "2026-02-18T00:00:00Z",
"w_obj": 0.986277,
"w_amb": -0.126407,
"w_pcb": 0.088753,
"c": 1.083602,
"tau_s": 120.0,
"k_vol": -0.012317,
"volume_ref_ml": 25.0,
"user_bias_c": 0.0,
"y": "temperature_c",
"is_active": false,
"pioreactor_unit": "localhost",
"device": "temperature_fir"
}
]
}
Get Estimators By Device
Get Estimators By Device endpoint.
Endpoint
GET /unit_api/estimators/{device}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
Response
Success
Status: 200 OK
Example body:
[]
Delete Estimator
Delete Estimator endpoint.
Endpoint
DELETE /unit_api/estimators/{device}/{estimator_name}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
| estimator_name | string | Yes | Estimator name. |
Response
Success
Status: 200 OK
No success response body.
Get Estimator
Get Estimator endpoint.
Endpoint
GET /unit_api/estimators/{device}/{estimator_name}
Request
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device | string | Yes | Target device name. |
| estimator_name | string | Yes | Estimator name. |
Response
Success
Status: 200 OK
Example body:
{
"estimator_name": "example_estimator",
"is_active": true
}
Check Hardware For Model
Check whether this unit's hardware supports a model.
Endpoint
PATCH /unit_api/hardware/check
Request
Request Body
| Name | Type | Required | Description |
|---|---|---|---|
| model_name | string | Yes | model name. |
| model_version | string | Yes | model version. |
{
"model_name": "pioreactor_40ml",
"model_version": "1.5"
}
Response
Success
Status: 202 Accepted
Example body:
{
"unit": "pio01",
"task_id": "abcd1234",
"result_url_path": "/unit_api/task_results/abcd1234",
"status": "accepted"
}