> 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.

# Bulk Transfer: Create and Fill (JavaScript)

> **Info**
>
> This example uses the [V3 API](/docs/v3-api-overview). `POST /api/v3/container:transfer` is
> stable, but creating the plate, listing its wells, and looking up the unit are beta, so those
> requests send the `EARLY-ACCESS: true` header. See the
> [V3 Stability Guidelines](/docs/stability-guidelines).
> [Introduction to Transfers](/docs/introduction-to-transfers) covers the transfer endpoint in more
> depth.

There is no V3 JavaScript SDK, so this example calls the API directly with `fetch`, which is built
into Node.js 18 and later. Replace `tenant`, `apiKey`, and the `loc_`/`src_`/`pltsch_`/`bfi_` IDs
with values from your own tenant.

#### Config

Set the tenant URL, API key, and task wait limit. `headers` carries a basic-auth header built
from your API key; `betaHeaders` adds `EARLY-ACCESS: true` for the beta operations.

**`JavaScript (lines 1-10)`**

```javascript JavaScript (lines 1-10)
const tenant = 'https://yourcompany.benchling.com';
const apiKey = '<your-api-key>';
const maxTaskWait = 30;

const headers = {
    Authorization: 'Basic ' + Buffer.from(apiKey + ':').toString('base64'),
    'Content-Type': 'application/json',
};
// beta V3 operations also need the EARLY-ACCESS header
const betaHeaders = { ...headers, 'EARLY-ACCESS': 'true' };
```

#### Helper functions

Three helpers. `sleep` wraps `setTimeout` in a promise, `request` sends a JSON request to
`/api/v3` and throws on any non-2xx response, and `waitForTransferTask` polls
`GET /api/v3/tasks/container/transfer/{task_id}` at a given interval until the task is `COMPLETED`, throwing if
it is `FAILED` and giving up after `maxTaskWait` attempts.

**`JavaScript (lines 12-40)`**

```javascript JavaScript (lines 12-40)
function sleep(seconds) {
    return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}

async function request(method, path, body, requestHeaders = headers) {
    const res = await fetch(tenant + '/api/v3' + path, {
        method,
        headers: requestHeaders,
        body: body === undefined ? undefined : JSON.stringify(body),
    });
    if (!res.ok) {
        throw new Error(method + ' ' + path + ' failed: ' + res.status + ' ' + await res.text());
    }
    return res.json();
}

async function waitForTransferTask(taskId, pollInterval) {
    for (let attempt = 0; attempt < maxTaskWait; attempt++) {
        await sleep(pollInterval);
        const task = await request('GET', '/tasks/container/transfer/' + taskId);
        if (task.status === 'COMPLETED') {
            return task;
        }
        if (task.status === 'FAILED') {
            throw new Error('Transfer task failed: ' + JSON.stringify(task));
        }
    }
    throw new Error('Task exceeded max wait time');
}
```

#### Create the plate

Call `POST /api/v3/well-plate` with the plate schema, the storage location, and the project.
This is the V3 replacement for V2 `POST /plates` for fixed (well) plates; matrix plates use
`POST /api/v3/matrix-plate` instead. Don't use the general `/api/v3/plate` endpoint, which is
being deprecated.

**`JavaScript (lines 43-50)`**

```javascript JavaScript (lines 43-50)
    // create the plate
    const plate = await request('POST', '/well-plate', {
        parentStorageId: 'loc_Bn4KJWlo', // shelf 10 of Example Freezer 1
        projectId: 'src_syE3mA0y', // Informatics testing project
        schemaId: 'pltsch_uzmzohPH', // plate schema
    }, betaHeaders);
    console.log(plate.id);
    console.log(plate.name);
```

#### List the wells and the unit

The created plate doesn't embed its wells, so list them with
`GET /api/v3/well-plate/{well_plate_id}/positions/items`; each well's `id` is the container ID to
transfer into. V3 measurements take a unit ID, so look up milliliters with
`GET /api/v3/unit/items`.

**`JavaScript (lines 52-55)`**

```javascript JavaScript (lines 52-55)
    // list the wells and look up the unit ID for mL
    const wells = await request('GET', '/well-plate/' + plate.id + '/positions/items', undefined, betaHeaders);
    const units = await request('GET', '/unit/items?name.anyOf=milliliter', undefined, betaHeaders);
    const milliliter = units.items[0].id;
```

#### Fill the plate wells

Build one transfer object per well, with the entity as an unlimited source. A single call to
`POST /api/v3/container:transfer` performs all of them.

**`JavaScript (lines 57-65)`**

```javascript JavaScript (lines 57-65)
    // build one transfer per well: the entity into the well
    const transfers = wells.items.map(well => ({
        destinationContainerId: well.id,
        sourceEntityId: 'bfi_SnNFMl28', // plasmid lot PLL001
        transferQuantity: { unitId: milliliter, value: 10 },
    }));

    // one call to the transfer endpoint fills every well
    const link = await request('POST', '/container:transfer', { transfers });
```

#### Await task completion

Transfers are asynchronous, so the response carries a `taskId` rather than the finished result.
Poll it with the helper and log the final status; if any step fails, the `catch` logs the error.

**`JavaScript (lines 67-74)`**

```javascript JavaScript (lines 67-74)
    // poll the task until it finishes
    const task = await waitForTransferTask(link.taskId, 1);
    console.log('task completed with status: ' + task.status);
}

main().catch(err => {
    console.log('error: ' + err);
});
```

#### Complete files

**`JavaScript`**

```javascript title="JavaScript"
const tenant = 'https://yourcompany.benchling.com';
const apiKey = '<your-api-key>';
const maxTaskWait = 30;

const headers = {
    Authorization: 'Basic ' + Buffer.from(apiKey + ':').toString('base64'),
    'Content-Type': 'application/json',
};
// beta V3 operations also need the EARLY-ACCESS header
const betaHeaders = { ...headers, 'EARLY-ACCESS': 'true' };

function sleep(seconds) {
    return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}

async function request(method, path, body, requestHeaders = headers) {
    const res = await fetch(tenant + '/api/v3' + path, {
        method,
        headers: requestHeaders,
        body: body === undefined ? undefined : JSON.stringify(body),
    });
    if (!res.ok) {
        throw new Error(method + ' ' + path + ' failed: ' + res.status + ' ' + await res.text());
    }
    return res.json();
}

async function waitForTransferTask(taskId, pollInterval) {
    for (let attempt = 0; attempt < maxTaskWait; attempt++) {
        await sleep(pollInterval);
        const task = await request('GET', '/tasks/container/transfer/' + taskId);
        if (task.status === 'COMPLETED') {
            return task;
        }
        if (task.status === 'FAILED') {
            throw new Error('Transfer task failed: ' + JSON.stringify(task));
        }
    }
    throw new Error('Task exceeded max wait time');
}

async function main() {
    // create the plate
    const plate = await request('POST', '/well-plate', {
        parentStorageId: 'loc_Bn4KJWlo', // shelf 10 of Example Freezer 1
        projectId: 'src_syE3mA0y', // Informatics testing project
        schemaId: 'pltsch_uzmzohPH', // plate schema
    }, betaHeaders);
    console.log(plate.id);
    console.log(plate.name);

    // list the wells and look up the unit ID for mL
    const wells = await request('GET', '/well-plate/' + plate.id + '/positions/items', undefined, betaHeaders);
    const units = await request('GET', '/unit/items?name.anyOf=milliliter', undefined, betaHeaders);
    const milliliter = units.items[0].id;

    // build one transfer per well: the entity into the well
    const transfers = wells.items.map(well => ({
        destinationContainerId: well.id,
        sourceEntityId: 'bfi_SnNFMl28', // plasmid lot PLL001
        transferQuantity: { unitId: milliliter, value: 10 },
    }));

    // one call to the transfer endpoint fills every well
    const link = await request('POST', '/container:transfer', { transfers });

    // poll the task until it finishes
    const task = await waitForTransferTask(link.taskId, 1);
    console.log('task completed with status: ' + task.status);
}

main().catch(err => {
    console.log('error: ' + err);
});
```