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.38.0. PATCH and create-by-typeName-alone require SDK 0.26+ — relevant only if your own integration pins an SDK build older than that; every first-party client is well past it.

Conventions

The list envelope

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

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

Drain it by feeding the verbatim nextCursor you were given back as startFrom, until nextCursor is null — that is the only end-of-results signal, on every endpoint that returns this envelope. A page can come back short, or even empty, while more results remain; never infer completion from page size or fullness. search.content and usage endpoints are not enveloped (they return their own shapes).

On list and lookup endpoints, the cursor is opaque and encrypted — never construct, parse, or store one long-term; a caller-constructed startFrom is rejected with 400, and a cursor is only valid for the exact query parameters it was issued for. Version history endpoints (get*Versions) resume from a plain row id instead — still echo nextCursor back rather than building your own, but it is not encrypted, and a caller-supplied id is the expected shape there rather than a rejected one.

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 and scopes are reserved and may not be redeclared as lookup fields or payload keys.

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

MethodPurpose
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 (list envelope).
getSchemaVersions({ id })Version history (list envelope).

Schema fields

FieldTypeRequiredNotes
typeNamestringyesImmutable after create. Identifier grammar. Unique within your ownership scope — a different owner may declare a schema under the same typeName via basedOn (see below).
displayNamestringyesHuman-readable name.
descriptionstringno
fieldsFieldDef[]noOmit for a bare schema (no validation).
lookupFieldsLookupDef[]noMax 10 (partner-declared); see lookup notes.
renderHintsmap keyed by fieldIdnoUI hints; does not affect storage/validation.
capabilitiesmap<string,boolean>noauditHistory (default true).
indexModeenumnoHYBRID | SEMANTIC | TEXT | NONE. Type-level default for instances. Omit = no default.
storageProfileenumnoSTANDARD (default) | LOW_LATENCY | LARGE_PAYLOAD.
allowedSurfacesstring[]yesNon-empty. Any of record, document, user, entity. Identity entities in every namespace — org, client, or one you register — bind under the single entity surface.
activebooleannoDefault true. Inactive schemas reject new record creation.
userId / scopesstring / string[]noOwnership defaults for the schema itself. scopes is <namespace>:<value> entries, max 2.
basedOnstringnoSchema id of the lineage base this schema customizes. The first schema created under a typeName has no basedOn and becomes that name's shared base (must be created by a root/unscoped credential with no userId/scopes); every other schema of that name must declare basedOn, pointing directly at the base (one hop — a variant cannot base off another variant). Immutable once set.

FieldDef

FieldTypeNotes
fieldIdstringRequired. Identifier grammar.
fieldTypeenumstring | number | boolean | date | enum | array | object | reference.
requiredbooleanEnforced on create.
searchablebooleanField text enters the full-text search lane.
filterablebooleanField available as a search filter (no relevance influence).
descriptionstring
validationobjectValidation rules (below).
enumValuesarrayAllowed values for enum fields.
sensitivebooleanRedact-at-write + search-exclusion + read-masking + blind-indexed lookup.
targetTypeNamestringreference only: the type pointed at (required for references).
targetFieldstringreference only: target lookup field to resolve against (default externalId; must be a unique lookup on the target).
cardinalityenumreference only: one (default) | many.
targetSurfacestringreference only (required for references): surface the target lives on — record, document, user, or the name of an entity-backed namespace (org, client, or one you registered via POST /v1/namespaces). Not a closed enum — the namespace must already be registered and entity-backed, or the schema is rejected at authoring time.

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.

64-bit number range (platform-wide, independent of any schema min/max). Every JSON number you write — in any field, whether or not your schema declares it, and in a search request body as well as a payload — must fall within the signed 64-bit range (−9223372036854775808 to 9223372036854775807), carry at most 38 significant digits, and be finite. A value outside that range (or a magnitude below roughly 1e-130) is rejected with a 400 naming the field. Store a large whole number you need to preserve exactly — an external id, an account number, epoch-nanoseconds — in a string field: it keeps the digits byte-exact and still supports exact-match lookup, where a number field would round or reject. (A record written before this rule that holds an out-of-range number may now read back with that field absent or coerced to a string.)

LookupDef

A lookup declares either a single field or a composite of fields — never both, and fieldName is optional on LookupDef precisely because a composite entry omits it:

  • Single-field: a bare field name string, or { fieldName, unique }. unique: true enforces one record per value per tenant+context.
  • Composite (record surface only): { fieldNames: [...] } — two or three field names, in the order they'll be queried; that order is fixed once the schema is live. A composite lookup cannot set unique or rangeEnabled. Code that reads or writes a schema's lookupFields must handle fieldName being absent when fieldNames is present — this is the one breaking shape change for typed clients this release.

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, within your own ownership scope. Re-issuing createSchema for a typeName you already own returns your existing schema rather than failing, so re-running your own provisioning step is safe. To change a schema, update it (PUT-replace).
  • A different owner reusing an existing typeName must declare basedOn. The first schema created under a name becomes that name's shared base (root/unscoped credential only, no userId/scopes); every other owner defining a schema under the same name must set basedOn to the base's schema id, or the create is rejected with a 400. A variant stays the same conceptual type as the base for references, listings, and blueprints, while declaring its own fields/validation.
  • 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.*

MethodPurpose
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?, scope?, startFrom?, limit?, includePayload? })List (list envelope). scope is one <namespace>:<value> filter (e.g. org:<id>) per call.
lookupRecords({ type, field, value?+sortFrom?+sortTo? | values?+sortFrom?+sortTo? | from?+to? | prefix?, startFrom?, limit?, includePayload? })Lookup, one mode (list envelope). For a composite lookup, field is comma-joined and pairs with values (array, order matches the schema's fieldNames).
lookupRecordsByBody({ ... })Body-based lookup (sensitive-safe).
getRecordVersions({ id })Version history (list envelope).
getRecordTombstone({ id })Tombstone for a deleted record.

Create / update fields (RecordRequest)

FieldTypeNotes
typeNamestringThe record type. See type-identification below. Immutable; ignored on update.
schemaIdstringSchema to validate against. See below. Immutable; ignored on update.
payloadobjectValidated against the schema. On PUT, replaces the stored payload in full.
statusstringLifecycle/workflow status (default ACTIVE).
folderIdstringGroup with a folder. Cannot currently be cleared once set.
userIdstringOwnership (Vectros user UUID). Subject to token identity auto-assign.
scopesstring[]Ownership entity edges, each <namespace>:<value> (org:..., client:..., or a namespace you registered) — at most two. On update, an explicit scopes replaces the full set; omit to leave ownership unchanged; [] clears it. Subject to token identity auto-assign.
externalIdstringStable partner id. Immutable. Unique within tenant+context+typeName (idempotent create). Max 256 chars.
indexModeenumPer-record override: HYBRID/SEMANTIC/TEXT/NONE. Immutable after create.
expectedVersionnumberOptimistic 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 to your own basedOn variant when one exists, otherwise the shared base — typeName is unique within an ownership scope, not tenant+context-wide (see basedOn in the schema fields above). With only schemaId, it resolves the type from the schema. With both, they must agree. An SDK older than 0.26 always sends both.

RecordResponse (selected fields)

id, typeName, schemaId, schemaVersion, externalId, payload, payloadExternalized, payloadBytes, status, folderId, userId, scopes, indexStatus (PENDING_INDEX | INDEXED | SKIPPED | FAILED, null for store-only; SKIPPED = no indexable text, so nothing was indexed — stored + retrievable, not an error), indexFailure (present only when indexStatus is FAILED — an object with a stable code and a human-readable message; see the code table in the operations-trust reference), 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 and by scopes without declaring them and without counting against the 10-field cap — so listRecords({ type, userId }) and listRecords({ type, scope: 'org:<id>' }) resolve directly (scope takes one <namespace>:<value> filter per call).

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, scopes, 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.*

MethodPurpose
ingestDocument(body)Inline text ingest.
uploadDocument(body)Request a presigned upload URL (file path).
getDocument({ id })Fetch by id.
getDocumentText({ id })Retrieve the retained text — always available for text-ingested documents; for file uploads unless uploaded with storeText: false.
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?, scope?, startFrom?, limit? })List (list envelope). scope is one <namespace>:<value> filter per call.
lookupDocuments(...) / lookup-by-bodyLookup on a schema-bound document's lookup fields.
getDocumentVersions({ id })Version history (list envelope).

Upload fields (FileUploadRequest — uploadDocument)

In addition to fileName/fileType/indexMode/ownership/payload/schemaId/externalId:

FieldTypeNotes
storeTextbooleanDefault true: the extracted text is retained — retrievable via getDocumentText and usable by document-ask. Set false to discard the extracted text once indexing completes (search and the original-file download are unaffected; getDocumentText then 404s and document-ask 409s). Fixed at ingest: it cannot be changed later, and a re-upload keeps the original choice.

Ingest / update fields (DocumentRequest)

Text-ingested documents always retain their body (it IS the document) — there is no retention flag on this path.

FieldTypeNotes
titlestringRequired.
textstringInline ingest body. Required on POST ingest; on PUT/PATCH it re-ingests write-through.
indexModeenumHYBRID/SEMANTIC/TEXT/NONE. Optional if the bound schema sets a default; otherwise required. Fixed at creation.
folderIdstringDefaults to the context root. Cannot be cleared once set.
payloadobjectStructured 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.
schemaIdstringOptional schema to validate + lookup-index the payload against.
userIdstringOwnership (Vectros user UUID).
scopesstring[]Ownership entity edges, each <namespace>:<value>, at most two. Same replace-on-update semantics as records.
externalIdstringStable partner id. Immutable. Unique within tenant+context (idempotent ingest). Max 256 chars.
expectedVersionnumberOptimistic 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), indexFailure (present only when the status is FAILED — an object with a stable code and a human-readable message; see the code table in the operations-trust reference), indexMode, storeText, folderId, payload, payloadExternalized, schemaId, schemaVersion, textBytes, userId, scopes, fileType, fileSize, createdAt, lastModified, version.

Notes & limits — documents

  • PUT replaces the payload in full; PATCH (0.26+) merges. PATCH patchable keys: title, text, folderId, schemaId, userId, scopes, payload, expectedVersion. indexMode, externalId, and storeText (text retention is fixed at ingest) 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 serves the retained text: always available for text-ingested documents, and for file uploads unless uploaded with storeText: false (which discards the extracted text once indexing completes — the original file stays downloadable). Returns 404 when the text is not retained or extraction has not completed.
  • The presigned PUT must omit the Authorization header — the URL itself carries the grant.

Folders — client.folders.*

MethodPurpose
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?, scope?, startFrom?, limit? })List (list envelope). scope is one <namespace>:<value> filter per call.
getFolderVersions({ id })Version history (list envelope).

Create / update fields (FolderRequest)

FieldTypeNotes
namestringRequired.
descriptionstringOptional.
parentFolderIdstringApplied at create only; ignored on update. Omit to create under the context root.
slugstringStable, sibling-unique slug; derived from the name when omitted. Lowercase letters/digits/hyphens. Immutable.
userIdstringOwnership (Vectros user UUID).
scopesstring[]Ownership entity edges, each <namespace>:<value>, at most two.
expectedVersionnumberOptimistic concurrency. Ignored on create.

FolderResponse (selected fields)

id, name, description, parentFolderId (null only for a true root), slug, depth (0 at root), isProtected, userId, scopes, 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, scopes, expectedVersion. slug and parentFolderId are immutable and rejected.

Lookups & references

Lookup modes

Exactly one mode per call:

ModeParametersConstraints
exactvalue (single field), or values (composite — array, order matches fieldNames)Any lookup field. A single-element values means the same as value. Sensitive fields must use the body variant. May be narrowed with sortFrom/sortTo (below).
rangefrom + toBoth required. Inclusive, ascending. Non-sensitive, single-field lookups only.
prefixprefixString, non-sensitive, single-field lookups 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.

Composite lookups (record surface only)

A LookupDef can name two or three fields together via fieldNames instead of a single fieldName. Query it with a comma-joined field (field: 'status,area') and a matching values array in the same order — repeated query params on GET, an array in the body on POST. Field order is fixed at authoring time and cannot be changed later; you may query a leading run of the declared fields (the first field alone, the first two, and so on) but never a later field on its own — declare a separate lookup for that. Supplying fewer values than declared returns records grouped by the unspecified fields; a requested sort order then applies within each group, not across the whole result. A composite lookup cannot be unique or rangeEnabled, and its schema's allowedSurfaces must be record only.

Sort-key window (sortFrom / sortTo)

An exact-value lookup accepts optional sortFrom/sortTo bounds on the lookup field's own sort key — inclusive, either given alone, expressed in that field's own units (epoch milliseconds for a timestamp field such as createdAt/lastUpdated). On a composite lookup, a sort-key window requires the full tuple of values; it is not available on a grouped/partial query. Records with no value on the sorted field sort ahead of records that have one, and are never included in a bounded window. The result still pages with the standard { data, nextCursor } envelope.

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 { data, nextCursor } list envelope of immutable version rows for an audited entity.

Version-row fields

FieldTypeNotes
idstringVersion-row id.
changeTypeenumCREATE | UPDATE | DELETE.
previousContentstringJSON-stringified snapshot of the state prior to this change. Populated on UPDATE/DELETE; null on CREATE. JSON.parse to inspect.
previousVersionnumberVersion number before this change; null on CREATE.
changeReasonstringCaller-supplied or system-derived reason.
changedBystringId of the user / key responsible.
createdAtstringISO-8601 timestamp of the row.
changedFieldsobjectField-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.

Errors

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

StatusMeaning (data-model context)
400Validation 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, a top-level field set to null in a PATCH, a number outside signed 64-bit range / with more than 38 significant digits / non-finite, a caller-constructed (non-verbatim) startFrom, or a startFrom used with different query parameters than it was issued for (the error names the field, where applicable).
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_CONFLICTexpectedVersion did not match — the entity changed since you read it; it is left untouched.
501 not_implementedA reserved-but-unbuilt operation (batch record write/lookup/get).

Where to go next