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

# Working with Molecules

Molecules are GA, so no header is needed. For how Compounds, Forms, and counterparts relate, see the [concept model](/docs/introduction-to-chemistry#concept-model).

| Operation  | Endpoint                        |
| ---------- | ------------------------------- |
| Create one | `POST /molecule`                |
| Get one    | `GET /molecule/{molecule_id}`   |
| List       | `GET /molecule/items`           |
| Update one | `PATCH /molecule/{molecule_id}` |

Higher-volume create and update variants are covered in [Choosing a create endpoint](#choosing-a-create-endpoint).

## The structure

Every molecule carries a `chemicalStructure` on input:

* `structureFormat`: `smiles` or `molfile`.
* `value`: the structure itself. An **empty SMILES** creates a structureless molecule (a valid, named placeholder with no structure).

On read, the molecule reports three SMILES fields. The request's `chemicalStructure` is **not** echoed back.

| Field                    | What it is                                                                                                                                            |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `canonicalSmiles`        | The canonicalized structure with salts and solvents **stripped** (base only)                                                                          |
| `originalSmiles`         | Exactly what you supplied (null if you supplied only a molfile)                                                                                       |
| `smilesWithCounterparts` | The full structure: base **plus** salt/solvent counterparts as fragments, repeated per ratio. Equals `canonicalSmiles` when there are no counterparts |

## Creating a molecule

```bash
curl -X POST 'https://{tenant_name}.benchling.com/api/v3/molecule' \
  -u "$API_KEY:" \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Aspirin",
    "chemicalStructure": { "structureFormat": "smiles", "value": "CC(=O)OC1=CC=CC=C1C(=O)O" },
    "folderId": "lib_XXXXXXXX"
  }'
```

`name`, `chemicalStructure`, and `folderId` are required. `schemaId` and `schemaFields` are optional; see [Working with Molecule Schemas](/docs/working-with-molecule-schemas) for discovering these.

## Registering a molecule

Registration is controlled by three fields on create or update:

* `registryId` is the on/off switch. When present, the molecule is registered into that registry. Setting it to `null` on update **unregisters** the molecule.
* Plus **exactly one** of:
  * `namingStrategy`: how Benchling assigns the registry ID and name (for example, `NEW_IDS` to auto-generate a new ID and keep the name). See [naming strategies](/docs/registering-entities#naming-strategies) for all of them.
  * `entityRegistryId`: supply your own ID.

Providing both, or neither, is rejected.

## Updating and archiving a molecule

To archive or restore a molecule, use the same update endpoint: `PATCH /molecule/{molecule_id}` with `archived: true` (optionally with an `archiveReason`) to archive, or `archived: false` to restore.

### Why can't I change a molecule's structure?

If the molecule is linked to a [reaction](/docs/working-with-reactions), editing its `chemicalStructure` returns a `400` ("linked to a reaction"). Editing its name or schema fields still works; only the structure edit is blocked. (Salt and solvent library structures are separately [immutable after creation](/docs/working-with-the-salt-and-solvate-library#fields), by design.)

## Choosing a create endpoint

There are four ways to create molecules. Pick by **how many** you're creating, and by whether you want Benchling to auto-create the parent Compound for salt forms.

| I want to create…                                      | Endpoint                                           | What you get back                                  | Stability |
| ------------------------------------------------------ | -------------------------------------------------- | -------------------------------------------------- | --------- |
| One molecule                                           | `POST /molecule`                                   | The created molecule, immediately                  | GA        |
| A handful (up to 25) together                          | `POST /molecule:batch-create`                      | All created molecules, immediately. All-or-nothing | GA        |
| Many                                                   | `POST /molecule:bulk-create`                       | A task to poll for results                         | GA        |
| Many, auto-creating the parent Compound for salt forms | `POST /molecule:bulk-create-molecules-with-parent` | A task to poll for results                         | Beta      |

> **Note**
>
> **"Batch" here means an operation, not an entity.** `:batch-create` is a synchronous multi-create. It is *not* related to a Batch/Lot, a physical sample of a registered entity, which isn't exposed on the V3 API at all. Creating molecules with `:batch-create` never touches Batches.

On the standard endpoints, a Form is linked to its parent Compound only if you supply the Compound's ID yourself (in `schemaFields`). Nothing is auto-resolved. `:bulk-create-molecules-with-parent` is the only endpoint that resolves the parent for you: it finds a matching Compound by structure and reuses it, or creates one if none exists.

### Using the bulk endpoints

Unlike `:batch-create`, the `:bulk-*` endpoints don't take an inline array. You stage your rows as a file first. The [Bulk Import API guide](/docs/bulk-import) covers the flow in full.

> **Info**
>
> `:bulk-create` itself is GA, but the scratch-file upload and the task-polling endpoints it depends on are beta. Send `EARLY-ACCESS: true` on steps 1 and 3.

1. Build a **JSONL** payload: one `{"request_id": "...", "input_object": { ... }}` per line, where `input_object` has the same fields as a single create and each `request_id` is unique. Upload it with `POST /scratch-file:create-and-upload` and `{ "filename": "molecules.jsonl", "contents": "<your JSONL>" }` (inline `contents` is UTF-8, up to 10 MiB). You get back a `scrfile_...` ID.
2. Call `POST /molecule:bulk-create` with the body `{ "fileId": "scrfile_..." }`. It returns `202` with a `taskId` and `pollingUri`.
3. Poll `GET /tasks/bulk/{task_id}` until it completes. Fetch the created IDs from `.../successful-results` and any failures from `.../errors`. Both are keyed by your `request_id`.