Skill Tracer
Skills › Scientific Agent Skills › opentrons-integration › migration-api-2-19-to-2-29.md

migration-api-2-19-to-2-29.md

336 lines · skills/opentrons-integration/references/migration-api-2-19-to-2-29.md at 5f2bc03

Open on GitHub
1# Migrating API 2.19 Protocols to the Current Baseline
2
3This guide updates protocols written around robot software 7.3.1 and Protocol
4API 2.19 to the 2026-10-01 baseline:
5
6- Flex: documented maximum Protocol API 2.30; bundled templates retain 2.29.
7- OT-2: Protocol API 2.28.
8- Flex local simulator: `opentrons==10.0.0`.
9- OT-2 local compatibility simulator: `opentrons==9.0.0`, followed by analysis
10 in the current OT-2 App.
12Do not mechanically change only the API string. Newer levels can change command
13validation and behavior.

Migrating API 2.19 Protocols to the Current Baseline

This guide updates protocols written around robot software 7.3.1 and Protocol API 2.19 to the 2026-10-01 baseline:

  • Flex: documented maximum Protocol API 2.30; bundled templates retain 2.29.
  • OT-2: Protocol API 2.28.
  • Flex local simulator: opentrons==10.0.0.
  • OT-2 local compatibility simulator: opentrons==9.0.0, followed by analysis in the current OT-2 App.

Do not mechanically change only the API string. Newer levels can change command validation and behavior.

Raw lines 1 to 14
15## 1. Identify the Target Robot
17API 2.29 and later are not supported on OT-2 at this baseline.
19```python
20# Flex
21requirements = {"robotType": "Flex", "apiLevel": "2.29"}
23# OT-2
24requirements = {"robotType": "OT-2", "apiLevel": "2.28"}
25```
27Use one `apiLevel` declaration. Older files often put it in both `metadata` and
28`requirements`; current analysis rejects that.
30Before:
32```python
33metadata = {"apiLevel": "2.19", "protocolName": "Example"}
34requirements = {"robotType": "Flex", "apiLevel": "2.19"}
35```
37After:
39```python
40metadata = {"protocolName": "Example"}
41requirements = {"robotType": "Flex", "apiLevel": "2.29"}
42```
44If the protocol must remain compatible with older robot software, keep 2.19 and
45apply only changes available at that level.

1. Identify the Target Robot

API 2.29 and later are not supported on OT-2 at this baseline.

# Flex
requirements = {"robotType": "Flex", "apiLevel": "2.29"}

# OT-2
requirements = {"robotType": "OT-2", "apiLevel": "2.28"}

Use one apiLevel declaration. Older files often put it in both metadata and requirements; current analysis rejects that.

Before:

metadata = {"apiLevel": "2.19", "protocolName": "Example"}
requirements = {"robotType": "Flex", "apiLevel": "2.19"}

After:

metadata = {"protocolName": "Example"}
requirements = {"robotType": "Flex", "apiLevel": "2.29"}

If the protocol must remain compatible with older robot software, keep 2.19 and apply only changes available at that level.

Raw lines 15 to 46
47## 2. Replace Incorrect Flex Pipette Names
49Current Flex load names describe channels and range:
51| Old or incorrect pattern | Current choice |
52| --- | --- |
53| `p50_single_flex` | `flex_1channel_50` |
54| `p50_multi_flex` | `flex_8channel_50` |
55| `p1000_single_flex` | `flex_1channel_1000` |
56| `p1000_multi_flex` | `flex_8channel_1000` |
57| `p300_single_flex` | No direct equivalent; choose `flex_1channel_50` or `flex_1channel_1000` from validated volume needs |
58| `p300_multi_flex` | No direct equivalent; choose `flex_8channel_50` or `flex_8channel_1000` |
60Also available:
62- `flex_96channel_200` — API 2.25+.
63- `flex_96channel_1000`.
65Do not choose solely by the largest transfer. Check every operation against the
66pipette's lower and upper range and compatible tip capacities.
68OT-2 GEN2 names remain `p20_*_gen2`, `p300_*_gen2`, and
69`p1000_single_gen2`.

2. Replace Incorrect Flex Pipette Names

Current Flex load names describe channels and range:

Old or incorrect patternCurrent choice
p50_single_flexflex_1channel_50
p50_multi_flexflex_8channel_50
p1000_single_flexflex_1channel_1000
p1000_multi_flexflex_8channel_1000
p300_single_flexNo direct equivalent; choose flex_1channel_50 or flex_1channel_1000 from validated volume needs
p300_multi_flexNo direct equivalent; choose flex_8channel_50 or flex_8channel_1000

Also available:

  • flex_96channel_200 — API 2.25+.
  • flex_96channel_1000.

Do not choose solely by the largest transfer. Check every operation against the pipette's lower and upper range and compatible tip capacities.

OT-2 GEN2 names remain p20_*_gen2, p300_*_gen2, and p1000_single_gen2.

Raw lines 47 to 70
71## 3. Make Flex Trash Explicit
73Flex API 2.16+ protocols should load the fixture actually installed:
75```python
76trash = protocol.load_trash_bin("A3")
77```
79Or:
81```python
82chute = protocol.load_waste_chute()
83```
85OT-2 keeps its fixed trash in slot 12 and does not call `load_trash_bin()`.
87If both a trash bin and waste chute exist, set the intended pipette trash
88container or pass the documented `trash_location` to complex commands.

3. Make Flex Trash Explicit

Flex API 2.16+ protocols should load the fixture actually installed:

trash = protocol.load_trash_bin("A3")

Or:

chute = protocol.load_waste_chute()

OT-2 keeps its fixed trash in slot 12 and does not call load_trash_bin().

If both a trash bin and waste chute exist, set the intended pipette trash container or pass the documented trash_location to complex commands.

Raw lines 71 to 89
90## 4. Update Liquid Loading, API 2.22+
92`Well.load_liquid()` is deprecated, and `Well.load_empty()` does not exist in
93the current package.
95Before:
97```python
98reservoir["A1"].load_liquid(liquid=buffer, volume=10_000)
99plate["A1"].load_empty()
100```
102After:
104```python
105reservoir.load_liquid(
106 wells=["A1"],
107 volume=10_000,
108 liquid=buffer,
110plate.load_empty(wells=["A1"])
111```
113For varying volumes:
115```python
116plate.load_liquid_by_well(
117 volumes={"A1": 20, "B1": 30},
118 liquid=sample,
120```

4. Update Liquid Loading, API 2.22+

Well.load_liquid() is deprecated, and Well.load_empty() does not exist in the current package.

Before:

reservoir["A1"].load_liquid(liquid=buffer, volume=10_000)
plate["A1"].load_empty()

After:

reservoir.load_liquid(
    wells=["A1"],
    volume=10_000,
    liquid=buffer,
)
plate.load_empty(wells=["A1"])

For varying volumes:

plate.load_liquid_by_well(
    volumes={"A1": 20, "B1": 30},
    liquid=sample,
)
Raw lines 90 to 121
122## 5. Update Adapter Loading
124`ProtocolContext.load_labware_on_adapter()` is not a current method.
126Load the adapter, then call the adapter's method:
128```python
129adapter = protocol.load_adapter(
130 "opentrons_96_well_aluminum_block",
131 "D1",
133plate = adapter.load_labware(
134 "opentrons_96_wellplate_200ul_pcr_full_skirt"
136```
138Some `load_labware()` calls also accept an `adapter=` load name for supported
139stacks. Use the pattern shown for the exact hardware in current documentation.

5. Update Adapter Loading

ProtocolContext.load_labware_on_adapter() is not a current method.

Load the adapter, then call the adapter's method:

adapter = protocol.load_adapter(
    "opentrons_96_well_aluminum_block",
    "D1",
)
plate = adapter.load_labware(
    "opentrons_96_wellplate_200ul_pcr_full_skirt"
)

Some load_labware() calls also accept an adapter= load name for supported stacks. Use the pattern shown for the exact hardware in current documentation.

Raw lines 122 to 140
141## 6. Fix Flex Magnetic Workflows
143The powered Magnetic Module is OT-2-only.
145Old Flex pattern:
147```python
148magnetic_module = protocol.load_module(
149 "magnetic module gen2",
150 "C2",
152magnetic_module.engage(height_from_base=6.5)
153```
155Current Flex pattern:
157```python
158magnetic_block = protocol.load_module("magneticBlockV1", "C2")
159protocol.move_labware(
160 labware=plate,
161 new_location=magnetic_block,
162 use_gripper=True,
164protocol.delay(minutes=5)
165protocol.move_labware(
166 labware=plate,
167 new_location="B2",
168 use_gripper=True,
170```
172The Magnetic Block is passive and has no `engage()` or `disengage()` method.

6. Fix Flex Magnetic Workflows

The powered Magnetic Module is OT-2-only.

Old Flex pattern:

magnetic_module = protocol.load_module(
    "magnetic module gen2",
    "C2",
)
magnetic_module.engage(height_from_base=6.5)

Current Flex pattern:

magnetic_block = protocol.load_module("magneticBlockV1", "C2")
protocol.move_labware(
    labware=plate,
    new_location=magnetic_block,
    use_gripper=True,
)
protocol.delay(minutes=5)
protocol.move_labware(
    labware=plate,
    new_location="B2",
    use_gripper=True,
)

The Magnetic Block is passive and has no engage() or disengage() method.

Raw lines 141 to 173
174## 7. Fix Absorbance Plate Reader Calls
176The reader was added in API 2.21 and is Flex-only. It does not accept
177`read(wavelengths=[...])`.
179Incorrect:
181```python
182result = plate_reader.read(wavelengths=[450, 650])
183```
185Correct sequence:
187```python
188reader = protocol.load_module("absorbanceReaderV1", "D3")
190reader.close_lid()
191reader.initialize(mode="multi", wavelengths=[450, 650])
192reader.open_lid()
193protocol.move_labware(
194 labware=plate,
195 new_location=reader,
196 use_gripper=True,
198reader.close_lid()
199result = reader.read(export_filename="absorbance")
200```
202The plate reader returns zeros during simulation. Avoid divide-by-zero logic in
203the simulation branch.

7. Fix Absorbance Plate Reader Calls

The reader was added in API 2.21 and is Flex-only. It does not accept read(wavelengths=[...]).

Incorrect:

result = plate_reader.read(wavelengths=[450, 650])

Correct sequence:

reader = protocol.load_module("absorbanceReaderV1", "D3")

reader.close_lid()
reader.initialize(mode="multi", wavelengths=[450, 650])
reader.open_lid()
protocol.move_labware(
    labware=plate,
    new_location=reader,
    use_gripper=True,
)
reader.close_lid()
result = reader.read(export_filename="absorbance")

The plate reader returns zeros during simulation. Avoid divide-by-zero logic in the simulation branch.

Raw lines 174 to 204
205## 8. Remove Unsupported Complex-Command Options
207Do not preserve options merely because an old reference listed them.
209For example, `gradient=(start, end)` is not a supported generic `transfer()`
210option in the current API. Build a validated volume list explicitly:
212```python
213volumes = [10, 20, 30, 40]
214pipette.transfer(
215 volume=volumes,
216 source=reservoir["A1"],
217 dest=plate.wells()[:4],
218 new_tip="always",
220```
222Check uncommon options against the exact current method and API level. The
223supported options for standard and liquid-class commands are not identical.

8. Remove Unsupported Complex-Command Options

Do not preserve options merely because an old reference listed them.

For example, gradient=(start, end) is not a supported generic transfer() option in the current API. Build a validated volume list explicitly:

volumes = [10, 20, 30, 40]
pipette.transfer(
    volume=volumes,
    source=reservoir["A1"],
    dest=plate.wells()[:4],
    new_tip="always",
)

Check uncommon options against the exact current method and API level. The supported options for standard and liquid-class commands are not identical.

Raw lines 205 to 224
225## 9. Revisit Behavior Changes

9. Revisit Behavior Changes

Raw lines 225 to 226
227### API 2.20
229- Liquid presence detection.
230- CSV runtime parameters.
231- Expanded partial-nozzle layouts.

API 2.20

  • Liquid presence detection.
  • CSV runtime parameters.
  • Expanded partial-nozzle layouts.
Raw lines 227 to 232
233### API 2.21
235- Absorbance Plate Reader.
236- Liquid presence checks only the first aspiration of a `mix()` cycle.

API 2.21

  • Absorbance Plate Reader.
  • Liquid presence checks only the first aspiration of a mix() cycle.
Raw lines 233 to 237
238### API 2.22
240- Labware-level liquid loading.
241- `Well.load_liquid()` deprecated.
242- Low-level robot motor control.

API 2.22

  • Labware-level liquid loading.
  • Well.load_liquid() deprecated.
  • Low-level robot motor control.
Raw lines 238 to 243
244### API 2.23
246- Meniscus locations.
247- Labware lids and lid moves.
248- Labware offset behavior aligned with newer App checks.

API 2.23

  • Meniscus locations.
  • Labware lids and lid moves.
  • Labware offset behavior aligned with newer App checks.
Raw lines 244 to 249
250### API 2.24
252- Verified and custom liquid classes.
253- `transfer_with_liquid_class()`, `distribute_with_liquid_class()`, and
254 `consolidate_with_liquid_class()`.
255- Additional flow, delay, position, and push-out options.

API 2.24

  • Verified and custom liquid classes.
  • transfer_with_liquid_class(), distribute_with_liquid_class(), and consolidate_with_liquid_class().
  • Additional flow, delay, position, and push-out options.
Raw lines 250 to 256
257### API 2.25
259- Flex Stacker.
260- Flex 96-Channel 200 µL pipette.

API 2.25

  • Flex Stacker.
  • Flex 96-Channel 200 µL pipette.
Raw lines 257 to 261
262### API 2.26
264- Liquid-class support for the 96-channel 200 µL pipette.

API 2.26

  • Liquid-class support for the 96-channel 200 µL pipette.
Raw lines 262 to 265
266### API 2.27
268- Concurrent module tasks.
269- Dynamic aspirate, dispense, and mix paths.
270- Built-in camera capture.
271- Explicit tips for liquid-class transfers.

API 2.27

  • Concurrent module tasks.
  • Dynamic aspirate, dispense, and mix paths.
  • Built-in camera capture.
  • Explicit tips for liquid-class transfers.
Raw lines 266 to 272
273### API 2.28
275- Flex 20 µL tips.
276- Improved return of partially picked-up tips.
277- Absolute blowout customization.
278- Thermocycler ramp-rate control.
279- `set_empty()` tip-rack state.
280- Errors for unsafe `touch_tip()` use in large spaces.

API 2.28

  • Flex 20 µL tips.
  • Improved return of partially picked-up tips.
  • Absolute blowout customization.
  • Thermocycler ramp-rate control.
  • set_empty() tip-rack state.
  • Errors for unsafe touch_tip() use in large spaces.
Raw lines 273 to 281
282### API 2.29
284- Step grouping in source and protocol visualization.
285- Flex-only at this migration baseline.

API 2.29

  • Step grouping in source and protocol visualization.
  • Flex-only at this migration baseline.
Raw lines 282 to 286
287### API 2.30
289- Aspirating at `well.meniscus(target="start")` without an `end_location` is
290 supported. Older API levels reject that pattern.
291- The 10.0.0 package exposes `MAX_SUPPORTED_VERSION=2.31`, while the official
292 robot support table stops at 2.30. Check the target robot; do not adopt 2.31
293 from the local constant.

API 2.30

  • Aspirating at well.meniscus(target="start") without an end_location is supported. Older API levels reject that pattern.
  • The 10.0.0 package exposes MAX_SUPPORTED_VERSION=2.31, while the official robot support table stops at 2.30. Check the target robot; do not adopt 2.31 from the local constant.
Raw lines 287 to 294
295## 10. Revisit Module and Deck Assumptions
297Check for:
299- Flex trash or waste chute not represented in old code.
300- Staging area and column-3 conflicts.
301- New Gripper or lid moves.
302- Heater-Shaker latch state.
303- Thermocycler generation and footprint.
304- Plate-reader caddy and lid travel.
305- Stacker shuttle paths.
306- Tip-rack adapter requirements for full versus partial 96-channel pickup.
308API analysis has improved, so a newly raised deck-conflict error may reveal an
309old protocol assumption that was never physically safe.

10. Revisit Module and Deck Assumptions

Check for:

  • Flex trash or waste chute not represented in old code.
  • Staging area and column-3 conflicts.
  • New Gripper or lid moves.
  • Heater-Shaker latch state.
  • Thermocycler generation and footprint.
  • Plate-reader caddy and lid travel.
  • Stacker shuttle paths.
  • Tip-rack adapter requirements for full versus partial 96-channel pickup.

API analysis has improved, so a newly raised deck-conflict error may reveal an old protocol assumption that was never physically safe.

Raw lines 295 to 310
311## 11. Revalidate Tip and Volume Policies
313Do not assume newer pipetting behavior produces assay-equivalent results.
315- Recalculate tip count.
316- Recalculate source and dead volume.
317- Confirm complex-command expansion in the run log.
318- Requalify flow rates, mix behavior, bottom clearances, air gaps, and blowout.
319- Recheck contamination policy.
320- Recheck multi-channel and partial-nozzle well targeting.

11. Revalidate Tip and Volume Policies

Do not assume newer pipetting behavior produces assay-equivalent results.

  • Recalculate tip count.
  • Recalculate source and dead volume.
  • Confirm complex-command expansion in the run log.
  • Requalify flow rates, mix behavior, bottom clearances, air gaps, and blowout.
  • Recheck contamination policy.
  • Recheck multi-channel and partial-nozzle well targeting.
Raw lines 311 to 321
322## 12. Migration Test Plan
3241. Preserve the original protocol and expected run log.
3252. Update declarations and load names.
3263. Replace deprecated or invalid calls.
3274. Simulate with the robot-specific pin: `opentrons==10.0.0` for Flex or
328 `opentrons==9.0.0` for OT-2.
3295. Compare command order, tip use, source/destination mapping, and module states.
3306. Test every runtime parameter branch.
3317. Import into the correct App and target robot.
3328. Resolve every analysis warning and error.
3339. Perform a nonhazardous dry run.
33410. Requalify assay performance before production use.
336Do not claim a migration is equivalent solely from a successful simulation.

12. Migration Test Plan

  1. Preserve the original protocol and expected run log.
  2. Update declarations and load names.
  3. Replace deprecated or invalid calls.
  4. Simulate with the robot-specific pin: opentrons==10.0.0 for Flex or opentrons==9.0.0 for OT-2.
  5. Compare command order, tip use, source/destination mapping, and module states.
  6. Test every runtime parameter branch.
  7. Import into the correct App and target robot.
  8. Resolve every analysis warning and error.
  9. Perform a nonhazardous dry run.
  10. Requalify assay performance before production use.

Do not claim a migration is equivalent solely from a successful simulation.

Raw lines 322 to 336