> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usenexio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Families and the generic read

> Read any registered family by plane and family name as typed columns and rows, bounded to a person, and page through it with a cursor.

A family is one named dataset that Records can read, such as one table of a system of record's warehouse replica. Families are grouped into planes, and each plane is one registered source area with a stable key. The generic family read, `GET /api/v1/records/families/{plane}/{family}`, reads any registered family by those two names. It is the core contract of Records: its behavior is the same for every family, and the family's own registration decides the columns and the row bounds. What a plane and a family are, and why the set is a registry, is on the [Records overview](/data-services/overview).

## How it works

1. Nexio resolves the person's authority, as for every read ([Authority and scope](/data-services/scope)).
2. Nexio looks up `{plane}/{family}` in the registry. An unregistered pair answers 404 `not_found`.
3. The family's serve grain decides which key filter the read accepts and how rows are bounded to the person ([Serve grains](#serve-grains)).
4. Nexio reads the family's declared columns from your warehouse at request time, one page at a time.
5. The response carries the column names once and each row as a list of values in that order, with the `serving`, `family_serving` and `scope` blocks.

## What a family declares

| Declaration    | What it decides                                                                                                                                        |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Plane and name | The address: `{plane}/{family}`.                                                                                                                       |
| Columns        | The column set the read serves, in a fixed order. The `columns` array in every response is that list, verbatim.                                        |
| Column types   | How each value is served. Numeric columns are exact decimal text, never floats, so parse them with a decimal library.                                  |
| Column class   | Whether a column identifies a person directly (an email address, a name, free text). Those columns follow [the positional rule](#the-positional-rule). |
| Serve grain    | How rows are bounded to the person, and which key filter the read takes.                                                                               |

The response is columnar rather than one object per row because the row shape belongs to the registration, not to a per-family type: a family registered in a later release serves through the same route with no client change beyond reading its `columns`.

## Serve grains

Every family declares one of four grains. The grain names `policy` and `client` are the two keyed record kinds: a `client` is a top-level record and a `policy` is a record under it.

| Grain    | Key filter it takes | Rows served                                                                                                                                       |
| -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `policy` | `policy_key`        | Rows attached to the policy records the person may open.                                                                                          |
| `client` | `client_key`        | Rows attached to the client records the person may open.                                                                                          |
| `table`  | none                | The whole table, under every scope. Reference tables and vocabularies use this grain.                                                             |
| `org`    | none                | The whole table to an `All` or `Platform` scope. A person-scoped caller gets 200 with no rows and `family_serving.notice.code` `org_scoped_only`. |

A key filter the grain does not take answers 400 `invalid_request`, and so does a malformed key. When a person's scope admits no rows at all, the read answers 200 with an empty page.

## The positional rule

Some columns identify a person directly. Where such a column is served depends on the position of the read:

* On a `policy` or `client` family read without a key filter, the column is present and every value is `null`.
* On a read narrowed by `policy_key` or `client_key` to a record the person may open, the column is served.
* On a `table` or `org` family, the column is not served at all: it is left out of `columns`, because those grains take no key that could narrow the read.

So a column that is `null` on a broad read and filled on a keyed read is expected. The broad read is not incomplete. Field classes, such as compensation fields, are withheld on every read by the person's policy instead; see [Field classes](/data-services/scope#field-classes).

## Keys

Keys are opaque strings. Read them from a row, a register or a keyed read, and send them back unchanged; never build or parse them. A key filter never widens the read: the person's scope bounds the rows first, and the key narrows inside it.

## Read a family

| Parameter                    | Meaning                                                                                                                                                             |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `{plane}`, `{family}`        | The family's registered address.                                                                                                                                    |
| `policy_key` or `client_key` | Optional. Narrows the read to one record, on a family whose grain takes that key.                                                                                   |
| `limit`                      | Rows per page. A positive integer; the default is 50, and a value above 10,000 is treated as 10,000. Zero, a negative number or text answers 400 `invalid_request`. |
| `cursor`                     | The previous page's `page.next_cursor`.                                                                                                                             |
| `connection_id`              | Required when the organization has more than one qualifying connection ([Records overview](/data-services/overview#where-the-data-comes-from)).                     |

The example below reads a reference table, so it takes no key filter.

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.usenexio.com/api/v1/records/families/book/products?limit=2" \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "X-Nexio-Acting-Principal: user_01J8Z3K4M5N6P7Q8R9S0T1U2V3" \
    -H "X-Nexio-Acting-Email: dana.ortiz@harborinsurance.example"
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.get(
      "https://api.usenexio.com/api/v1/records/families/book/products",
      params={"limit": 2},
      headers={
          "Authorization": "Bearer " + os.environ["NEXIO_API_KEY"],
          "X-Nexio-Acting-Principal": "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3",
          "X-Nexio-Acting-Email": "dana.ortiz@harborinsurance.example",
      },
      timeout=30,
  )
  response.raise_for_status()
  body = response.json()
  rows = [dict(zip(body["columns"], row)) for row in body["data"]]
  print(rows)
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.usenexio.com/api/v1/records/families/book/products?limit=2", {
    headers: {
      Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
      "X-Nexio-Acting-Principal": "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3",
      "X-Nexio-Acting-Email": "dana.ortiz@harborinsurance.example",
    },
  });
  if (!response.ok) throw new Error(`family read failed: ${response.status}`);
  const body = await response.json();
  const rows = body.data.map((row: unknown[]) =>
    Object.fromEntries(body.columns.map((column: string, i: number) => [column, row[i]])),
  );
  console.log(rows);
  ```
</CodeGroup>

```json 200 OK theme={null}
{
  "plane": "book",
  "family": "products",
  "grain": "table",
  "columns": [
    "dim_product_sk",
    "dw_inserted_at",
    "dw_updated_at",
    "line_of_business",
    "product_category",
    "product_id",
    "product_name",
    "product_operating_name",
    "product_type"
  ],
  "data": [
    ["8f2c1a7e", "2026-03-02T08:14:00Z", "2026-09-20T06:02:11Z", "Commercial Auto", "Commercial", "4412", "Business Auto", "Northwind Business Auto", "Package"],
    ["b71d09c4", "2026-03-02T08:14:00Z", "2026-09-20T06:02:11Z", "General Liability", "Commercial", "4418", "Commercial General Liability", "Example Specialty CGL", "Monoline"]
  ],
  "page": {
    "limit": 2,
    "next_cursor": "eyJyIjoiZ2VuZXJpY19mYW1pbHkiLCJrIjpbImI3MWQwOWM0Il19"
  },
  "serving": {
    "binding_id": "2c4e6a8b-0d1f-4a3c-9e5b-7d9f1a3c5e7b",
    "overlay_rev": 0,
    "as_of": "2026-09-23T14:20:05Z",
    "source": {
      "mode": "query_first",
      "current": true,
      "fetched_at": "2026-09-23T14:20:05Z"
    }
  },
  "family_serving": {
    "family": "products",
    "plane": "book",
    "presence": "present",
    "batch_set_id": null,
    "published_at": null
  },
  "scope": { "kind": "Self", "selection": "boundary", "selection_source": "none" }
}
```

| Field              | Meaning                                                                                                                                            |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `plane`, `family`  | The address you read.                                                                                                                              |
| `grain`            | The family's serve grain.                                                                                                                          |
| `columns`          | The declared column names, in order.                                                                                                               |
| `data`             | One array per row, with values in `columns` order.                                                                                                 |
| `page.limit`       | The number of rows on this page, not the limit you asked for.                                                                                      |
| `page.next_cursor` | The cursor for the next page, or `null` on the last page.                                                                                          |
| `serving`          | Which connection answered and when the read ran ([Completeness and errors](/data-services/completeness#when-the-data-was-read-the-serving-block)). |
| `family_serving`   | The family's `presence` (`present`, `absent` or `unknown`) and, on a refused-by-design read, a `notice` with `code` and `message`.                 |
| `scope`            | The scope and selection the rows were served at ([Authority and scope](/data-services/scope#the-scope-block)).                                     |

## Pagination and cursors

The family read pages by keyset: each page continues after the last row the previous page returned.

* Follow `page.next_cursor` until it is `null`. An empty `data` array with a `null` cursor is a complete, empty answer.
* The cursor is opaque. It fixes the family and the key filter of the first page. Send `cursor`, an optional `limit`, `connection_id` and the identity headers on each next page; sending `policy_key` or `client_key` with a cursor answers 400 `cursor_filter_mismatch`.
* A cursor from another family, or one that is malformed, answers 400 `invalid_cursor`. A cursor that no longer matches how the family is served answers 409 `cursor_expired`: start again from the first page.
* Each page is a new current read. A row created or changed during a walk can appear on a later page, or be missed. [When the data changes during a walk](/data-services/registers#when-the-data-changes-during-a-walk) applies here too.

## Composite reads and dedicated routes

The generic read serves one family per call. Dedicated routes can exist beside it for a connected system type: registers, keyed reads of one record and its sub-resources, composite reads that compose many families under one authority in one call, and document content, which fetches a file from the system of record's own API. A composite read reports an outcome for each family it composes, in the same terms as a single family read (`presence`, plus `coverage` and a `reason`); the model is on [Completeness and errors](/data-services/completeness#per-family-coverage). A dedicated route that serves one family returns its plane in `family.plane`; the generic read returns it in `plane`.

## The registry today

Every plane registered today belongs to one supported system type, AMS360, replicated to Snowflake. Three of its families are served only through dedicated reads, so the generic read reaches 100 of the 103; reading one of the three here answers 404 `not_found`.

| Plane                | Families | Readable through the generic read | Grains                                      |
| -------------------- | -------- | --------------------------------- | ------------------------------------------- |
| `book`               | 19       | 19                                | 1 `client`, 3 `policy`, 3 `table`, 12 `org` |
| `rawams_activity`    | 3        | 3                                 | 1 `policy`, 2 `org`                         |
| `rawams_billing`     | 10       | 9                                 | 4 `client`, 1 `policy`, 2 `table`, 3 `org`  |
| `rawams_certs`       | 4        | 4                                 | 2 `client`, 1 `policy`, 1 `org`             |
| `rawams_claims`      | 11       | 11                                | 3 `policy`, 8 `org`                         |
| `rawams_contacts`    | 20       | 20                                | 6 `client`, 10 `policy`, 4 `org`            |
| `rawams_documents`   | 3        | 3                                 | 3 `org`                                     |
| `rawams_exposures`   | 6        | 6                                 | 6 `policy`                                  |
| `rawams_forms`       | 4        | 4                                 | 3 `policy`, 1 `table`                       |
| `rawams_markets`     | 1        | 0                                 | 1 `table`                                   |
| `rawams_personnel`   | 11       | 11                                | 3 `policy`, 8 `table`                       |
| `rawams_renewals`    | 5        | 4                                 | 1 `client`, 3 `policy`, 1 `org`             |
| `rawams_submissions` | 5        | 5                                 | 2 `client`, 1 `policy`, 2 `org`             |
| `rawams_vocabulary`  | 1        | 1                                 | 1 `table`                                   |
| **Total**            | **103**  | **100**                           |                                             |

## Errors

| Status | Code                     | Cause                                                                                                        | Fix                                                                                                     |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`        | A `limit` that is not a positive integer, a key filter the family's grain does not take, or a malformed key. | Correct the parameter.                                                                                  |
| 400    | `invalid_cursor`         | The cursor is malformed or belongs to another family.                                                        | Use the cursor exactly as returned.                                                                     |
| 400    | `cursor_filter_mismatch` | `policy_key` or `client_key` sent with a cursor.                                                             | Send only the cursor and `limit`.                                                                       |
| 404    | `not_found`              | The plane and family pair is not registered, or `connection_id` names no qualifying connection.              | Check the address against the registry.                                                                 |
| 409    | `cursor_expired`         | The cursor no longer matches how the family is served.                                                       | Restart from the first page.                                                                            |
| 409    | `book_unavailable`       | No qualifying connection, or the warehouse could not answer.                                                 | See [Completeness and errors](/data-services/completeness#book_unavailable-reasons-and-retry-guidance). |

Authority refusals (`identity_unmapped`, `scope_unavailable` and the rest) are on [Authority and scope](/data-services/scope#refusal-codes).

## Next

<CardGroup cols={2}>
  <Card title="Read a family" href="/api-reference/data-services/read-family">Endpoint reference.</Card>
  <Card title="Completeness and errors" href="/data-services/completeness">Tell every outcome apart.</Card>
  <Card title="Authority and scope" href="/data-services/scope">How the person's rows are bounded.</Card>
  <Card title="Registers and pagination" href="/data-services/registers">Register paging and totals.</Card>
</CardGroup>
