Skip to navigation

Introduction to Transfers

An overview of how to perform transfers using the Benchling Containers API.

Introduction

Working in a lab often involves working with containers: filling them, emptying them, diluting them; there’s an enormous amount of science to be done on a sample in tubes and plates. If you’re using the Benchling Inventory for sample management, you’re likely to be working with virtual containers as well. We cover how to use the inventory in-app in our Knowledge Base; here, we’re going to cover how to work with containers and perform transfers using the Benchling API.

Working with Containers

In Benchling, a container is the lowest level of storage tracked in the inventory. Containers can be held in boxes and locations, and hold entities. Some common examples are cryovials, Eppendorf tubes, and plate wells (which are effectively containers in their own right). More info on containers can be found in this knowledge base article.

Inventory SchemaDescription
LocationHeld in other locations or independent; can hold locations, boxes, plates, and containers
BoxHeld in locations; can hold containers
PlateHeld in locations; can hold wells
ContainerHeld in boxes and locations; can hold entities

What is a Transfer?

Conceptually, every individual transfer has three pieces: a source, a material, and a destination.

The source of a transfer is where the material is being transferred from; the origin of the material. In a physical lab, the source might be a container like an Eppendorf tube, or it could be an “unlimited” source. When the source is a container, the transfer happening in Benchling represents the physical case of taking the sample out of one container and putting it into another container. When the source is “unlimited,” the transfer happening in Benchling represents the physical case of the sample being put into a container from a source that isn’t important to define in the inventory.

When performing a transfer via the Benchling API, the source is represented by either the sourceContainerId field (in the case of a container source) or the sourceEntityId field (in the case of an “unlimited” source). Each transfer takes exactly one of the two.

The material is the item(s) that are being transferred; the actual material that the containers are holding. The material might be an entity, multiple entities, or a mixture. Importantly, the material of a transfer can include not just the entity/entities, but the corresponding quantity and concentration.

When performing a transfer via the Benchling API, the material is described by the source together with the transferQuantity field, plus the optional sourceConcentration field, which sets the concentration of an entity source.

The destination of a transfer is the container that the material is being transferred into. The destination is always a container, and must be defined for any transfer. Benchling doesn’t enforce a maximum quantity on containers, so a transfer can’t overfill its destination.

When performing a transfer via the Benchling API, the destination is represented by the destinationContainerId field.

Structure of a Transfer

Transfers use the V3 API endpoint POST /api/v3/container:transfer, which is stable. Its request body is a transfers array; each item is one transfer. This example transfers material out of one container and into another:

{
"transfers": [
{
"sourceContainerId": "con_jrj3j8jv",
"destinationContainerId": "con_W5llmhfe",
"transferQuantity": {
"unitId": "<milliliter-unit-id>",
"value": 10
}
}
]
}

There are three critical fields here that roughly correspond to the source, material, and destination of this transfer: sourceContainerId, transferQuantity, and destinationContainerId:

The sourceContainerId field is the API ID of the container in Benchling from which we are transferring the material.

The transferQuantity field is the amount of material being transferred. In all transfers, this amount will be added to the destination container; because we’re specifying a sourceContainerId, this amount will also be subtracted from the source container. V3 measurements name their unit by ID (unitId) rather than by symbol; you can read unit IDs from the quantity of an existing container or look them up with GET /api/v3/unit/items.

The destinationContainerId field is the API ID of the container receiving the material.

Unlike the V2 transfer endpoints, a V3 transfer has no destinationContents field: you don’t list the destination’s contents after the transfer yourself. V3 works out the destination’s contents and their concentrations from the source and whatever the destination already holds, for both container and entity sources, so you don’t need to read the destination first.

The endpoint starts an async task and returns a taskId and pollingUri; poll GET /api/v3/tasks/container/transfer/{task_id} until its status is COMPLETED or FAILED. A completed task’s result.destinationContainerIds lists the containers that were updated.

While the specific parameters differ for different use cases, the overall structure of a transfer remains the same. Let’s look at another example request, this time for a transfer from an “unlimited” source:

{
"transfers": [
{
"sourceEntityId": "prtn_f7snnf8e",
"destinationContainerId": "con_TbU7UrgR",
"transferQuantity": {
"unitId": "<milliliter-unit-id>",
"value": 10
},
"sourceConcentration": {
"unitId": "<g/mL-unit-id>",
"value": 1.23
}
}
]
}

Many of the same components are present, but there are some important differences from the previous example:

We’ve replaced sourceContainerId with sourceEntityId here, because for this transfer there is no source container in Benchling. Transfers without a specified source container in Benchling can serve a few different purposes. Sometimes, they correspond to some lab process like filling plate wells from a bulk container, where it’s not important to track the source of the material in the Benchling inventory (i.e. an “unlimited” supply).

Other times, the “transfer” is just an abstraction, and doesn’t correspond to the literal movement of samples in a lab. This is most clear in the case where a lab acquires brand new pre-filled containers of some material, which would involve creating containers in Benchling and “transferring” material into them.

The transferQuantity field is used in the same way here, though since we’re not specifying a source container this quantity will not be subtracted from any source; it will only be added to the destination container.

The sourceConcentration field sets the concentration of the entity being transferred. It applies only to entity sources: a container-to-container transfer can’t set it, and always carries over every source content at the concentration it has in the source. How the concentration lands in the destination depends on what’s already there:

  • Empty destination: the destination holds the entity at sourceConcentration.
  • Destination already holds the same entity: V3 recalculates the entity’s concentration for the combined volume.
  • sourceConcentration left out: the entity’s concentration is not tracked.

Each transfer can also set finalQuantity, the quantity to dilute the destination to after the transfer. V3 recalculates concentrations for the final quantity: transferring 10 mL of an entity at 10% with a finalQuantity of 100 mL leaves the entity at 1% in the destination. So a dilution, even into a container that already has contents, takes a single transfer. finalQuantity can’t be lower than the quantity the destination would hold anyway (its existing quantity plus transferQuantity). The request is still accepted, but the task ends FAILED with the error “Your request is invalid”.

Types of Transfers

Now that we’ve covered the mechanics of performing transfers in Benchling via the API, it’s worth diving into a few common examples of the types of transfers that an integration may perform. Each can be performed with the transfer endpoint directly or through the V3 Python SDK, whose container.transfer method wraps the same endpoint.

Create: Adding materials to empty containers

The most straightforward type of transfer is adding entities to a brand new or empty container. This often corresponds to the physical use case of filling containers from an “unlimited” source, or receiving pre-filled containers, discussed above. Containers can be filled using the transfer endpoint by specifying an entity or mixture as well as a quantity.

Move: Transferring the contents of a container into another container

Likely the most familiar kind of transfer, moving entities between containers is a common use case. This corresponds to the physical use case of emptying one container into another; this involves using the transfer endpoint to specify a source container and a destination container, as well as the amount(s) to move.

Disperse: Transferring some portion of the contents of one container into another container

Another common type of transfer, disperse transfers involve transferring entities between non-empty containers. This corresponds to the physical use case of mixing the contents of different containers; this involves transferring some of the contents of the source into a non-empty destination container. The resulting combination of entities/mixtures in the destination has to be modeled correctly.

Bulk: Performing multiple transfers at once

In addition to the common use cases described above, the Benchling API also supports more advanced use cases like bulk transfers. This involves creating new entities and containers, then sending one transfer per container in a single request to fill the containers with the entities. This is most comparable to the “Move” case above, though it also includes creating a plate.