> ## 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.

# Source tables

> Understand the estate: the source tables registered on a connection, why they live on the connection rather than under Records, who reads them, and how to read one.

Records serves **collections**: accounts, policies, claims, contacts, documents and the rest, each named for what a person opens, lists or counts, in the shape an application needs. Behind every collection sits the system of record's own data, held in **source tables**: the objects of the system's warehouse replica, such as its company table, its contact table or its code dictionary, plus the curated warehouse tables Nexio reads beside them. The full set of source tables registered on a connection is the **estate**.

The estate is served on the connection, at `GET /connections/{id}/tables` and `GET /connections/{id}/tables/{table}`. Nothing under `/records` names a source table. Product code reads collections; the estate is the window onto the raw rows a collection is built from.

## What a source table is

A source table is one object of a connected system, registered with Nexio and read by the system's own name for it. Its registration decides everything a read does:

| Declaration | What it decides |
| - | - |
| Name | The `{table}` path segment: the object's name, lower case. An AMS360 table drops the `AFW_` prefix (`AFW_COMPANY` is `company`). |
| Columns | Every column a read serves, each with a field class. A row carries exactly these columns. |
| Owner | The record the table hangs off: `policy`, `account`, `claim`, or `none` for a table read whole. An owned table is read one owner at a time. |
| Match filter | The one column, if any, a read can narrow on, offered as a filter named after it. Some tables are read one match value at a time. |
| Requirement | The access row the table is read under: `reference` (the vocabulary dataset), `accounting`, `policy`, `account`, `claim` or `org`. |

A table registered in a Nexio release is readable on the estate in that release, with no new route and no client change. The registry is fixed in code; no organization defines its own tables.

## Why it lives on the connection

A source table belongs to one system at one connection. An organization with two connections on different systems has two different contact tables, so a path that names a source table has to name the connection too. A collection is the opposite: it is one thing, answered from wherever its data lives, and Records resolves that source per connection without the source showing on the wire.

Keeping source names out of `/records` keeps the product surface vendor neutral. When a field moves from one source to another, or a second system of record is connected, the collection keeps its name and shape, and only the estate changes.

## Who reads it

The estate is for looking underneath a collection, never for building a product screen on:

* **Lineage.** Each column of the listing names the collection fields it feeds, so a field can be traced to the raw row it came from and back.
* **Operators.** Support and data work reads the raw rows of one record, under that person's own authority, to explain what a collection shows.
* **Proofs.** Parity and lifecycle checks compare collections to the rows they are built from, and count which columns a collection serves.

Product code reads collections under `/records`. A column that no collection serves yet is marked `estate_only` in the listing; the way to make it available to an application is a collection field, not an estate read.

## Authority on the estate

The estate never widens what a person can see. Every read resolves the person's authority first, as every Records read does ([Authority and scope](/data-services/scope)), then applies the table's requirement before any row is read.

* A table owned by a policy, an account or a claim is read under that record's own requirement, and only for a record inside the person's scope. A record outside it answers 404 `NOT_VISIBLE_OR_ABSENT`, the same as a record that does not exist.
* An accounting table is read only under the accounting dataset. Without it the read answers 403 `CAPABILITY_DENIED`, reason `DATASET_DENIED`.
* Direct identifiers (names, email addresses, phone numbers, free text) are served only on a read narrowed to one owner.
* An organization-wide table read for a person-scoped seat answers 200 with no rows and names the table in `unreachable`.

## List a connection's tables

`GET /connections/{id}/tables` returns, in one page sorted by name, every registered table the person may read. A table whose requirement the person's access policy does not grant is not listed: a seat without the accounting dataset sees no accounting table, exactly as reading one answers 403. `{id}` is one of your organization's connection ids, or `sandbox` for the sandbox seat's view of it; any other id answers 404 `NOT_VISIBLE_OR_ABSENT`. The listing reads no rows.

| Field | Meaning |
| - | - |
| `table` | The name to read the table by. |
| `object` | The fully qualified source object. |
| `system` | `ams360` for the system of record's replica, `warehouse` for a curated warehouse table. |
| `owner` | `policy`, `account`, `claim` or `none`. Its id is the filter a read requires. |
| `requirement` | The access row the table is read under. |
| `filter` | The match filter the table takes, named after its column (`cocode`, `custid`). Absent when it takes none. |
| `filter_required` | `true` when the table is read one filter value at a time. |
| `columns[]` | Each served column: `column` (as the system spells it), `class` (its field class), `state` and `feeds`. |
| `columns[].state` | `served` or `proven` when a collection field serves the column, `estate_only` when only the estate reads it. Absent on a curated warehouse table. |
| `columns[].feeds` | The collection fields the column serves, as `<family>.<field>`. Empty when it is `estate_only`. |

## Read a table

`GET /connections/{id}/tables/{table}` returns one page of the table's rows.

| Parameter | Meaning |
| - | - |
| `{id}` | One of your organization's connection ids, or `sandbox`. |
| `{table}` | The table's registered name, lower case. An unregistered table answers 404 `NOT_VISIBLE_OR_ABSENT`. |
| `policy_id`, `account_id` or `claim_id` | The owner filter, as an instance key. Required on a table with that owner, refused on any other. |
| The match filter | On a table with a match column, the filter named after it, lower case, such as `cocode=` or `custid=`. Required where the listing says `filter_required`. |
| `limit` | Page size. Defaults to 25; a value above 200 is coerced to 200 with a `LIMIT_COERCED` warning. |
| `cursor` | The `page.next` of the previous page. |

There is no generic `match=` parameter: it answers 400 `INVALID_ARGUMENT`, reason `FILTER_NOT_TAKEN`, naming the table's own match filter. Any other query parameter the table does not take is refused too, so a misspelt filter never widens a read. Send the identity headers on every request, as for any Records read.

The response is the one list envelope:

| Field | Meaning |
| - | - |
| `data[].row_key` | The row's identity in its table, opaque and stable. Pages follow its order. |
| `data[].source_columns` | Every registered column of the row as text, keyed `<OBJECT>.<COLUMN>`. A NULL value is left out of the object. |
| `page` | `cursor`, `next`, `prev` and `limit`, as on every list. |
| `source` | Who answered: `kind` (`warehouse`, or `absent` on the sandbox view), `family` (the registered family the table reads), the `connection`, when the read ran, and the `subject` the rows were served under. |
| `unreachable` | Parts of the answer no source could fill, each with `part` and `reason`. |
| `warnings` | Adjustments made to the request, such as `LIMIT_COERCED`. |
| `request_id` | The request id, as echoed in the `Nexio-Request-Id` response header. |

The sandbox view reads no warehouse, so every table answers 200 with no rows, `source.kind` `absent` and the reason in `unreachable`.

### Example

Read the first two rows of the code dictionary, `prcode`, a table read whole with no filter. Replace `CONNECTION_ID` with one of your connection ids.

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.usenexio.com/connections/CONNECTION_ID/tables/prcode?limit=2" \
    -H "Authorization: Bearer $NEXIO_API_KEY" \
    -H "Nexio-Act-As: user_01J8Z3K4M5N6P7Q8R9S0T1U2V3" \
    -H "Nexio-Act-As-Email: dana.ortiz@harborgroup.example"
  ```

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

  import requests

  response = requests.get(
      "https://api.usenexio.com/connections/CONNECTION_ID/tables/prcode",
      params={"limit": 2},
      headers={
          "Authorization": "Bearer " + os.environ["NEXIO_API_KEY"],
          "Nexio-Act-As": "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3",
          "Nexio-Act-As-Email": "dana.ortiz@harborgroup.example",
      },
      timeout=60,
  )
  response.raise_for_status()
  for row in response.json()["data"]:
      print(row["row_key"], row["source_columns"].get("AFW_PRCODE.DESCRIPTION"))
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch("https://api.usenexio.com/connections/CONNECTION_ID/tables/prcode?limit=2", {
    headers: {
      Authorization: `Bearer ${process.env.NEXIO_API_KEY}`,
      "Nexio-Act-As": "user_01J8Z3K4M5N6P7Q8R9S0T1U2V3",
      "Nexio-Act-As-Email": "dana.ortiz@harborgroup.example",
    },
  });
  if (!response.ok) throw new Error(`table read failed: ${response.status}`);
  const page = await response.json();
  for (const row of page.data) console.log(row.row_key, row.source_columns["AFW_PRCODE.DESCRIPTION"]);
  ```
</CodeGroup>

A narrowed read names its owner or its match filter. `custcontact` hangs off an account, so it takes `account_id`; `company` takes its match column as `cocode`:

```bash theme={null}
curl "https://api.usenexio.com/connections/CONNECTION_ID/tables/custcontact?account_id=ACCOUNT_INSTANCE_KEY" \
  -H "Authorization: Bearer $NEXIO_API_KEY" \
  -H "Nexio-Act-As: user_01J8Z3K4M5N6P7Q8R9S0T1U2V3" \
  -H "Nexio-Act-As-Email: dana.ortiz@harborgroup.example"

curl "https://api.usenexio.com/connections/CONNECTION_ID/tables/company?cocode=CARRIER_CODE" \
  -H "Authorization: Bearer $NEXIO_API_KEY" \
  -H "Nexio-Act-As: user_01J8Z3K4M5N6P7Q8R9S0T1U2V3" \
  -H "Nexio-Act-As-Email: dana.ortiz@harborgroup.example"
```

Instance keys come from a row of a Records collection: `{instance}:policy_term:{id}` for a policy term and `{instance}:customer:{id}` for an account. Send them back unchanged.

## Pagination and cursors

The table read pages through `page.next` until it is `null`, the same walk as a register ([Walk a whole register safely](/data-services/registers#walk-a-whole-register-safely)).

* The cursor is opaque. It records the table and its filters: resend the first page's owner or match filter with it, unchanged. A different filter answers 400 `INVALID_ARGUMENT`, reason `CURSOR_FILTER_MISMATCH`.
* A cursor this API did not issue answers reason `INVALID_CURSOR`. A cursor the read can no longer continue from answers reason `CURSOR_EXPIRED` (400 `INVALID_ARGUMENT`, or 424 `SOURCE_UNAVAILABLE` when the warehouse read can no longer continue from it), as on every Records list: drop it and start again from the first page.
* The identity headers are never taken from a cursor. Resend them on every 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.

## Errors

Every refusal is an RFC 9457 problem (`application/problem+json`) with an UPPER\_SNAKE `code`, and a finer cause in `reason` when there is one.

| Status | Code and reason | Cause | Fix |
| - | - | - | - |
| 400 | `INVALID_ARGUMENT`, `FILTER_REQUIRED` | The table's owner filter, or its required match filter, is missing. | Send the filter the listing names. |
| 400 | `INVALID_ARGUMENT`, `FILTER_NOT_TAKEN` | A filter the table does not take, such as another owner's id or the generic `match`. | Send only the table's own filters; the match filter is named after its column. |
| 400 | `INVALID_ARGUMENT`, `INVALID_ID` | An owner id that is not an instance key of its kind. | Send the id exactly as a collection returned it. |
| 400 | `INVALID_ARGUMENT` | A query parameter the route does not declare. | Send only the parameters listed above. |
| 400 | `INVALID_ARGUMENT`, `INVALID_CURSOR`, `CURSOR_FILTER_MISMATCH` or `CURSOR_SUBJECT_MISMATCH` | A cursor this read did not issue, or issued for another table, filter or subject. | Use the cursor exactly as returned, with the first page's filters. |
| 400 | `INVALID_ARGUMENT`, `CURSOR_EXPIRED` | The read cannot continue from the cursor. | Restart from the first page. |
| 400 | `INVALID_ARGUMENT`, `INVALID_TABLE_QUERY` | A query the table's reader cannot serve. | Check the filters against the listing. |
| 400 | `INVALID_ARGUMENT`, `CONNECTION_MISMATCH` | A `connection_id` query parameter names another connection than the path. | Drop `connection_id`; the path names the connection. |
| 403 | `CAPABILITY_DENIED`, `DATASET_DENIED` | The person's access policy does not grant the table's requirement, such as the accounting dataset. | Read it as a person who holds that access, or read the collection instead. |
| 404 | `NOT_VISIBLE_OR_ABSENT` | The table is not registered, the id is not one of your organization's connections, or the owner record is outside the person's scope. | Check the name against the listing and the id against your connections. |
| 424 | `SOURCE_UNAVAILABLE` | The warehouse could not answer the read now. | Retry after `retry_after_ms` when present. See [Completeness and errors](/data-services/completeness). |

Authority refusals are on [Authority and scope](/data-services/scope#refusal-codes).

## Next

<CardGroup cols={2}>
  <Card title="List a connection's tables" href="/api-reference/connections/list-tables">Endpoint reference.</Card>
  <Card title="List a table's rows" href="/api-reference/connections/list-table-rows">Endpoint reference.</Card>
  <Card title="Records overview" href="/data-services/overview">Collections, the product surface.</Card>
  <Card title="Authority and scope" href="/data-services/scope">How the person's rows are bounded.</Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.