interface / typed SDK
Query through the TypeScript SDK
Send an authenticated, version-pinned entity query through the typed CatDB client.
Prerequisites and boundary
Run pnpm add @catdb/client, then provide a CatDB server URL and bearer credential.
request
TypeScript SDK request
import {
createCatdbClient,
type EntityQueryRequest,
} from '@catdb/client';
export async function queryCustomers(baseUrl: string, accessToken: string) {
const client = createCatdbClient({
baseUrl,
headers: { authorization: `Bearer ${accessToken}` },
});
const request: EntityQueryRequest = {
workspace_id: '00000000-0000-0000-0000-000000000001',
entity: 'Customer',
select: ['id', 'name'],
filters: [],
order_by: [],
page_size: 50,
limit: 50,
};
return client.entityQuery(request);
}
Fieldsentity required · filters required · limit required · order_by required · page_size required · select required · workspace_id required · catalog_revision_id optional · cursor optional · max_staleness_ms optional · query_id optional · require_complete optional · schema_version_id optional · source_ids optional
Response contract
A successful call returns the EntityQueryResponse shape defined by the interface. The schema below identifies the fields your application can read from that response.
Output contract · EntityQueryResponse
Output JSON Schema
{
"type": "object",
"description": "Transport-neutral entity-query answer.",
"required": [
"query_id",
"pins",
"rows",
"sources",
"completeness"
],
"properties": {
"completeness": {
"$ref": "#/$defs/Completeness",
"description": "How far this answer's denominator reaches, derived from\n[`Self::sources`]."
},
"next_cursor": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/EntityCursor",
"description": "Continuation bound to this request, authorization, and pin set."
}
]
},
"pins": {
"$ref": "#/$defs/PinnedVersions",
"description": "Exact immutable versions used."
},
"query_id": {
"type": "string",
"format": "uuid",
"description": "Query execution identity."
},
"reason": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/EmptyAnswerReason",
"description": "When [`Self::rows`] is empty, why; `None` when at least one row was\nadmitted."
}
]
},
"rows": {
"type": "array",
"items": {
"$ref": "#/$defs/EntityRow"
},
"description": "One deterministic page of reconciled rows."
},
"sources": {
"type": "array",
"items": {
"$ref": "#/$defs/SourceExecution"
},
"description": "Visible authorized source reports in deterministic order."
}
},
"$defs": {
"Completeness": {
"oneOf": [
{
"type": "object",
"description": "Every source this caller is authorized to see was examined and\naccounted for. Sources outside the caller's authorization were never in\nthis denominator.",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"closed_over_authorized"
]
}
}
},
{
"type": "object",
"description": "The denominator is open: these authorized sources are unaccounted for,\nand each says why.",
"required": [
"unaccounted",
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"open"
]
},
"unaccounted": {
"type": "array",
"items": {
"$ref": "#/$defs/UnaccountedSource"
},
"description": "Authorized sources missing from the denominator, in authority\norder. Never empty — an empty list would be\n[`Self::ClosedOverAuthorized`]."
}
}
}
],
"description": "How far an answer's denominator reaches.\n\nThere is deliberately no state called `complete`. An answer can only ever\nbe closed over the sources the caller was authorized to see; a source the\ncaller cannot see was never in the denominator, and no reading of the\nvisible reports can discover it. Naming the good state\n[`Self::ClosedOverAuthorized`] keeps that limit on the wire instead of\nletting `complete` imply a guarantee the runtime cannot make."
},
"EmptyAnswerReason": {
"oneOf": [
{
"type": "object",
"description": "Nothing was examined: no visible source ran to completion. The empty\nanswer says nothing about the data, only about the sources.",
"required": [
"suppressed",
"reason"
],
"properties": {
"reason": {
"type": "string",
"enum": [
"nothing_examined"
]
},
"suppressed": {
"type": "array",
"items": {
"$ref": "#/$defs/UnaccountedSource"
},
"description": "The suppressed sources, in authority order. Empty only when the\ncaller had no visible source at all — in which case nothing here\ncan explain the gap either, and saying so is the honest answer."
}
}
},
{
"type": "object",
"description": "Sources ran to completion and every candidate row they examined was\nrejected.\n\n`examined: 0` here is a real measurement — every source looked and\nthere was nothing to look at — and is a different fact from\n[`Self::NothingExamined`], where nobody looked.",
"required": [
"examined",
"suppressed",
"reason"
],
"properties": {
"examined": {
"type": "integer",
"format": "int64",
"description": "Candidate rows examined across every source that ran to completion.",
"minimum": 0
},
"reason": {
"type": "string",
"enum": [
"examined_none_admitted"
]
},
"suppressed": {
"type": "array",
"items": {
"$ref": "#/$defs/UnaccountedSource"
},
"description": "Sources that produced no evidence, if any. When this is non-empty\n`examined` covers only part of the population, not the whole of it."
}
}
},
{
"type": "object",
"description": "Sources admitted candidate rows and none survived CatDB's own\nevaluation of the query.\n\nA source's `admitted` count is measured *before* reconciliation and the\nresidual predicate, so this is the one empty answer whose cause is not\nvisible in any per-source number: every report can show `admitted > 0`\nwhile the caller receives nothing. Reporting it as\n[`Self::ExaminedNoneAdmitted`] would state that every examined candidate\nwas rejected *at its source*, which the reports themselves contradict.\n\nTwo things land here, and the caller needs to tell them from a source\nthat filtered:\n\n- **Residual evaluation.** The source could not push the filter down, so\n it admitted rows it had not tested and CatDB applied the predicate\n afterwards.\n- **Precedence.** A lower-precedence source supplied a matching value, a\n higher-precedence source won the field, and the reconciled row no\n longer satisfies the query.\n\nIn both cases the rows were real and the answer is still empty.",
"required": [
"examined",
"admitted",
"suppressed",
"reason"
],
"properties": {
"admitted": {
"type": "integer",
"format": "int64",
"description": "Candidate rows the sources admitted to reconciliation, all of which\nreconciliation or the post-reconciliation predicate removed.",
"minimum": 0
},
"examined": {
"type": "integer",
"format": "int64",
"description": "Candidate rows examined across every source that ran to completion.",
"minimum": 0
},
"reason": {
"type": "string",
"enum": [
"admitted_none_survived_final_evaluation"
]
},
"suppressed": {
"type": "array",
"items": {
"$ref": "#/$defs/UnaccountedSource"
},
"description": "Sources that produced no evidence, if any."
}
}
}
],
"description": "Why an answer carried no rows.\n\nPresent only when [`EntityQueryResponse::rows`] is empty, and absent\notherwise — the same shape `BuildContextResponse::reason` uses on the\ndocument path, so both surfaces answer \"why is there nothing here\" the same\nway. The variants keep \"the denominator was empty\" and \"the denominator was\nN and nothing matched\" apart, because those two mean opposite things about\nthe data."
},
"EntityCursor": {
"type": "string",
"description": "An opaque, versioned entity-query continuation cursor.",
"maxLength": 8192
},
"EntityRow": {
"type": "object",
"description": "One typed, reconciled semantic row.",
"required": [
"record_key",
"fields",
"provenance"
],
"properties": {
"fields": {
"type": "object",
"description": "Explicitly selected fields; absent remains different from JSON null.",
"additionalProperties": {},
"propertyNames": {
"type": "string"
}
},
"provenance": {
"type": "object",
"description": "Provenance for every selected field.",
"additionalProperties": {
"$ref": "#/$defs/FieldProvenance"
},
"propertyNames": {
"type": "string"
}
},
"record_key": {
"type": "string",
"description": "Stable source record key."
}
}
},
"FieldObservation": {
"type": "object",
"description": "One source's evidence for one field on one source record.",
"required": [
"source_version_id",
"record_key",
"mapping_version_id",
"state"
],
"properties": {
"mapping_version_id": {
"type": "string",
"format": "uuid"
},
"observed_at_ms": {
"type": [
"integer",
"null"
],
"format": "int64"
},
"physical_attribute": {
"type": [
"string",
"null"
]
},
"record_key": {
"type": "string"
},
"source_version_id": {
"type": "string",
"format": "uuid"
},
"state": {
"$ref": "#/$defs/WireObservationState"
}
}
},
"FieldProvenance": {
"type": "object",
"description": "Provenance for one resolved field.",
"required": [
"provenance_rule_version_id",
"field",
"considered"
],
"properties": {
"considered": {
"type": "array",
"items": {
"$ref": "#/$defs/FieldObservation"
}
},
"field": {
"type": "string"
},
"provenance_rule_version_id": {
"type": "string",
"format": "uuid"
},
"selected": {
"type": [
"integer",
"null"
],
"minimum": 0
}
}
},
"PinnedVersions": {
"type": "object",
"description": "Exact immutable versions used by one answer.",
"required": [
"catalog_resolver_id",
"catalog_revision_id",
"schema_version_id",
"physical_schema_version_ids",
"mapping_version_ids",
"source_version_ids",
"policy_version_ids",
"provenance_rule_version_id"
],
"properties": {
"catalog_resolver_id": {
"type": "string",
"format": "uuid",
"description": "Stable identity of the catalog authority that resolved the revision."
},
"catalog_revision_id": {
"type": "string",
"format": "uuid",
"description": "Catalog revision resolved once at query start."
},
"mapping_version_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "Mapping versions used, in source-report order."
},
"physical_schema_version_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "Physical schema versions used, in source-report order."
},
"policy_version_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "Policy versions in authoritative revision order."
},
"provenance_rule_version_id": {
"type": "string",
"format": "uuid",
"description": "Exact provenance rule version used to reconcile every field."
},
"schema_version_id": {
"type": "string",
"format": "uuid",
"description": "Semantic schema version."
},
"source_version_ids": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"description": "Source versions used, in source-report order."
}
}
},
"SourceAccounting": {
"oneOf": [
{
"type": "object",
"description": "The source ran to completion.\n\nIt examined `examined` candidate rows; `admitted` of them survived\nevery pushed and residual predicate and were offered to reconciliation,\nand `rejected` did not. `admitted + rejected == examined`.\n\n`admitted` is measured where this source stops: whether a reconciled\nrow reached the caller is an answer-level fact (see\n[`EntityQueryResponse::reason`]), because one reconciled row may draw\nits fields from several sources.",
"required": [
"examined",
"admitted",
"rejected",
"state"
],
"properties": {
"admitted": {
"type": "integer",
"format": "int64",
"description": "Rows that survived every predicate and reached reconciliation.",
"minimum": 0
},
"examined": {
"type": "integer",
"format": "int64",
"description": "Candidate rows the source physically examined, across all pages.",
"minimum": 0
},
"rejected": {
"type": "integer",
"format": "int64",
"description": "Rows examined and removed by a pushed or residual predicate.",
"minimum": 0
},
"state": {
"type": "string",
"enum": [
"examined"
]
}
}
},
{
"type": "object",
"description": "The source was asked, began reading, and had its work discarded before\nit could finish.\n\n`examined` is work performed, not evidence about the data — nothing it\nread reached the answer — which is why there is no admitted or rejected\ncount to read here.",
"required": [
"examined",
"cause",
"state"
],
"properties": {
"cause": {
"$ref": "#/$defs/SuppressionCause",
"description": "Why its work was discarded."
},
"examined": {
"type": "integer",
"format": "int64",
"description": "Candidate rows examined before the source was abandoned.",
"minimum": 0
},
"state": {
"type": "string",
"enum": [
"abandoned"
]
}
}
},
{
"type": "object",
"description": "The source examined no candidate row at all.\n\nEither it was never asked — the caller's authorization does not reach\nit, or a budget was already gone — or it was asked and failed before it\ncould look at anything. `cause` says which. Either way no count from it\nexists, which is why this variant carries no number: a `0` here would\nbe a fabricated measurement, not a small one.",
"required": [
"cause",
"state"
],
"properties": {
"cause": {
"$ref": "#/$defs/SuppressionCause",
"description": "Why it examined nothing."
},
"state": {
"type": "string",
"enum": [
"unexamined"
]
}
}
}
],
"description": "What one source contributed to an answer's denominator.\n\nThree structurally different values, not three readings of one number.\n\"Examined 19 and admitted none\", \"started reading and had its work thrown\naway\", and \"was never asked\" are separate facts, and a caller must not have\nto infer which one a pair of zeroes meant.\n\n[`Self::Unexamined`] carries **no numeric field at all**, so a client\ncannot read a `0` out of a source that never ran and mistake it for a\nmeasurement."
},
"SourceExecution": {
"type": "object",
"description": "One visible source execution and freshness report.",
"required": [
"source_id",
"source_version_id",
"outcome",
"accounting"
],
"properties": {
"accounting": {
"$ref": "#/$defs/SourceAccounting",
"description": "What this source contributed to the answer's denominator."
},
"freshness_ms": {
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Age at execution time, or `None` when the source did not run.",
"minimum": 0
},
"outcome": {
"$ref": "#/$defs/SourceOutcome",
"description": "Typed execution outcome."
},
"retry_after_ms": {
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Provider-requested delay before retrying a rate-limited source.",
"minimum": 0
},
"source_id": {
"type": "string",
"format": "uuid",
"description": "Stable source identity."
},
"source_version_id": {
"type": "string",
"format": "uuid",
"description": "Exact immutable source version."
}
}
},
"SourceOutcome": {
"type": "string",
"description": "Outcome of one authorized source execution.",
"enum": [
"ran",
"refused",
"stale",
"runtime_refused",
"budget_exceeded",
"authentication_failed",
"rate_limited",
"incomplete_search",
"transport_failed",
"source_cancelled",
"schema_drift",
"source_failed",
"protocol_failed",
"timed_out",
"source_timed_out",
"cancelled"
]
},
"SuppressionCause": {
"type": "string",
"description": "Why a source contributed nothing to an answer's denominator.\n\nA cause is always carried alongside the fact of suppression, because\n\"this source is missing\" without \"and here is why\" leaves the caller to\nguess whether the gap is benign.",
"enum": [
"refused",
"unauthorized",
"stale",
"exhausted",
"unavailable"
]
},
"UnaccountedSource": {
"type": "object",
"description": "One authorized source that is missing from an answer's denominator.",
"required": [
"source_id",
"cause"
],
"properties": {
"cause": {
"$ref": "#/$defs/SuppressionCause",
"description": "Why this source is unaccounted for."
},
"source_id": {
"type": "string",
"format": "uuid",
"description": "Stable source identity visible to the caller."
}
},
"additionalProperties": false
},
"WireObservationState": {
"oneOf": [
{
"type": "object",
"required": [
"value",
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"observed_value"
]
},
"value": {}
}
},
{
"type": "object",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"observed_null"
]
}
}
},
{
"type": "object",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"field_unseen"
]
}
}
},
{
"type": "object",
"required": [
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"record_unseen"
]
}
}
},
{
"type": "object",
"required": [
"value",
"state"
],
"properties": {
"state": {
"type": "string",
"enum": [
"not_asked"
]
},
"value": {
"type": "object",
"required": [
"reason"
],
"properties": {
"reason": {
"type": "string"
}
}
}
}
}
]
}
}
}completeness- How far this answer's denominator reaches, derived from [`Self::sources`].
next_cursor- unknown value.
pins- Exact immutable versions used.
query_id- Query execution identity.
reason- unknown value.
rows- One deterministic page of reconciled rows.
sources- Visible authorized source reports in deterministic order.
Version, schema, and provenance
The SDK types and entityQuery method are generated from the OpenAPI EntityQueryRequest and EntityQueryResponse contracts.
- Every entity query resolves and pins catalog, schema, mapping, source, and policy versions before execution.
- Responses include per-field provenance, source execution reports, and an explicit completeness outcome.
- Authorization happens before planning or source disclosure.
- Unknown fields and unsupported operations return typed errors instead of changing query semantics.