# Writing rows

Source: https://neondeerdata.com/docs/api/lookup-tables/writing/

Add, update, and delete lookup-table rows with a Read and write API key.

## Choose what the application can change

An organization admin grants **Read & write** in **Settings → Integrations**. Keys keep their existing permissions until an admin changes them. A key can write only tables and fields it can read. Rotation does not grant new permissions.

| Table                                             | Allowed writes                                                                                 |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| A custom table your organization created          | Add rows, update values, add or update by a lookup key, and delete rows.                       |
| A table synced from Attio or maintained by an app | Update your own fields on existing rows. The source controls row membership and synced values. |

To delete a row, set the table's field access to **All fields**. Selecting individual fields does not permit row deletion, even if you select every current field. Writes change lookup tables in Neon Deer, not Attio records. Use the separate [table settings](https://neondeerdata.com/docs/api/lookup-tables/settings/) and [field endpoints](https://neondeerdata.com/docs/api/lookup-tables/fields/) to change the table itself. Those operations need their own permissions.

## Choose an operation

- [Add a row](https://neondeerdata.com/docs/api/lookup-tables/create-row/) when the row should not already exist.
- [Update a row](https://neondeerdata.com/docs/api/lookup-tables/update-row/) to change selected values in a known row.
- [Add or update rows](https://neondeerdata.com/docs/api/lookup-tables/upsert-rows/) to match each item by a lookup key, updating a match or adding a row when none exists.
- [Delete a row](https://neondeerdata.com/docs/api/lookup-tables/delete-row/) to remove it from a custom table.

Use field keys, not labels. Values follow the [field's JSON type](https://neondeerdata.com/docs/api/lookup-tables/concepts/#value-formats). An omitted field keeps its existing value; `null` clears it. An empty string is not a clearing instruction.

## Avoid overwriting another change

Rows include a `version`. Send it as `expected_version` when an update or deletion must apply only to the version you read. Add-or-update items also accept this property when updating an existing row.

If the row has changed, the API returns `409 version_conflict`. Read it again and decide whether your change is still appropriate. Omitting `expected_version` does not check for an intervening change.

## Retry a write

**Add a row** and **Add or update rows** require `Idempotency-Key`. Generate a UUID for each new request and retain it with the exact request body. After an uncertain response, retry promptly with the same key, target, and body. While the original result remains available, the API returns it without repeating the write and includes `Idempotent-Replayed: true`.

Reusing a key with a different request returns `409 idempotency_conflict`. An earlier result can also become unavailable, for example after a row it wrote is deleted. Do not assume request keys provide permanent deduplication. Inspect the table before sending a new request when the original outcome is uncertain.

PATCH and DELETE do not use this request-key mechanism. After an uncertain response, read the row before retrying. A version check can prevent a repeated update from overwriting a later change. A deleted row is no longer available to read.

Follow `Retry-After` for temporary errors. See [Errors](https://neondeerdata.com/docs/api/errors/) and [Rate limits](https://neondeerdata.com/docs/api/rate-limits/) for recovery.

A field used for add-or-update must have a lookup. Create one with [Add lookup](https://neondeerdata.com/docs/api/lookup-tables/lookups/) before using it as `key_field`.

## Check every item in an add-or-update response

By default, each item succeeds or fails independently. `200` means the request was accepted, even if individual items failed. Read `data.summary` and every entry in `data.results`. Successful items remain applied.

Set `all_or_nothing: true` when the entire request must succeed together. If any item would fail, the API returns `422 batch_rejected`, writes nothing, and includes `results` identifying errors and items marked `not_applied`.

## Try with test data

The [Playground](https://neondeerdata.com/docs/api/testing/#try-a-write) can send writes using the key's saved permissions. Its **Send request** control applies changes to the actual table, including immediate deletion without another confirmation. Use a custom table with disposable test rows, then read the affected rows back.
