Introduction to Transfers

An overview of how to perform transfers using the Benching 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 SchemasDescription
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 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).

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 represented as components of destinationContents (see below).

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. A destination container should have enough room for the transfer to succeed; if the combined volume of the material (indicated by the transferQuantity field) and the material already present would exceed the total volume of the destination container, the transfer will fail with a 400 error.

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

Structure of a Transfer

Let’s take a look at an example JSON request body for the single transfer endpoint; this specific example demonstrates transferring material out of one container and into another:

{
  "sourceContainerId": "con_jrj3j8jv",
  "transferQuantity": {
    "units": "mL",
    "value": 10
  },
  "destinationContents": [
    {
      "entityId": "prtn_f7snnf8e",
      "concentration": {
        "value": 1.23,
        "units": "g/mL"
      }
    }
  ]
}

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

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.

The destinationContents field is an array of objects describing both the material and the contents of the destination container. destinationContents is required for all transfers, and serves two crucial purposes:

  • First, destinationContents is used to determine which contents need to be created in the destination container and the concentration values of all containable items in the destination container
  • Second, destinationContents is used to validate that the sum of all containable items that make up the material and existing destination container contents match what is specified in destinationQuantity, if specified

While the specific parameters differ for different use cases, the overall structure of a transfer remains the same. Let’s look at another example JSON request body for the single transfer endpoint, this time for a transfer from an “unlimited” source into a non-empty container:

{
  "sourceEntityId": "prtn_f7snnf8e",
  "transferQuantity": {
    "units": "mL",
    "value": 10
  },
  "destinationQuantity": {
    "units": "mL",
    "value": 15
  },
  "destinationContents": [
    {
      "entityId": "prtn_f7snnf8e",
      "concentration": {
        "value": 1.23,
        "units": "g/mL"
      }
    },
    {
      "entityId": "prtn_mgs7d882",
      "concentration": {
        "value": 0.77,
        "units": "g/mL"
      }
    }
  ]
}

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

We've excluded sourceContainerId 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 subtraceted from any source; it will only be added to the destination container.

The destinationContents field now includes two objects, representing the two distinct entities in this container. The combination of each object in destinationContents represents the final state of the container after the transfer; in this case, we’re including both the new entity to be transferred into the container, and the entity already in the container.

📘

Diving deeper: destinationContents

In the example above, we discuss that when transferring a material into a container that isn’t empty, destinationContents must include both the material being transferred and the material already in the container. In practice, this means that you must know the existing contents of the destination container in order to correctly perform a transfer into a non-empty container. Transfers therefore usually follow the same steps:

  1. Read contents of destination container
  2. Construct destinationContents
  3. Perform transfer

While this introduces overhead for the integration, it ensures that any integration performing transfers via API knows exactly what containable item(s) they're working with.

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.

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. The example code in the following recipes illustrates how to perform these types of transfer using the Benchling SDK:

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. Destination contents is still a required field even when moving the entire source contents into an empty destination container:

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. Destination contents is critical here, since the resulting combination of entities/mixtures must 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 example includes creating new entities and containers, then using the bulk transfer endpoint to fill the containers with the entities. This is most comparable to the "Move" example above, though this example also includes creating a plate.