Skip to navigation

Bulk Transfer: Create and Fill (JavaScript)

Create a plate, transfer an entity into every well with one bulk call, and poll the resulting async task to completion.

This example uses the V3 API. 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. 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.

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

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

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

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

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

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

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);
});