# Data model reference

> Exhaustive reference for records, schemas, documents, folders, lookups, references,
> and version history: methods, parameters, field types, validation rules, limits,
> envelope shape, error codes, and an honest "Notes & limits" for each area.
>
> This page does not reproduce the raw endpoint reference — the full request/response
> shapes are in the generated **API reference** (OpenAPI / Scalar). Method names below
> are the Node SDK sub-client methods. The spec is currently at **0.29.9**. **PATCH** and
> **create-by-`typeName`-alone** require **SDK 0.26+** (bundled in the CLI and MCP server);
> all other calls also work on a 0.23 client.

## Conventions

### The list envelope (FC-01)

List, lookup, and version endpoints return a paginated envelope:

```jsonc
{ "data": [ /* items */ ], "nextCursor": "opaque-string-or-null" }
```

Drain it by feeding `nextCursor` back as `startFrom` until it is `null`. The cursor is
opaque — never construct or offset it. **`search.content` and usage endpoints are not
enveloped** (they return their own shapes).

### Identifiers

`typeName`, field ids, and lookup field names share one grammar: `^[A-Za-z0-9_-]{1,64}$`
— letters (any case), digits, underscore, hyphen; 1–64 characters. `camelCase`,
`snake_case`, `PascalCase`, and `kebab-case` are all valid. The names `userId`, `orgId`,
and `clientId` are reserved and may not be redeclared as lookup fields.

### Timestamps

Creation and modification timestamps are returned as ISO-8601 UTC strings.

### Optimistic concurrency

Records, documents, and folders accept an optional `expectedVersion` on update/patch.
Supply the `version` you last read; the write is rejected with `409 VERSION_CONFLICT` if
the entity changed since, leaving it untouched. Omit it for last-write-wins.

---

## Schemas — `client.schemas.*`

| Method | Purpose |
|---|---|
| `createSchema(body)` | Create a schema. |
| `getSchema({ id })` | Fetch by id. |
| `updateSchema({ id, body })` | **PUT** full-replace (no PATCH). |
| `deleteSchema({ id })` | Delete. |
| `listSchemas({ startFrom?, limit?, surface? })` | List (FC-01 envelope). |
| `getSchemaVersions({ id })` | Version history (FC-01 envelope). |

### Schema fields

| Field | Type | Required | Notes |
|---|---|---|---|
| `typeName` | string | yes | Immutable after create. Identifier grammar. Unique per tenant+context. |
| `displayName` | string | yes | Human-readable name. |
| `description` | string | no | |
| `fields` | FieldDef[] | no | Omit for a bare schema (no validation). |
| `lookupFields` | LookupDef[] | no | Max **10** (partner-declared); see lookup notes. |
| `renderHints` | map keyed by fieldId | no | UI hints; does not affect storage/validation. |
| `capabilities` | map<string,boolean> | no | `auditHistory` (default **true**). |
| `indexMode` | enum | no | `HYBRID` \| `SEMANTIC` \| `TEXT` \| `NONE`. Type-level default for instances. Omit = no default. |
| `storageProfile` | enum | no | `STANDARD` (default) \| `LOW_LATENCY` \| `LARGE_PAYLOAD`. |
| `allowedSurfaces` | string[] | **yes** | Non-empty. Any of `record`, `document`, `user`, `org`, `client`. |
| `active` | boolean | no | Default true. Inactive schemas reject new record creation. |
| `userId` / `orgId` / `clientId` | string | no | Ownership defaults for the schema itself. |

### FieldDef

| Field | Type | Notes |
|---|---|---|
| `fieldId` | string | Required. Identifier grammar. |
| `fieldType` | enum | `string` \| `number` \| `boolean` \| `date` \| `enum` \| `array` \| `object` \| `reference`. |
| `required` | boolean | Enforced on create. |
| `searchable` | boolean | Field text enters the full-text search lane. |
| `filterable` | boolean | Field available as a search filter (no relevance influence). |
| `description` | string | |
| `validation` | object | Validation rules (below). |
| `enumValues` | array | Allowed values for `enum` fields. |
| `sensitive` | boolean | Redact-at-write + search-exclusion + read-masking + blind-indexed lookup. |
| `targetTypeName` | string | `reference` only: the type pointed at (required for references). |
| `targetField` | string | `reference` only: target lookup field to resolve against (default `externalId`; must be a unique lookup on the target). |
| `cardinality` | enum | `reference` only: `one` (default) \| `many`. |
| `targetSurface` | enum | `reference` only (**required** for references): surface the target lives on — `record`/`document`/`user`/`org`/`client`. |

### Validation rules (the `validation` object)

`required`, `minLength`, `maxLength`, `min`, `max`, `pattern`, `email`, `url`, `phone`,
`step`, `multipleOf`, `minItems`, `maxItems`. Rules are enforced at record write; a
violation returns a 400 with a readable message before the record is persisted.

### LookupDef

A lookup field is either a bare field name string, or `{ fieldName, unique }`.
`unique: true` enforces one record per value per tenant+context.

### RenderHintDef

`label`, `widget` (`text` \| `textarea` \| `select` \| `date` \| `checkbox`), `order`,
`section`, `helpText`, `displayField` (marks the headline field; at most one per schema).

### Schema versioning

`schemaVersion` is a public revision counter: `1` on create, prior + 1 on each update.
Records and documents are stamped with the governing `schemaVersion` at write and keep
that value even after the schema evolves. `getSchemaVersions` returns the immutable
version-row history (same envelope and row shape as record versions).

### Notes & limits — schemas

- **No PATCH.** Schemas are PUT-replace only. Collection fields (`fields`,
  `lookupFields`, `renderHints`, `capabilities`) are replaced in full on update — supply
  the complete intended set; omitted *scalar* fields are preserved.
- `typeName` is immutable after creation.
- **Creating a schema is idempotent by `typeName`.** Re-issuing `createSchema` for a
  `typeName` that already exists returns the existing schema rather than failing, so a
  provisioning step that runs twice is safe. To change a schema, update it (PUT-replace).
- A bare schema runs no payload validation; all string values are still text-indexed for
  search when its index mode permits.
- Schema-field reference targets are declarable today; write-time existence/type
  enforcement of references is not yet active — a `reference` field carries the link but
  is not yet validated against the target on write.

---

## Records — `client.records.*`

| Method | Purpose |
|---|---|
| `createRecord(body)` | Create. `typeName` and/or `schemaId` (see below). |
| `getRecord({ id })` | Fetch by id (always full payload). |
| `updateRecord({ id, body })` | **PUT** — replace mutable fields; payload replaced in full. |
| `patchRecord({ id, body })` | **PATCH** (RFC 7386). **SDK 0.26+.** |
| `deleteRecord({ id })` | Hard delete (+ tombstone). |
| `listRecords({ type, userId?, orgId?, clientId?, startFrom?, limit?, includePayload? })` | List (FC-01 envelope). |
| `lookupRecords({ type, field, value? \| from?+to? \| prefix?, startFrom?, limit?, includePayload? })` | Lookup, one mode (FC-01 envelope). |
| `lookupRecordsByBody({ ... })` | Body-based lookup (sensitive-safe). |
| `getRecordVersions({ id })` | Version history (FC-01 envelope). |
| `getRecordTombstone({ id })` | Tombstone for a deleted record. |

### Create / update fields (RecordRequest)

| Field | Type | Notes |
|---|---|---|
| `typeName` | string | The record type. See type-identification below. Immutable; ignored on update. |
| `schemaId` | string | Schema to validate against. See below. Immutable; ignored on update. |
| `payload` | object | Validated against the schema. On PUT, **replaces** the stored payload in full. |
| `status` | string | Lifecycle/workflow status (default `ACTIVE`). |
| `folderId` | string | Group with a folder. Cannot currently be cleared once set. |
| `userId` / `orgId` / `clientId` | string | Ownership (Vectros UUIDs). Subject to token identity auto-assign. |
| `externalId` | string | Stable partner id. Immutable. Unique within tenant+context+typeName (idempotent create). Max **256** chars. |
| `indexMode` | enum | Per-record override: `HYBRID`/`SEMANTIC`/`TEXT`/`NONE`. Immutable after create. |
| `expectedVersion` | number | Optimistic concurrency. Ignored on create. |

**Type identification (SDK 0.26+ either-or):** provide `typeName` **or** `schemaId`
(at least one). With only `typeName`, the server resolves the schema (record type is
unique per tenant+context). With only `schemaId`, it resolves the type from the schema.
With both, they must agree. A 0.23 client always sends both.

### RecordResponse (selected fields)

`id`, `typeName`, `schemaId`, `schemaVersion`, `externalId`, `payload`,
`payloadExternalized`, `payloadBytes`, `status`, `folderId`, `userId`, `orgId`,
`clientId`, `indexStatus` (`PENDING_INDEX` \| `INDEXED` \| `SKIPPED` \| `FAILED`, null for store-only;
`SKIPPED` = no indexable text, so nothing was indexed — stored + retrievable, not an error),
`indexMode`, `createdBy`, `createdAt`, `updatedAt`, `version`.

For an externalized (large) payload, list/lookup responses return only the indexed
projection and set `payloadExternalized: true`; fetch the full payload via by-id GET or
pass `includePayload: true` on the list/lookup call.

### Automatic ownership lookups

Every record is automatically lookup-indexed by `userId`, `orgId`, and `clientId`
without declaring them and without counting against the 10-field cap — so
`listRecords({ type, userId })` (etc.) resolve directly.

### Notes & limits — records

- **PUT replaces the payload in full** — it is not deep-merged. Use **PATCH** (0.26+) for
  a true partial payload update.
- **PATCH** patchable keys: `payload`, `status`, `folderId`, `userId`, `orgId`,
  `clientId`, `expectedVersion`. Immutable keys (`typeName`, `schemaId`, `externalId`,
  `indexMode`) are rejected if present. Within `payload`, a key set to `null` is deleted;
  a **top-level** patchable field (such as `status` or `folderId`) set to `null` is *not* a
  delete — it is rejected with `400`. Clearing a top-level field is not supported.
- `typeName` is immutable after creation. To change a record's type, write a new record
  and delete the old one.
- `folderId` cannot currently be cleared once set.
- Delete is hard delete — there is no soft-delete status that lingers in the index.
- **Batch write / lookup / get are reserved and not yet implemented** (they return a
  `501 not_implemented`). Do not depend on them.

---

## Documents — `client.documents.*`

| Method | Purpose |
|---|---|
| `ingestDocument(body)` | Inline text ingest. |
| `uploadDocument(body)` | Request a presigned upload URL (file path). |
| `getDocument({ id })` | Fetch by id. |
| `getDocumentText({ id })` | Retrieve stored raw text (requires `storeText: true`). |
| `getDocumentDownloadUrl({ id })` | Presigned download URL for a file-backed document. |
| `updateDocument({ id, body })` | **PUT** — full replace; `text` re-ingests. |
| `patchDocument({ id, body })` | **PATCH** (RFC 7386). **SDK 0.26+.** |
| `deleteDocument({ id })` | Hard delete (+ tombstone). |
| `listDocuments({ userId?, orgId?, clientId?, startFrom?, limit? })` | List (FC-01 envelope). |
| `lookupDocuments(...)` / lookup-by-body | Lookup on a schema-bound document's lookup fields. |
| `getDocumentVersions({ id })` | Version history (FC-01 envelope). |

### Ingest / update fields (DocumentRequest)

| Field | Type | Notes |
|---|---|---|
| `title` | string | Required. |
| `text` | string | Inline ingest body. Required on POST ingest; on PUT/PATCH it re-ingests write-through. |
| `indexMode` | enum | `HYBRID`/`SEMANTIC`/`TEXT`/`NONE`. Optional if the bound schema sets a default; otherwise required. Fixed at creation. |
| `storeText` | boolean | Default false. When true, the raw text is retrievable via `getDocumentText`. |
| `folderId` | string | Defaults to the context root. Cannot be cleared once set. |
| `payload` | object | Structured data (records parity). Validated + lookup-indexed when `schemaId` is set; undeclared keys pass through as free-form, filterable in search. Replaced in full on PUT. |
| `schemaId` | string | Optional schema to validate + lookup-index the payload against. |
| `userId` / `orgId` / `clientId` | string | Ownership (Vectros UUIDs). |
| `externalId` | string | Stable partner id. Immutable. Unique within tenant+context (idempotent ingest). Max 256 chars. |
| `expectedVersion` | number | Optimistic concurrency. Ignored on create. |

### Upload handshake (uploadDocument)

`uploadDocument` returns `uploadUrl` and `expiresAt`. PUT the raw bytes to `uploadUrl`
**without** an Authorization header, set `Content-Type` to the file's MIME type, then
poll `getDocument` until `status` is `INDEXED`.

### DocumentResponse (selected fields)

`id`, `title`, `externalId`, `status` (`PENDING_UPLOAD` \| `UPLOADED` \| `EXTRACTING` \|
`PENDING_INDEX` \| `INDEXED` \| `SKIPPED` \| `STORED` \| `FAILED`; `SKIPPED` = extraction produced no
indexable text, so nothing was indexed — stored + retrievable, not an error), `indexMode`, `storeText`,
`folderId`, `payload`, `payloadExternalized`, `schemaId`, `schemaVersion`, `textBytes`,
`userId`, `orgId`, `clientId`, `fileType`, `fileSize`, `createdAt`, `lastModified`,
`version`.

### Notes & limits — documents

- **PUT replaces the payload in full**; **PATCH** (0.26+) merges. PATCH patchable keys:
  `title`, `text`, `storeText`, `folderId`, `schemaId`, `userId`, `orgId`, `clientId`,
  `payload`, `expectedVersion`. `indexMode` and `externalId` are immutable and rejected.
- An update re-runs the indexing pipeline; old content is removed from the index as the
  new content is written.
- `getDocumentText` requires the document to have been ingested with `storeText: true`.
- The presigned PUT must omit the Authorization header — the URL itself carries the grant.

---

## Folders — `client.folders.*`

| Method | Purpose |
|---|---|
| `createFolder(body)` | Create (optionally under a parent). |
| `getFolder({ id })` | Fetch by id. |
| `updateFolder({ id, body })` | **PUT** — name/description/ownership. |
| `patchFolder({ id, body })` | **PATCH** (RFC 7386). **SDK 0.26+.** |
| `deleteFolder({ id })` | Delete (rejects non-empty). |
| `listFolders({ userId?, orgId?, clientId?, startFrom?, limit? })` | List (FC-01 envelope). |
| `getFolderVersions({ id })` | Version history (FC-01 envelope). |

### Create / update fields (FolderRequest)

| Field | Type | Notes |
|---|---|---|
| `name` | string | Required. |
| `description` | string | Optional. |
| `parentFolderId` | string | Applied at **create only**; ignored on update. Omit to create under the context root. |
| `slug` | string | Stable, sibling-unique slug; derived from the name when omitted. Lowercase letters/digits/hyphens. Immutable. |
| `userId` / `orgId` / `clientId` | string | Ownership. |
| `expectedVersion` | number | Optimistic concurrency. Ignored on create. |

### FolderResponse (selected fields)

`id`, `name`, `description`, `parentFolderId` (null only for a true root),
`slug`, `depth` (0 at root), `isProtected`, `userId`, `orgId`, `clientId`, `createdAt`,
`lastModified`, `version`.

### Notes & limits — folders

- **No move / reparent.** `parentFolderId` is fixed at creation; there is no operation to
  relocate a folder in the hierarchy.
- **Delete rejects a non-empty folder** with a 400 — remove children first.
- The context root folder is protected (`isProtected: true`) and created lazily on first
  folder interaction. Unparented folders are placed under it, so a folder created without
  a parent still has a non-null `parentFolderId` (the root's id).
- PATCH patchable keys: `name`, `description`, `userId`, `orgId`, `clientId`,
  `expectedVersion`. `slug` and `parentFolderId` are immutable and rejected.

---

## Lookups & references

### Lookup modes

Exactly **one** mode per call:

| Mode | Parameters | Constraints |
|---|---|---|
| exact | `value` | Any lookup field. Sensitive fields must use the body variant. |
| range | `from` + `to` | Both required. Inclusive, ascending. Non-sensitive fields only. |
| prefix | `prefix` | String, non-sensitive fields only. Ascending. |

Supplying zero or more than one mode is a 400. Range with only one bound is a 400.
Prefix on a non-string field is a 400.

### Unique vs non-unique (enumeration)

- A `unique` lookup field returns at most one record and is enforced unique on write.
- A non-unique lookup field is an **enumeration** — it returns every record sharing the
  value, paginated via the `{ data, nextCursor }` envelope.

### Sensitive-field lookup (body variant)

For a `sensitive` field, the exact value must not travel in a URL. The GET lookup rejects
`value` on a sensitive field and directs you to the body-based variant, where the value
travels in the request body and is blind-indexed server-side. Range and prefix are not
available on sensitive fields (a blind-indexed value has no usable order).

### References

A `reference` field links to another record: it declares `targetTypeName` (required),
`targetField` (default `externalId`; must be a unique lookup on the target), `cardinality`
(`one`/`many`), and `targetSurface`. The platform can additionally maintain per-field
reverse-reference rows (opt-in) to index the inverse direction.

### Notes & limits — lookups & references

- Partner-declared lookup fields are capped at **10** per schema (the three automatic
  ownership lookups do not count).
- The **reverse-reference list endpoint is not yet available** — you cannot query
  back-references through the API today.
- Reference targets are declarable now; write-time existence/type enforcement of a
  reference against its target is not yet active.

---

## Version history — `getRecordVersions` / `getDocumentVersions` / `getSchemaVersions` / `getFolderVersions`

Each returns the FC-01 `{ data, nextCursor }` envelope of immutable version rows for an
audited entity.

### Version-row fields

| Field | Type | Notes |
|---|---|---|
| `id` | string | Version-row id. |
| `changeType` | enum | `CREATE` \| `UPDATE` \| `DELETE`. |
| `previousContent` | string | JSON-stringified snapshot of the state **prior** to this change. Populated on `UPDATE`/`DELETE`; null on `CREATE`. `JSON.parse` to inspect. |
| `previousVersion` | number | Version number before this change; null on `CREATE`. |
| `changeReason` | string | Caller-supplied or system-derived reason. |
| `changedBy` | string | Id of the user / key responsible. |
| `createdAt` | string | ISO-8601 timestamp of the row. |
| `changedFields` | object | Field-level diff: summary, changed field names, count, per-field old→new detail. Null on `CREATE`. |

### Audit capability & retention

- Audit history is governed per schema by `capabilities.auditHistory` (default **true**).
  Setting it false stops recording the change-history trail for that type's data; it never
  deletes or affects the records/documents themselves. Tombstones on delete are recorded
  regardless.
- Version rows are written **asynchronously** (typically 1–3 seconds after a write
  returns) — poll briefly if you read history immediately after a write.
- Audit and version history are retained compliantly; heavy historical content is
  externalized to a write-once, retention-governed store, and the history forms a
  tamper-evident continuity chain. The full retention and integrity posture lives in
  [../operations-trust/compliance.md](../operations-trust/compliance.md).

---

## Errors

The platform returns a uniform error contract. Common cases for the data model:

| Status | Meaning (data-model context) |
|---|---|
| `400` | Validation error: schema-field violation, bad identifier, multiple/zero lookup modes, range/prefix on a sensitive field, prefix on a non-string field, delete of a non-empty folder, immutable field present in a PATCH, or a top-level field set to `null` in a PATCH. |
| `404` / "not found" | The entity does not exist, **or** belongs to another tenant/context, **or** is out of the caller's token scope — a single uniform shape (the message never distinguishes these). |
| `409 VERSION_CONFLICT` | `expectedVersion` did not match — the entity changed since you read it; it is left untouched. |
| `501 not_implemented` | A reserved-but-unbuilt operation (batch record write/lookup/get). |

---

## Where to go next

- [explanation.md](explanation.md) — the concepts and the why.
- [how-to.md](how-to.md) — runnable guides for every operation above.
- [../operations-trust/compliance.md](../operations-trust/compliance.md) — version
  history retention, the tamper-evident chain, and sensitive-data handling.
