> This page is for Developer Documentation.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.benchling.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.benchling.com/_mcp/server.

# Introduction to Transfers

## 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](https://help.benchling.com/hc/en-us/sections/39551486017805-Inventory); 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](https://help.benchling.com/hc/en-us/articles/39943809066637-Create-and-track-samples-with-the-Inventory).

| Inventory Schema | Description                                                                               |
| ---------------- | ----------------------------------------------------------------------------------------- |
| Location         | Held in other locations or independent; can hold locations, boxes, plates, and containers |
| Box              | Held in locations; can hold containers                                                    |
| Plate            | Held in locations; can hold wells                                                         |
| Container        | Held 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](/docs/v3-api-overview) 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:

```json
{
  "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:

```json
{
  "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](/docs/getting-started-with-the-v3-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.