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

# Write data

POST https://platform.labric.co/api/v1/data/write
Content-Type: application/json

Insert, upsert, update, or delete rows across tables, and write raw
series, in a single transaction.

Table writes target the organization's tables from get\_schema or core
tables such as experiment, operation, and carrier. They are applied in
request order, so one request can create a sample and then the
measurements that reference it: label a row with "\_ref": "s1" and point at
it with "@s1" from a foreign key column of any later row. A foreign key may
also be a lookup object such as \{"name": "S-001"} that matches exactly one
row that existed before the request. Primary keys are generated when
omitted where the table allows it, and are always returned in input
order.

A series holds the points of one parent row in a raw table as one list per
column. Writing a series replaces the parent's existing series in that
table, so repeating a write is safe. The server fills in the primary key,
the parent foreign key, and an integer order column.

Every row, series, and lookup is checked before anything is written, and
all of those problems are reported together in errors. Each has a path
naming its place in the request, such as tables\[1].rows\[0].sample. Keys
for upsert, update, and delete are matched as each table write is
applied, and any failure rolls back the whole request. The write is recorded under a job execution,
created if none is given. Reverting that execution deletes the rows it
created along with their series; updates, deletes, and series written to
existing parents are not undone.

The request body must be under 4.5 MB, which the row and series value
limits keep most requests within.

Requires an API key with the `write` scope.

Reference: https://docs.labric.co/api-reference/labric-api/data/write

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Request

### Body (application/json)

This endpoint expects a WriteDataRequest.

- `tables` (list of TableWrite, optional) — Table writes, applied in order. A row may reference only rows that appear before it. At most 10,000 rows across all table writes.
- `series` (list of SeriesWrite, optional) — Raw series, applied after the table writes. Writing a series replaces any series the parent already has in that table. At most 250,000 values across all columns of all series.
- `job_execution_id` (string, optional, nullable) — Job execution to record this write under. When omitted, one is created and returned so the write can be reverted as a unit.
- `job_name` (string, optional, nullable) — Name of the job an auto-created execution belongs to. Defaults to 'Off-Platform Manual Job'.
- `dry_run` (boolean, optional, default: false) — Run every validation and constraint check, then roll back instead of committing.
- `return_rows` (boolean, optional, default: false) — Include the written rows of each table write in its result.

## Response

### 200

OK

- `job_execution_id` (string, required, nullable) — Job execution the write was recorded under, for reverting it. Null on a dry run.
- `tables` (list of TableWriteResult, required) — One result per table write, in request order.
- `series` (list of SeriesWriteResult, required) — One result per series, in request order.
- `refs` (map from string to string, required) — Primary key of every row labeled with _ref.

## Errors

### 400 Bad Request Error

Bad Request

- `errors` (list of WriteDataProblem, required) — Problems with the request. Past 100, a final problem counts the ones left out.

### 401 Unauthorized Error

Unauthorized

- `detail` (string, required)

### 403 Forbidden Error

Forbidden

- `detail` (string, required)

### 404 Not Found Error

Not Found

- `detail` (string, required)

### 422 Unprocessable Entity Error

Unprocessable Content

- `detail` (list of map from string to any, required)

### 500 Internal Server Error

Internal Server Error

- `detail` (string, required)

## Types

### TableWrite

Rows to write to one table.

- `table` (string, required) — Table to write to: one of the organization's tables, or a core table such as experiment, operation, or carrier.
- `rows` (list of map from string to any, required) — Rows keyed by column name. A row may carry '\_ref': '\<label>' so later rows and series can point at it with '@\<label>'. A foreign key value may be an id, an '@\<label>' reference to an earlier row, or a lookup object such as \{'name': 'S-001'} that matches exactly one row that existed before the request. Omit a string 'id' primary key to have it generated.
- `mode` (enum, optional, default: insert) — 'insert' creates every row. 'upsert' updates rows that match on key and creates the rest. 'update' and 'delete' require every row to match on key.
  - Allowed values: `insert`, `upsert`, `update`, `delete`
- `key` (list of string, optional, nullable) — Columns that identify an existing row for upsert, update, and delete. Defaults to the primary key.
- `cascade` (boolean, optional, default: false) — For delete: also delete rows of other tables that reference the deleted rows. Rows of raw tables are always deleted with the row they belong to. Without cascade, a delete that other rows reference is rejected.
- `on_match` (enum, optional, default: overwrite) — How a matched row takes the provided columns. 'overwrite' replaces them. 'fill_missing' only sets columns that are currently null. Columns absent from the row are never changed.
  - Allowed values: `overwrite`, `fill_missing`

### SeriesWrite

A raw data series belonging to one parent row, given column by column.

- `table` (string, required) — Raw table that holds the series.
- `parent` (SeriesWriteParent, required) — Parent row that owns the series: an id, an '@\<label>' reference to a row written in this request, or a lookup object such as \{'name': 'M-001'}.
- `columns` (map from string to list of any, required) — Values keyed by column name. Every list holds one value per point and all lists have the same length. An integer 'order' column is filled with each point's position unless given here.
- `parent_column` (string, optional, nullable) — Foreign key column that points at the parent. Required only when the raw table has more than one foreign key.

### TableWriteResult

- `table` (string, required)
- `created` (integer, required)
- `updated` (integer, required)
- `deleted` (integer, required)
- `ids` (list of string, required) — Primary key of each input row, in input order.
- `cascade_deleted` (map from string to integer, optional) — Rows deleted because they referenced a deleted row, by table. A dry run reports them without deleting.
- `rows` (list of map from string to any, optional, nullable) — Written rows in input order, when return_rows is set.

### SeriesWriteResult

- `table` (string, required)
- `parent_id` (string, required)
- `points_written` (integer, required)
- `points_replaced` (integer, required) — Points of the parent's previous series that were removed.

### WriteDataProblem

- `path` (string, required, nullable) — Place in the request the problem concerns, such as tables[1].rows[0].sample or series[0].columns.value. Null when it concerns the request as a whole, such as a table constraint violation.
- `message` (string, required)

### SeriesWriteParent

Parent row that owns the series: an id, an '@\<label>' reference to a row written in this request, or a lookup object such as \{'name': 'M-001'}.

## Examples

**Request**

```json
{
  "tables": [
    {
      "table": "sample",
      "rows": [
        {
          "_ref": "s1",
          "name": "PVK-042",
          "thickness_nm": 412
        }
      ],
      "mode": "upsert",
      "key": [
        "name"
      ]
    },
    {
      "table": "pl_measurement",
      "rows": [
        {
          "_ref": "m1",
          "excitation_nm": 405,
          "sample": "@s1"
        }
      ]
    }
  ],
  "series": [
    {
      "table": "pl_raw",
      "parent": "@m1",
      "columns": {
        "intensity_counts": [
          1520,
          1610,
          1575
        ],
        "wavelength_nm": [
          700,
          701,
          702
        ]
      }
    }
  ]
}
```

**Response**

```json
{
  "job_execution_id": "6b2e9f41-8c3d-4a7e-b5f2-1d9c0e7a4b38",
  "tables": [
    {
      "table": "sample",
      "created": 1,
      "updated": 0,
      "deleted": 0,
      "ids": [
        "3f8a1c52-9d4e-4b6a-8e1f-7c2d5b9a0e64"
      ],
      "cascade_deleted": {},
      "rows": null
    },
    {
      "table": "pl_measurement",
      "created": 1,
      "updated": 0,
      "deleted": 0,
      "ids": [
        "a7d4e2b9-1c6f-4e83-9a5d-0b8f3c7e2d16"
      ],
      "cascade_deleted": {},
      "rows": null
    }
  ],
  "series": [
    {
      "table": "pl_raw",
      "parent_id": "a7d4e2b9-1c6f-4e83-9a5d-0b8f3c7e2d16",
      "points_written": 3,
      "points_replaced": 0
    }
  ],
  "refs": {
    "m1": "a7d4e2b9-1c6f-4e83-9a5d-0b8f3c7e2d16",
    "s1": "3f8a1c52-9d4e-4b6a-8e1f-7c2d5b9a0e64"
  }
}
```

**SDK Code**

```python
import requests

url = "https://platform.labric.co/api/v1/data/write"

payload = {
    "tables": [
        {
            "table": "sample",
            "rows": [
                {
                    "_ref": "s1",
                    "name": "PVK-042",
                    "thickness_nm": 412
                }
            ],
            "mode": "upsert",
            "key": ["name"]
        },
        {
            "table": "pl_measurement",
            "rows": [
                {
                    "_ref": "m1",
                    "excitation_nm": 405,
                    "sample": "@s1"
                }
            ]
        }
    ],
    "series": [
        {
            "table": "pl_raw",
            "parent": "@m1",
            "columns": {
                "intensity_counts": [1520, 1610, 1575],
                "wavelength_nm": [700, 701, 702]
            }
        }
    ]
}
headers = {
    "Authorization": "Bearer <api_key>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```