---
title: "POST /v1/jobs/{jobId}/plate"
description: "Save wells on a job's plate"
full: true
---

**Auth:** required. Send an `ivy_` API key (`Authorization: Bearer ivy_…` or `X-Ivy-Api-Key`), a Clerk session JWT (`Authorization: Bearer …`) or the Clerk `__session` cookie (browser sessions). See [Authentication](/docs/api/authentication).

Saves one or more wells on a job you can write to. Each update is keyed by `row` and `col`: an existing well at that position is replaced, otherwise it is created. The well name is derived from `row` and `col` when you do not send `well`.

If you send `compound_smiles` without `compound_id`, the SMILES is resolved to a molecule id, which is stored on the well and linked to the job. If you do not send `concentration_uM`, it is computed as `stock_uM` divided by `dilution_ratio` when both are present and `dilution_ratio` is above zero. Updates whose `row` or `col` is not a finite number are dropped.

After saving, the route refreshes the job's rollups and appends a plate-update event with the previous and new values to the job's manifest. Both steps are best effort: `manifestEventWritten` tells you whether the manifest event was written, and a failure in either does not fail the request. A project viewer can read a plate but not save it; saving returns 404 for them.

## Request

#### Path parameters

- `jobId` (string (uuid)) —
UUID of the job.

#### Request body

- `updates` (object[]) —
Wells to save. Must contain at least one entry with a numeric `row` and `col`.

##### Example

```json
{
  "updates": [
    {
      "row": 1,
      "col": 1,
      "compound_smiles": "CCO",
      "stock_uM": 10000,
      "dilution_ratio": 100,
      "volume_uL": 25
    }
  ]
}
```

## Responses

### `200` OK

The wells were saved

Response shape:

- `success` (enum (true)) —

- `upserted` (integer) —
Number of wells saved.

- `manifestEventWritten` (boolean) —
Whether the plate-update event was appended to the job's manifest. The wells are saved either way.

### `400` Bad Request

The job id is missing; the body is not valid (`code` is `VALIDATION_ERROR` and `details.issues` lists the problems); `updates` is empty; or no update has a numeric `row` and `col`

Response shape: `object`

### `401` Unauthorized

Authentication required

Response shape: `object`

### `404` Not Found

The job does not exist, you cannot write to it (a project viewer cannot), it is outside your API key's project scope, or the id is not a job UUID. All of these return the same body.

Response shape: `object`

### `429` Too Many Requests

Rate limit exceeded

Response shape: `object`

### `500` Internal Server Error

The job row could not be loaded, or saving failed

Response shape: `object`

**curl**

```bash
curl -X POST 'https://ivybiosciences.com/api/v1/jobs/{jobId}/plate' \
  -H "Authorization: Bearer $IVY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "updates": [
    {
      "row": 1,
      "col": 1,
      "compound_smiles": "CCO",
      "stock_uM": 10000,
      "dilution_ratio": 100,
      "volume_uL": 25
    }
  ]
}'
```

**JavaScript**

```js
const res = await fetch('https://ivybiosciences.com/api/v1/jobs/{jobId}/plate', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.IVY_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
  "updates": [
    {
      "row": 1,
      "col": 1,
      "compound_smiles": "CCO",
      "stock_uM": 10000,
      "dilution_ratio": 100,
      "volume_uL": 25
    }
  ]
}),
});
const data = await res.json();
```

**Python**

```python
import os, requests

res = requests.post(
    "https://ivybiosciences.com/api/v1/jobs/{jobId}/plate",
    headers={"Authorization": f"Bearer {os.environ['IVY_API_KEY']}"},
    json={
  "updates": [
    {
      "row": 1,
      "col": 1,
      "compound_smiles": "CCO",
      "stock_uM": 10000,
      "dilution_ratio": 100,
      "volume_uL": 25
    }
  ]
},
)
data = res.json()
```
