# Tables and fields

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

Table structure, field types, and access permissions.

## Tables and rows

[List tables](https://neondeerdata.com/docs/api/lookup-tables/list-tables/) returns the active tables your key can read. Use a table's ID with [Get a table](https://neondeerdata.com/docs/api/lookup-tables/get-table/) to inspect its fields. A table is listed only if the key can read at least one field.

Rows contain `id`, `object: "lookup_row"`, `version`, and `values`, keyed by field key. Use `version` as `expected_version` when a write should apply only if the row has not changed since you read it. An empty cell is `null`, distinct from `0`, `false`, and an empty string.

Rows are returned in ascending ID order, not creation order or a custom sort. Rows archived in a synchronized source are excluded. Synchronized member rows may include `roster_verified_at`, the last time the member was verified in the source roster.

## Field keys

Use the field's `key` in requests. A field labelled **SLA (Hours)** might have the key `sla_hours`. Changing the label does not rename the key.

The field schema also includes:

| Property     | Meaning                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------- |
| `type`       | The JSON value format.                                                                            |
| `filterable` | Whether you can filter the field now. **Lookup key** does not need to be enabled.                 |
| `unique`     | Whether nonempty values are unique after normalization. Empty cells can still match several rows. |

A readable field may not be filterable yet while the table is being prepared. Selecting fewer response fields does not grant access to other filter fields.

## Value formats

Send values in the field's JSON type. Use a number such as `25`, not `"25"`, and `true` or `false` for booleans.

| Field type  | Filter value and matching                                                                                          | Returned value                                            |
| ----------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
| `text`      | Text, trimmed and Unicode NFC; matching ignores case                                                               | Trimmed NFC text with its original case                   |
| `number`    | A finite JSON number; integer values must be safe integers                                                         | JSON number                                               |
| `boolean`   | JSON `true` or `false`                                                                                             | JSON boolean                                              |
| `date`      | Calendar date in `YYYY-MM-DD` form                                                                                 | A date such as `2027-03-01`                               |
| `timestamp` | Timestamp with uppercase `T`, and uppercase `Z` or a numeric offset; use seconds or milliseconds, not microseconds | UTC with milliseconds, such as `2026-10-29T14:30:00.000Z` |
| `email`     | Email address; matching ignores case                                                                               | Lowercase email address                                   |
| `phone`     | International number with a country code, such as `+1 415 555 0123`; extensions and letters are rejected           | E.164, such as `+14155550123`                             |
| `domain`    | Hostname such as `acme.example.com`; no scheme, path, port, or IP address                                          | Lowercase hostname with any trailing dot removed          |
| `url`       | An `http://` or `https://` URL; exact matching after trimming                                                      | Trimmed URL; case, path, and query are preserved          |
| `member`    | Attio workspace member ID as a UUID string                                                                         | Lowercase UUID string                                     |
| `record`    | An object containing exactly `objectId` and `recordId`, both UUIDs                                                 | The same object with lowercase UUID strings               |

Equivalent timestamp offsets match the same instant. JavaScript's `Date.prototype.toISOString()` returns a supported format. For Python datetimes with fractional seconds, use `value.isoformat(timespec="milliseconds")`.

A record reference uses both IDs:

```json
{
  "objectId": "4e2d6a8b-7c1f-4d3e-9a5b-2c6f8d0e1a3b",
  "recordId": "8f3b1c5d-2e6a-4b7c-9d0e-3f5a7b1c2d4e"
}

```

## Permissions

An admin chooses which tables and fields a key can read. **All tables** and **All fields** include later additions. Saved changes apply to the next request.

Unreadable tables are omitted from lists and return `404 resource_not_found` when requested directly. Unreadable fields are omitted and cannot be filtered. A table becomes unavailable if none of the key's selected fields remain readable.

Your application decides which of its users can see the returned data. A key does not inherit each user's Neon Deer permissions. See [Authentication](https://neondeerdata.com/docs/api/authentication/).

## Write access

A key with `lookup_tables:write` can change permitted fields. Use the value formats above for writes, and send `null` to clear a cell. Fields omitted from an update keep their values. Empty strings are not a substitute for clearing a cell.

Custom tables support adding, updating, and deleting rows. On tables filled from Attio or by an app, update only your own fields on existing rows. You cannot add or remove those rows or write values maintained by the source. Lookup-row writes change the Neon Deer table; they do not update Attio records. See [Writing rows](https://neondeerdata.com/docs/api/lookup-tables/writing/).

## Changes during a read

Data and permissions can change between pages. Keep the table and its readable fields unchanged when you need a consistent read. Restarting and removing duplicate IDs does not recover every concurrent change.

Exposing a field to an API key locks its type. Removing that access later does not unlock the type. An admin can still archive the field or change which keys can read it. See [Versioning](https://neondeerdata.com/docs/api/versioning/) and [Pagination](https://neondeerdata.com/docs/api/lookup-tables/pagination/).
